技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 网络系统 --> TSQ 的原理

TSQ 的原理

浏览:5952次  出处信息

   通常看到 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. 无锁消息队列    (阅读:12822)
  2. 多线程队列的算法优化    (阅读:6518)
  3. 各消息队列软件产品大比拼    (阅读:5156)
  4. Gearman Server 使用 MySQL UDFs 来管理和保持队列    (阅读:4865)
  5. 一些队列理论 吞吐量、延迟和带宽    (阅读:3261)
  6. 无锁消息队列    (阅读:3204)
  7. 实现多线程对队列的读写操作(封装类)    (阅读:3065)
  8. RabbitMQ与Redis队列对比    (阅读:2963)
  9. Feed消息队列架构分析    (阅读:2618)
  10. 一淘网offline系统简介    (阅读:2253)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
<< 前一篇:TCP/IP 相关总结
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1