TCP发送的数据经过IP层添加IP包头后,会被发送到IP数据栈和网卡之间的队列中,当网卡能够发送数据时会到这个队列中去取skb。当TCP发送数据过快时,或一个带宽很大或者包速率很大的非TCP数据流会把队列里的所有空间占满,造成数据包丢失和延时的问题。更糟的是,这样很可能会使另一个缓存产生,进而产生一个静止队列(standing queue),造成更严重的延时并使TCP的RTT和拥塞窗口的计算出现问题。Linux3.6.0出现后,Linux内核增加了TCP小队列(TCP Small Queue)的机制,用于解决该问题。TCP小队列对每个TCP数据流中,能够同时参与排队的字节数做出了限制,这个限制是通过net.ipv4.tcp_limit_output_bytes内核选项实现的。当TCP发送的数据超过这个限制时,多余的数据会被放入另外一个队列中,再通过tastlet机制择机发送。下面分析一下TSQ的实现。
TSQ tasklet是一个每CPU变量,这样可以保证在多核条件下的并发:
678 struct tsq_tasklet {
679 struct tasklet_struct tasklet;
680 struct list_head head; /* queue of tcp sockets */
681 };
682 static DEFINE_PER_CPU(struct tsq_tasklet, tsq_tasklet);
TSQ tasklet的初始化是在内核加载时完成:
3375 void __init tcp_init(void)
3376 {
...
3456 tcp_tasklet_init();
3457 }
772 void __init tcp_tasklet_init(void)
773 {
774 int i;
775
776 for_each_possible_cpu(i) {
777 struct tsq_tasklet *tsq = &per_cpu(tsq_tasklet, i);
778
779 INIT_LIST_HEAD(&tsq->head);
780 tasklet_init(&tsq->tasklet,
781 tcp_tasklet_func,
782 (unsigned long)tsq); //初始化tasklet任务
783 }
784 }
tasklet_init函数指定了tcp_tasklet_func函数为处理函数:
697 static void tcp_tasklet_func(unsigned long data)
698 {
699 struct tsq_taskl