iproute2 中 tc qdisc cbs源码分析

iproute2 用户空间

Sched 与 iproute2 的通信,是典型的 Linux 内核模块和用户空间的进程之间的通信,这 种通信一般由 Netlink Socket 来提供这种双向的通信连接。这种连接由标准的提供给用户进程的 socket 和提供给内核模块的 API 组成,用户空间的接口简单的说就是创建一个 family 为 AF_NETLINK 的 socket,然后使用这个 socket 进行通信。

rtnl_open() 函数的作用是打开一个AF_NETLINK 的 socket,rtnl_close() 函数的作用是关闭一个AF_NETLINK 的 socket。

用户空间通信前的准备:填充 netlink 包;然后把 netlink 包发送到内核空间去。

tc.c

通过cbs_qdisc_util→cbs_parse_opt调用 q_cbs.c 中的cbs_parse_opt()函数。

q_cbs.c

最后通过rtnl_talk(&rth, &req.n, NULL)把 netlink 包发送到内核空间去,rtnl_talk()发送过程包括 sendmsg 和 recvmsg。

内核模块的初始化

内核模块的初始化:在 net/sched/sch_api.c 文件中的 void __init pktsched_init (void)函数中,初始化了 link_rtnetlink_table 表,link_rtnetlink_table 是一张 struct rtnetlink_link的表。

1
2
3
4
struct rtnetlink_link {
int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int __init pktsched_init(void)
{
int err;

err = register_pernet_subsys(&psched_net_ops);
if (err) {
pr_err("pktsched_init: "
"cannot initialize per netns operations\n");
return err;
}

register_qdisc(&pfifo_fast_ops);
register_qdisc(&pfifo_qdisc_ops);
register_qdisc(&bfifo_qdisc_ops);
register_qdisc(&pfifo_head_drop_qdisc_ops);
register_qdisc(&mq_qdisc_ops);
register_qdisc(&noqueue_qdisc_ops);

rtnl_register(PF_UNSPEC, RTM_NEWQDISC, tc_modify_qdisc, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_DELQDISC, tc_get_qdisc, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_GETQDISC, tc_get_qdisc, tc_dump_qdisc,
0);
rtnl_register(PF_UNSPEC, RTM_NEWTCLASS, tc_ctl_tclass, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_DELTCLASS, tc_ctl_tclass, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_GETTCLASS, tc_ctl_tclass, tc_dump_tclass,
0);

return 0;
}

struct rtnetlink_link 由函数指针 doitdumpit 组成,这张表可以由需要执行的动作的宏定义 (例如:RTM_NEWQDISCRTM_DELQDISC)来索引,以使得能通过这张表调动相应的函数。内核模块从用户空间收到的就是这些索引和参数,以此调用注册在此表中的函数。

qdisc_create()中的opt→init调用sch_cbs.ccbs_init()初始化函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct Qdisc *qdisc_create(struct net_device *dev,
struct netdev_queue *dev_queue,
struct Qdisc *p, u32 parent, u32 handle,
struct nlattr **tca, int *errp,
struct netlink_ext_ack *extack)
{
// ......
rtnl_unlock();
request_module("sch_%s", name);
rtnl_lock();
ops = qdisc_lookup_ops(kind);
// ......
if (ops->init) {
err = ops->init(sch, tca[TCA_OPTIONS], extack);
if (err != 0)
goto err_out5;
}
// ......
}

sch_cbs.c

gdb调试过程

tc部分分析

1
2
3
/bin/tc qdisc replace dev eth1 parent 6666:2 handle 7777 cbs \
idleslope 98688 sendslope -901312 hicredit 153 locredit -1389 \
offload 1

设置断点。main()—>do_cmd()—>do_qdisc()—>tc_qdisc_modify()

tc_qdisc_modify() 首先解析参数eth1 …… cbs

找到cbs_qdisc_util

通过addttr_l()添加k到NETLINK包里。

执行cbs_qdisc_util→parse_qopt

通过cbs_parse_opt()解析参数idleslope …… offload 1

通过addattr_l()添加到NETLINK包里。

与内核通信。

此时内核运行完打印消息

rtnl_close()结束与内核的通信

内核部分 net/sched/sch_api.c

断到tc_modify_qdisc()函数。

进入ops→init

解析到cbs_init()函数,初始化。

进入cbs_change() —> csb_enable_offload()

ops→ndo_setup_tc()开始进入驱动部分。

gmac网卡驱动部分

调用stmmac_tc_setup_cbs()

stmmac_config_cbs()中对寄存器进行读写配置。

对CBS相关的DMA寄存器进行读写操作。