文章目录
绝大多数的场景下,网络设备都是使用默认的流量控制机制在工作,这篇笔记分析了默认的流量控制机制。
数据结构
排队规则: Qdisc
struct Qdisc
{
// 入队与出队操作
int (*enqueue)(struct sk_buff *skb, struct Qdisc *dev);
struct sk_buff *(*dequeue)(struct Qdisc *dev);
unsigned flags;
#define TCQ_F_BUILTIN 1
#define TCQ_F_THROTTLED 2
#define TCQ_F_INGRESS 4
int padded; // Qdisc的第一个成员是字节对齐的,这使得开头可能会有一定的padding
struct Qdisc_ops *ops; // 具体排队规则实现的函数操作集
struct qdisc_size_table *stab;
u32 handle; // 句柄
u32 parent; // 父节点句柄,通过这两个字段可以构建复杂的排队规则
atomic_t refcnt;
unsigned long state;
struct sk_buff *gso_skb;
struct sk_buff_head q;
struct netdev_queue *dev_queue;
struct Qdisc *next_sched;
struct list_head list;
struct gnet_stats_basic bstats;
struct gnet_stats_queue qstats;
struct gnet_stats_rate_est rate_est;
int (*reshape_fail)(struct sk_buff *skb,
struct Qdisc *q);
void *u32_node;
/* This field is deprecated, but it is still used by CBQ
* and it will live until better solution will be invented.
*/
struct Qdisc *__parent;
};
默认网络设备排队规则
dev_init_scheduler_queue()
在register_netdevice()中有调用dev_init_scheduler()对网络设备对象中的流量控制字段进行初始化。
static void dev_init_scheduler_queue(struct net_device *dev,
struct netdev_queue *dev_queue,
void *_qdisc)
{
struct Qdisc *qdisc = _qdisc;
dev_queue->qdisc = qdisc;
dev_queue->qdisc_sleeping = qdisc;
}
void dev_init_scheduler(struct net_device *dev)
{
// 将每个发送队列的排队规则都设置为noop_qdisc
netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
// 将接收队列的排队规则也设置为noop_qdisc
dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
// 初始化watchdog_timer
setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}
可见,设备注册时,会将所有的发送队列和接收队列的排队规则都设置为noop_qdisc,顾名思义,这个队列什么都不做,仅仅是释放入队列的skb,所以使用该队列是没有办法进行数据包收发的。
dev_activate()
设备打开时,在dev_open()中会调用dev_activate()激活网络设备对象的发送队列,激活后,设备接口层的dev_queue_xmit()才能通过流控机制向网卡发送数据。
void dev_activate(struct net_device *dev)
{
int need_watchdog;
/* No queueing discipline is attached to device;
create default one i.e. pfifo_fast for devices,
which need queueing and noqueue_qdisc for
virtual interfaces
*/
// 如果所有发送队列的排队规则都是noop_qdisc,那么将默认的改为pfifo_fast
if (dev_all_qdisc_sleeping_noop(dev))
netdev_for_each_tx_queue(dev, attach_one_default_qdisc, NULL);
// 没有__LINK_STATE_NOCARRIER标记说明网络设备物理层信号正常,可以发送数据
if (!netif_carrier_ok(dev))
/* Delay activation until next carrier-on event */
return;
need_watchdog = 0;
netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);
// 设置接收队列排队规则
transition_one_qdisc(dev, &dev->rx_queue, NULL);
if (need_watchdog) {
dev->trans_start