前面的几篇文章里讨论过了进程上下文切换和系统调用对系统性能的影响,我们今天再来看另外一个CPU吃货,那就是软中断。
你在用vmstat或者其他一些工具查看系统CPU消耗的时候,发现有两列是单独列出来的,分别是是hi和si。他们分别是硬中断和软中断。既然vmstat把中断的开销单独列出来了,就说明一个问题,中断吃起CPU来那也是丝毫不含糊。
我们没必要啃明白软中断的所有原理,但从一名追求性能的开发者的角度来看,我们有必要了解以下问题:
- 一次软中断的开销到底多大?
- 你的服务器上被软中断吃掉了多少CPU时间?
如果你和我一样好奇上面的问题的答案,那请跟我来!
软中断的诞生
CPU正常情况下都是专心处理用户的进程的,当外部的硬件或软件有消息想要通知CPU,就会通过中断请求(interrupt request,IRQ)的方式来进行。比如当你的鼠标有了点击产生,再比如磁盘设备完成了数据的读取的时候,都会通过中断通知CPU工作已完成。
但是当中断机制应用到网络IO的时候,就产生了一点点问题。网络包收到后的处理工作,不像鼠标、键盘、磁盘IO读取完成那样简单,而是要进行大量的内核协议栈的处理,最终才能放到进程的接收缓存区中。假如只用一种中断(硬终端)的方式来处理网络IO,由于硬中断的优先级又比较高,这样CPU就会忙于处理大量的网络IO而不能及时响应键盘鼠标等事情,导致操作系统实时性变差,你会感觉机器以卡一卡的。
所以现代的Linux又发明了软件中断,配合硬中断来处理网络IO。 硬中断你可以理解只是个收包的,把包收取回来放到“家里”就完事,很快就能完成,这样不耽误CPU响应其它外部高优先级的中断。而软中断优先级较低,负责将包进行各种处理,完成从驱动层、到网络协议栈,最终把处理出来的数据放到socker的接收buffer中。
软中断消耗的CPU周期相对比硬中断要多不少,所以我们本文来重点关注软中断的开销。
软中断开销估算
前面大致介绍了软中断的来龙去脉,好了直接进入本文的主题上,软中断开销到底多大。好了,请跟我来一起计算:
1) 查看软中断总耗时
首先用top命令可以看出每个核上软中断的开销占比,是在si列
top
top - 19:51:24 up 78 days, 7:53, 2 users, load average: 1.30, 1.35, 1.35
Tasks: 923 total, 2 running, 921 sleeping, 0 stopped, 0 zombie
Cpu(s): 7.1%us, 1.4%sy, 0.0%ni, 90.1%id, 0.1%wa, 0.2%hi, 1.2%si, 0.0%st
Mem: 65872372k total, 64711668k used, 1160704k free, 339384k buffers
Swap: 0k total, 0k used, 0k free, 55542632k cached
如上图所示,CPU大约花费了1.2%的时钟周期在软中断上,也就是说每个核要花费12ms。
2)查看软中断次数
再用vmstat命令可以看到软中断的次数
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1231716 339244 55474204 0 0 6 496 0 0 7 3 90 0 0
2 0 0 1231352 339244 55474204 0 0 0 128 57402 24593 5 2 92 0 0
2 0 0 1230988 339244 55474528 0 0 0 140 55267 24213 5 2 93 0 0
2 0 0 1230988 339244 55474528 0 0 0 332 56328 23672 5 2 93 0 0
每秒大约有56000次左右的软中断(该机器上是web服务,网络IO密集型的机器,其它中断可以忽略不计)。
3)计算每次软中断的耗时
该机器是16核的物理实机,故可以得出每个软中断需要的CPU时间是=12ms/(56000/16)次=3.428us
从实验数据来看,一次软中断CPU开销大约3.4us左右
软中断的上下文切换
前文我们计算出了一个相对比较精确的开销时间。这个时间里其实包含两部分,一是上下文切换开销,二是软中断内核执行开销。 其中上下文切换和系统调用、进程上下文切换有很多相似的地方。让我们将他们进行一个简单的对比:
1.和系统调用开销对比
《深入理解Linux内核-第五章》开头的一句话,很形象地把中断和系统调用两个不相关的概念联系了起来,巧妙地找到了这二者之间的相似处。“你可以把内核看做是不断对请求进行响应的服务器,这些请求可能来自在CPU上执行的进程,也可能来自发出中断的外部设备。老板的请求相当于中断,而顾客的请求相当于用户态进程发出的系统调用”。
软中断和系统调用一样,都是CPU停止掉当前用户态上下文,保存工作现场,然后陷入到内核态继续工作。二者的唯一区别是系统调用是切换到同进程的内核态上下文,而软中断是则是切换到了另外一个内核进程ksoftirqd上。
而事实上,早期的系统调用也还真的是通过汇编指令int(中断)来实现的,当用户态进程发出int $0x80指令时,CPU切换到内核态并开始执行system_call函数。后来大家觉得系统调用实在是太慢了,因为int指令要执行一致性和安全性检查。后来内核又该用了Intel提供的“快速系统调用”的sysenter指令,才算是和中断脱离了一点点干系。而软中断必须得进行这些检查,所以从这点上来看,中断的开销应该是比系统调用的开销要多的。
根据前文的实验结果,系统调用开销是200ns起步。没看过这个文章的同学可以关注我,然后从历史文章里找。
2.和进程上下文切换开销对比
和进程上下文切换比较起来,进程上下文切换是从用户进程A切换到了用户进程B。而软中断切换是从用户进程A切换到了内核线程ksoftirqd上。 而ksoftirqd作为一个内核控制路径,其处理程序比一个用户进程要轻量,所以上下文切换开销相对比进程切换要稍一些。大家感兴趣的,可以继续阅读《深入理解Linux内核》的-第五章。
根据前文的实验结果,进程上下文切换开销是3us-5us。没看过这个文章的同学可以关注我,然后从历史文章里找。
相关Linux命令
- top: si列展示软中断造成CPU开销
- vmstat 1:in列每秒展示软中断次数
- cat /proc/softirqs:展示所有软中断发生的总数,包括TIMER、NET_TX、NET_RX等
参考文献
- [深入理解Linux内核]
- 中断上下文切换
- CPU检测到中断信号时,怎么知道是发给哪个进程的?