图1.jpg

在进程A切换到进程B的过程中,先保存A进程的高下文,以便于等A规复运行的时候,能够知道A进程的下一条指令是啥。
然后将要运行的B进程的高下文规复到寄存器中。
这个过程被称为高下文切换。
高下文切换开销在进程不多、切换不频繁的运用处景下问题不大。
但是现在Linux操作系统被用到了高并发的网络程序后端做事器。
在单机支持成千上万个用户要求的时候,这个开销就得拿出来说道说道了。
由于用户进程在要求Redis、Mysql数据等网络IO壅塞掉的时候,或者在进程韶光片到了,都会引发高下文切换。

图2.png

linux系统php消耗cpu过程/线程高低文切换会用失落你若干CPU JavaScript

一个大略的进程高下文切换开销测尝尝验

废话不多说,我们先用个实验测试一下,到底一次高下文切换须要多长的CPU韶光!
实验方法是创建两个进程并在它们之间传送一个令牌。
个中一个进程在读取令牌时就会引起壅塞。
另一个进程发送令牌后等待其返回时也处于壅塞状态。
如此来回传送一定的次数,然后统计他们的均匀单次切换韶光开销。

详细的实验代码拜会test04

# gcc main.c -o main# ./main./mainBefore Context Switch Time1565352257 s, 774767 usAfter Context SWitch Time1565352257 s, 842852 us

每次实行的韶光会有差异,多次运行后均匀每次高下文切换耗时3.5us旁边。
当然了这个数字因机器而异,而且建议在实机上测试。

前面我们测试系统调用的时候,最低值是200ns。
可见,高下文切换开销要比系统调用的开销要大。
系统调用只是在进程内将用户态切换到内核态,然后再切回来,而高下文切换可是直接从进程A切换到了进程B。
显然这个高下文切换须要完成的事情量更大。

进程高下文切换开销都有哪些

那么高下文切换的时候,CPU的开销都详细有哪些呢?开销分成两种,一种是直接开销、一种是间接开销。

直接开销便是在切换时,cpu必须做的事情,包括:

1、切换页表全局目录2、切换内核态堆栈3、切换硬件高下文(进程规复前,必须装入寄存器的数据统称为硬件高下文)ip(instruction pointer):指向当前实行指令的下一条指令bp(base pointer): 用于存放实行中的函数对应的栈帧的栈底地址sp(stack poinger): 用于存放实行中的函数对应的栈帧的栈顶地址cr3:页目录基址寄存器,保存页目录表的物理地址......4、刷新TLB5、系统调度器的代码实行

间接开销紧张指的是虽然切换到一个新进程后,由于各种缓存并不热,速率运行会慢一些。
如果进程始终都在一个CPU上调度还好一些,如果跨CPU的话,之前热起来的TLB、L1、L2、L3由于运行的进程已经变了,以是以局部性事理cache起来的代码、数据也都没有用了,导致新进程穿透到内存的IO会变多。
实在我们上面的实验并没有很好地丈量到这种情形,以是实际的高下文切换开销可能比3.5us要大。

想理解更详细操作过程的同学请参考《深入理解Linux内核》中的第三章和第九章。

一个更为专业的测试工具-lmbench

lmbench用于评价系统综合性能的多平台开源benchmark,能够测试包括文档读写、内存操作、进程创建销毁开销、网络等性能。
利用方法大略,但便是跑有点慢,感兴趣的同学可以自己试一试。

这个工具的上风是是进行了多组实验,每组2个进程、8个、16个。
每个进程利用的数据大小也在变,充分仿照cache miss造成的影响。
我用他测了一下结果如下:

-------------------------------------------------------------------------Host OS 2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64K ctxsw ctxsw ctxsw ctxsw ctxsw ctxsw ctxsw --------- ------------- ------ ------ ------ ------ ------ ------- ------- bjzw_46_7 Linux 2.6.32- 2.7800 2.7800 2.7000 4.3800 4.0400 4.75000 5.48000

lmbench显示的进程高下文切换耗时从2.7us到5.48之间。

线程高下文切换耗时

前面我们测试了进程高下文切换的开销,我们再连续在Linux测试一下线程。
看看究竟比进程能不能快一些,快的话能快多少。

在Linux下实在本并没有线程,只是为了迎合开拓者口味,搞了个轻量级进程出来就叫做了线程。
轻量级进程和进程一样,都有自己独立的task_struct进程描述符,也都有自己独立的pid。
从操作系统视角看,调度上和进程没有什么差异,都是在等待行列步队的双向链表里选择一个task_struct切到运行态而已。
只不过轻量级进程和普通进程的差异是可以共享同一内存地址空间、代码段、全局变量、同一打开文件凑集而已。

