IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

TSQ 的原理

A Geek's Page 2012-11-05 22:11:03 累计浏览 6,996 次
本机暂存

   通常看到 TCP Small Queue (简称 TSQ)之后第一问题是,既然我们已经有 tcp_wmem 了,为何还需要一个新的 sysctl?

   这个问题是理解 TSQ 的关键,其实 tcp_wmem 仅仅是从 TCP socket 层限制队列(或 buffer)中最多可以 queue 多少数据包,而实际上,一个包从 TCP 层发出到最后到达网卡,中间还经历了很多个 queue,TCP socket 只是其中一层罢了。而 TSQ 用了一个非常聪明的技巧来限制在所有这些 queue 中的包,从而降低 latency。

   TSQ 的代码中最关键的一个地方是它实现了一个新的 skb destructor,也就是 tcp_wfree(),看它的定义:


  • static void tcp_wfree(struct sk_buff *skb)
  • {
  •        struct sock *sk = skb->sk;
  •        struct tcp_sock *tp = tcp_sk(sk);
  •        if (test_and_clear_bit(TSQ_THROTTLED, &tp->tsq_flags) &&
  •            !test_and_set_bit(TSQ_QUEUED, &tp->tsq_flags)) {
  •                unsigned long flags;
  •                struct tsq_tasklet *tsq;
  •                /* Keep a ref on socket.
  •                 * This last ref will be released in tcp_tasklet_func()
  •                 */
  •                atomic_sub(skb->truesize - 1, &sk->sk_wmem_alloc);
  •                /* queue this socket to tasklet queue */
  •                local_irq_save(flags);
  •                tsq = &__get_cpu_var(tsq_tasklet);
  •                list_add(&tp->tsq_node, &tsq->head);
  •                tasklet_schedule(&tsq->tasklet);
  •                local_irq_restore(flags);
  •        } else {
  •                sock_wfree(skb);
  •        }
  • }

   我们知道在Linux内核网络子系统中,kfree_skb() 是内核丢包的地方,而 skb 的 destructor 就是在丢包时被调用的,用来清理和该 skb 相关的东西。TSQ 实现新的 destructor 就是想在包被丢弃的时候做一些动作,也就是如果条件合适(设置了 TSQ_THROTTLED,没有设置 TSQ_QUEUED),那么就把它加入进 TSQ。

   而在下一个 softIRQ 时,相应的 tasklet 就会调度,从而触发 tcp_tasklet_func():


  • static void tcp_tasklet_func(unsigned long data)
  • {
  •        struct tsq_tasklet *tsq = (struct tsq_tasklet *)data;
  •        LIST_HEAD(list);
  •        unsigned long flags;
  •        struct list_head *q, *n;
  •        struct tcp_sock *tp;
  •        struct sock *sk;
  •        local_irq_save(flags);
  •        list_splice_init(&tsq->head, &list);
  •        local_irq_restore(flags);
  •        list_for_each_safe(q, n, &list) {
  •                tp = list_entry(q, struct tcp_sock, tsq_node);
  •                list_del(&tp->tsq_node);
  •                sk = (struct sock *)tp;
  •                bh_lock_sock(sk);
  •                if (!sock_owned_by_user(sk)) {
  •                        tcp_tsq_handler(sk);
  •                } else {
  •                        /* defer the work to tcp_release_cb() */
  •                        set_bit(TCP_TSQ_DEFERRED, &tp->tsq_flags);
  •                }
  •                bh_unlock_sock(sk);
  •                clear_bit(TSQ_QUEUED, &tp->tsq_flags);
  •                sk_free(sk);
  •        }
  • }

   这里对加入到 TSQ 队列中的 socket 进行处理,对于没有 owner 的 socket(不是由用户使用的socket),直接发送;否则就是推迟到 tcp_release_cb() 中发送,即 release_sock() 的时候。

   现在,我们从总体上可以看出 TSQ 的用意了:当发送的包超过 sysctl_tcp_limit_output_bytes 时,就会发生抖动(throttle),这时就会把包推迟发送,推迟到什么时候呢?当然是该队列有空闲的时候!那么什么时候有空闲呢?包被丢弃的时候!内核发送的包是在即将被丢弃的时候(忽略tasklet,用tasklet仅仅是因为skb destructor 中不能进行发送),用户层发送的包则是关闭 socket 的时候,这种时候 TSQ 会有新的空间,所以可以重新入队。

   可见,TSQ 的聪明之处在于,它虽然有自己的所谓队列,但并没有计算该队列的长度,而是巧妙地利用了内核中几个关键点来判断何时可以入队!这当然需要对 TCP 实现非常熟悉才可以,由此亦可见作者水平之高。

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. gen_tcp发送进程被挂起起因分析及对策 (累计阅读 37,821)
  2. TCP 的那些事儿(上) (累计阅读 22,696)
  3. 从输入 URL 到页面加载完成的过程中都发生了什么事情? (累计阅读 15,933)
  4. 自建DNS以防止GFW干扰 (累计阅读 13,125)
  5. 浅谈TCP优化 (累计阅读 11,082)
  6. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 10,844)
  7. 查看 Apache并发请求数及其TCP连接状态 (累计阅读 10,068)
  8. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 8,840)
  9. websocket 连接 C Server的尝试 (累计阅读 7,922)
  10. 计算机网络协议包头赏析-TCP (累计阅读 7,854)