TCP/IP协议栈到底是内核态的好还是用户态的好

                       

“TCP/IP协议栈到底是内核态的好还是用户态的好?”这根本就是一个错误的问题,问题的根源在于,干嘛非要这么刻意地去区分什么内核态和用户态。


引子

为了不让本文成为干巴巴的说教,在文章开头,我以一个实例分析开始。

最近一段时间,我几乎每天深夜都在做一件事,对比mtcp,Linux内核协议栈的收包处理和TCP新建连接的性能,同时还了解了一下腾讯的F-Stack。这里指明,我的mtcp使用的是netmap作为底层支撑,而不是DPDK,主观地讲,我不喜欢DPDK。

测试过程中,我确认了Linux内核协议栈的scalable问题并且确认了用户态协议栈是如何解决这个问题的。然而这并没有让我得出用户态协议栈就一定比内核态协议栈好这么一个明确的结论。具体怎么讲呢?先来看一张图,这张图大致描述了我的测试结论:
这里写图片描述

可以看出,Linux内核协议栈存在严重的scalable问题(可伸缩性),虽然我们看到用户态协议栈性能也并非完美地随着CPU核数的增加而线性扩展,但已经好太多了。看到这个结论,我们不禁要问,Why?两个问题:

  • 为什么内核协议栈PPS曲线呈现严重上凸?
  • 为什么内核协议栈的CPS(TCP每秒新建连接数)随着CPU核数的增加几乎没有什么变化?

第一个问题好回答,就像《人月神话》里说的一样,任何事情都不能完美线性扩展,因为沟通需要成本。好吧,当我巧妙绕开第一个问题后,我不得不深度解析第二个问题。

我们知道,Linux内核协议栈会将所有的Listener socket和已经建立连接的establish socket分别链接到两个全局的hash表中,这意味着每一个CPU核都有可能操作这两张hash表,作为抢占式SMP内核,Linux处理TCP新建连接时加锁是必须的。具体参见:
Linux socket hash查找的持续优化历程:https://blog.csdn.net/dog250/article/details/80490859
好在如今的新内核的锁粒度已经细化到了hash slot,这大大提升了性能,然而面对hash到同一个slot的TCP syn请求来讲,还是歇菜!【特别严重的是,如果用户态服务器仅仅侦听一个Nginx 80端口,那么这个机制就相当于一个全局的内核大锁!对于Listener的slot锁,那是为多个Listener而优化的(最多INET_LHTABLE_SIZE个bucket,即32个),对于仅有一个Listener的新建连接而言,不会起到任何作用。但是这个只是在频繁启停服务+reuseport的时候才会发生,无关我们描述的场景

对于TCP新建连接测试,很显然要频繁操作那张establish hash表,握手完成后加锁插入hash表,连接销毁时加锁从hash表删除!

问题已经描述清楚了,要揭示答案了。

对于TCP CPS测试而言,会有频繁的连接创建和链接销毁的过程在执行,映射到代码,那就是inet_hashinet_unhash两个函数会频繁执行,我们看一下unhash:

void inet_unhash(struct sock *sk){    struct ine

你可能感兴趣的:(TCP/IP协议栈到底是内核态的好还是用户态的好)