TSQ 的原理
通常看到 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 实现非常熟悉才可以,由此亦可见作者水平之高。
建议继续学习:
- 无锁消息队列 (阅读:12780)
- 多线程队列的算法优化 (阅读:6498)
- 各消息队列软件产品大比拼 (阅读:5136)
- Gearman Server 使用 MySQL UDFs 来管理和保持队列 (阅读:4856)
- 一些队列理论 吞吐量、延迟和带宽 (阅读:3253)
- 无锁消息队列 (阅读:3196)
- 实现多线程对队列的读写操作(封装类) (阅读:3046)
- RabbitMQ与Redis队列对比 (阅读:2940)
- Feed消息队列架构分析 (阅读:2581)
- 一淘网offline系统简介 (阅读:2245)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:王 聪 来源: A Geek's Page
- 标签: sysctl tcp_wmem TSQ 队列
- 发布时间:2012-11-05 22:11:03
- [67] Go Reflect 性能
- [67] Oracle MTS模式下 进程地址与会话信
- [67] 如何拿下简短的域名
- [61] IOS安全–浅谈关于IOS加固的几种方法
- [60] 图书馆的世界纪录
- [59] 【社会化设计】自我(self)部分――欢迎区
- [58] android 开发入门
- [56] 视觉调整-设计师 vs. 逻辑
- [49] 给自己的字体课(一)——英文字体基础
- [47] 界面设计速成