tc源码分析二,内核中流控代码的位置
使用的内核版本
Linux 4.3
问题探索
我们要研究源码除了好奇代码的书写方式之外最关心的还是代码的实际逻辑,这样就能便于我们理解内部究竟发生了什么。
在研究Linux内核中关于流控的代码之前我有两个疑惑:
- 流控的代码是如何与网卡发生关联的?
- 流控相关代码是何时被调用的?
问题分析
1.关联网卡设备
首先要知道的是在内核中所有的流控相关的操作都在 F:\Hert\Studycode\linux-4.9.37\net\sched
目录下。 流控子系统也是在该目录下的 sch_api.c 中被注册的。
使用过 tc 命令的都知道 pfifo 和其他一些别的方法相比是一种最简单的流控方案。那就就这个功能的注册开始找起。
首先在 /net/sched/sch_api.c
中找到函数 register_qdisc(&pfifo_fast_ops);
。
int register_qdisc(struct Qdisc_ops *qops)
{
struct Qdisc_ops *q, **qp;
int rc = -EEXIST;
write_lock(&qdisc_mod_lock);
for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next)
if (!strcmp(qops->id, q->id))
goto out;
if (qops->enqueue == NULL)
qops->enqueue = noop_qdisc_ops.enqueue;
if (qops->peek == NULL) {
if (qops->dequeue == NULL)
qops->peek = noop_qdisc_ops.peek;
else
goto out_einval;
}
if (qops->dequeue == NULL)
qops->dequeue = noop_qdisc_ops.dequeue;
if (qops->cl_ops) {
const struct Qdisc_class_ops *cops = qops->cl_ops;
if (!(cops->get && cops->put && cops->walk && cops->leaf))
goto out_einval;
if (cops->tcf_chain && !(cops->bind_tcf && cops->unbind_tcf))
goto out_einval;
}
qops->next = NULL;
*qp = qops;
rc = 0;
...
在这个函数中可以知道 pfifo_fast_ops
这个结构被添加在 qdisc_case 中。
找到两条线索,一个是qdisc_lookup_ops <- qdisc_create <- qdisc_alloc(dev_queue, ops) <- tc_modify_qdisc <- rtnl_register(PF_UNSPEC, RTM_NEWQDISC, tc_modify_qdisc, NULL, NULL);
在 qdisc_alloc 中,参数 dev_queue 是设备队列,而 ops 则是经由 qdisc_base 内部找到的流控操作的结构。
在 qdisc_alloc 可以找到以下代码,可知在调用 qdisc_alloc 的时候,流控操作就会与设备进行关联。
sch->ops = ops;
sch->enqueue = ops->enqueue;
sch->dequeue = ops->dequeue;
sch->dev_queue = dev_queue;
而后在继续追找下去实际上这种方式是由 netlink 交互的方式所指定。
还有一种方式是由设备开启之时默认的流控方式。
实际上除了qdisc_lookup_ops 会使用到 qdisc_base 表,还有一个函数 qdisc_lookup_default 也同样会使用该列表。
qdisc_lookup_default <- qdisc_set_default <- set_default_qdisc (这里会有 proc 文件系统的注册)
-> qdisc_set_default 会使用 default_qdisc_ops 结构,该结构会在 attach_one_default_qdisc 函数被调用,进一步被 attach_default_qdiscs 调用 -> netdev_for_each_tx_queue(dev, attach_one_default_qdisc, NULL); 与设备产生关联。
而其本身 attach_default_qdiscs <- dev_activate <- __dev_open <- dev_open 也就说当设备启用的时候就会被设置有流控操作,忘了说明,默认的流控结构 default_qdisc_ops 的值就是 pfifo_fast_ops ,也就是说默认的流控操作为 pfifo。
2. 流控操作何时被调用
在内核发包的最后环节的 dev_queue_xmit 函数中就会对流控进行处理。
txq = netdev_pick_tx(dev, skb, accel_priv);
q = rcu_dereference_bh(txq->qdisc);
trace_net_dev_queue(skb);
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
/*此处主要是取出此netdevice的txq和txq的Qdisc,Qdisc主要用于进行拥塞处理,一般的情况下,直接将
*数据包发送给driver了,如果遇到Busy的状况,就需要进行拥塞处理了,就会用到Qdisc*/
__dev_xmit_skb 这个函数中可以看到大量关于 qdisc 的操作。
总结
至此有关于流控在内核中存在的大致位置就可以比较清晰了。
在设备被开启的之时就会有默认的 pfifo 发生的流控被与网卡进行关联。之后如果使用了 tc 命令进行设置的话其默认配置就会被修改成设置成的流控方式。