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的表。
struct rtnetlink_link {
int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};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 由函数指针 doit 和 dumpit 组成,这张表可以由需要执行的动作的宏定义 (例如:RTM_NEWQDISC,RTM_DELQDISC)来索引,以使得能通过这张表调动相应的函数。内核模块从用户空间收到的就是这些索引和参数,以此调用注册在此表中的函数。
在qdisc_create()中的opt→init调用sch_cbs.c的cbs_init()初始化函数。
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部分分析
/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寄存器进行读写操作。






