同一进程下的线程之所有getpid()看到的pid是一样的,实在task_struct里还有一个tgid字段。
对付多线程程序来说,getpid()系统调用获取的实际上是这个tgid,因此从属同一进程的多线程看起来PID相同。

我们用一个实验来测试一下test06。
其事理和进程测试差不多,创建了20个线程,在线程之间通过管道来通报旗子暗记。
接到旗子暗记就唤醒,然后再通报旗子暗记给下一个线程,自己就寝。
这个实验里单独考虑了给管道通报旗子暗记的额外开销,并在第一步就统计了出来。

# gcc -lpthread main.c -o main0.508250 4.363495

每次实验结果会有一些差异,上面的结果是取了多次的结果之后然后均匀的,大约每次线程切换开销大约是3.8us旁边。
从高下文切换的耗时上来看,Linux线程(轻量级进程)实在和进程差别不太大。

Linux干系命令

既然我们知道了高下文切换比较的花费CPU韶光,那么我们通过什么工具可以查看一下Linux里究竟在发生多少切换呢?如果高下文切换已经影响到了系统整体性能,我们有没有办法把有问题的进程揪出来,并把它优化掉呢?

# 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 2 0 0 595504 5724 190884 0 0 295 297 0 0 14 6 75 0 4 5 0 0 593016 5732 193288 0 0 0 92 19889 29104 20 6 67 0 7 3 0 0 591292 5732 195476 0 0 0 0 20151 28487 20 6 66 0 8 4 0 0 589296 5732 196800 0 0 116 384 19326 27693 20 7 67 0 7 4 0 0 586956 5740 199496 0 0 216 24 18321 24018 22 8 62 0 8

或者是

# sar -w 1 proc/s Total number of tasks created per second. cswch/s Total number of context switches per second. 11:19:20 AM proc/s cswch/s 11:19:21 AM 110.28 23468.22 11:19:22 AM 128.85 33910.58 11:19:23 AM 47.52 40733.66 11:19:24 AM 35.85 30972.64 11:19:25 AM 47.62 24951.43 11:19:26 AM 47.52 42950.50 ......

上图的环境是一台生产环境机器,配置是8核8G的KVM虚机,环境是在nginx+fpm的,fpm数量为1000,均匀每秒处理的用户接口要求大约100旁边。
个中cs列表示的便是在1s内系统发生的高下文切换次数,大约1s切换次数都达到4W次了。
粗略估算一下,每核大约每秒须要切换5K次,则1s内须要花将近20ms在高下文切换上。
要知道这是虚机,本身在虚拟化上还会有一些额外开销,而且还要真正花费CPU在用户接口逻辑处理、系统调用内核逻辑处理、以及网络连接的处理以及软中断,以是20ms的开销实际上不低了。

那么进一步,我们看下到底是哪些进程导致了频繁的高下文切换?

# pidstat -w 1 11:07:56 AM PID cswch/s nvcswch/s Command11:07:56 AM 32316 4.00 0.00 php-fpm 11:07:56 AM 32508 160.00 34.00 php-fpm 11:07:56 AM 32726 131.00 8.00 php-fpm ......

由于fpm是同步壅塞的模式,每当要求Redis、Memcache、Mysql的时候就会壅塞导致cswch/s志愿高下文切换,而只有韶光片到了之后才会触发nvcswch/s非志愿切换。
可见fpm进程大部分的切换都是志愿的、非志愿的比较少。

如果想查看详细某个进程的高下文切换总情形,可以在/proc接口下直接看,不过这个是总值。

grep ctxt /proc/32583/status voluntary_ctxt_switches: 573066 nonvoluntary_ctxt_switches: 89260

本节结论

高下文切换详细做哪些事情我们没有必要记,只须要记住一个结论既可,测得作者开拓机高下文切换的开销大约是2.7-5.48us旁边,你自己的机器可以用我供应的代码或工具进行一番测试。

lmbench相对更准确一些,由于考虑了切换后Cache miss导致的额外开销。

个人"大众年夜众号“开拓内功修炼”,打通理论与实践的任督二脉。

参考文献

进程高下文切换,残酷的性能杀手测试高下文切换开销进程高下文切换导致Load过高CPU高下文切换的次数和韶光Linux操作系统测试工具lmbench官方文档lmbench安装与利用