进程是操作系统的伟大发明之一,对应用程序屏蔽了CPU调度、内存管理等硬件细节,而抽象出一个进程的概念,让应用程序专心于实现自己的业务逻辑既可,而且在有限的CPU上可以“同时”进行许多个任务 。但是它为用户带来方便的同时,也引入了一些额外的开销 。如下图,在进程运行中间的时间里,虽然CPU也在忙于干活,但是却没有完成任何的用户工作,这就是进程机制带来的额外开销 。

文章插图
图1.jpg
在进程A切换到进程B的过程中,先保存A进程的上下文,以便于等A恢复运行的时候,能够知道A进程的下一条指令是啥 。然后将要运行的B进程的上下文恢复到寄存器中 。这个过程被称为上下文切换 。上下文切换开销在进程不多、切换不频繁的应用场景下问题不大 。但是现在linux操作系统被用到了高并发的网络程序后端服务器 。在单机支持成千上万个用户请求的时候,这个开销就得拿出来说道说道了 。因为用户进程在请求redis、MySQL数据等网络IO阻塞掉的时候,或者在进程时间片到了,都会引发上下文切换 。

文章插图
图2.png
一个简单的进程上下文切换开销测试实验
废话不多说,我们先用个实验测试一下,到底一次上下文切换需要多长的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、刷新TLB
- 5、系统调度器的代码执行
想了解更详细操作过程的同学请参考《深入理解Linux内核》中的第三章和第九章 。
一个更为专业的测试工具-lmbench
lmbench用于评价系统综合性能的多平台开源benchmark,能够测试包括文档读写、内存操作、进程创建销毁开销、网络等性能 。使用方法简单,但就是跑有点慢,感兴趣的同学可以自己试一试 。
这个工具的优势是是进行了多组实验,每组2个进程、8个、16个 。每个进程使用的数据大小也在变,充分模拟cache miss造成的影响 。我用他测了一下结果如下:
-------------------------------------------------------------------------Host OS 2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64Kctxsw 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之间 。
推荐阅读
- python 进程、线程、协程对比
- dwm.exe进程是什么?怎么关闭?
- 线程,进程,协程, 并发,并行,同步,异步概念解析
- python多线程爬取youtube视频,外面的世界很精彩
- 一个进程开启多少线程最好
- top命令详解:CPU,内存,进程信息统计
- Linux杀不死的进程之CPU使用率700%
- Linux多进程和多线程的一次gdb调试实例
- 关于并发框架 Java原生线程池原理及Guava与之的补充
- Tomcat使用线程池配置高并发连接
