文章目录
写在前面:
由于之前在开发分布式系统中由于云服务器性能原因,导致系统总是断连等错误。但是之前一般只是简单gdb调试一下,定位错误异常艰难,所以决定开设此专栏,系统的记录我学习Linux 性能优化的历程。
作者邮箱:2107810343@qq.com
时间:2021/04/28 21:10
实现环境:Linux
系统:ubuntu 18.04
CPU上下文切换
什么是CPU上下文
在每个任务运行前,CPU都需要之道任务从哪儿加载,又从哪儿运行,也就是说,需要系统实现帮它设置好CPU寄存器和程序计数器。它们都是CPU在运行任何任务前,必依赖的运行环境,因此也称之为CPU上下文。
- CPU寄存器:CPU内置的容量小,但速度极快的内存。
- 程序计数器:用来存储CPU正在执行的指令位置,或者即将执行的下一条指令位置。
CPU上下文切换,就是先把前一个任务的CPU上下文保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后在跳转到程序计数器所指的新位置,运行新任务。而保存的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。
而根据任务的不同,CPU上下文切换还分为进程上下文切换,线程上下文切换和中断上下文切换。这些除了执行保存寄存器和程序计数器的步骤,各自还有自己不同的事情要处理。
进程上下文切换
进程的运行空间分为用户态和内核态。其分别对应着下图CPU特权等级ring0和ring3。
- 内核态:具有最高权限,可以直接访问所有资源
- 用户态:只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入内核中,才能访问这些资源
关于ring1和ring2:
这里直接引用StackOverflow上面大佬的回复:
As a hobbyist operating system writer, I found that because paging (a major part of the modern protection model) only has a concept of privileged (ring 0,1,2) and unprivileged, the benefit to rings 1 and 2 were diminished greatly.
The intent by Intel in having rings 1 and 2 is for the OS to put device drivers at that level, so they are privileged, but somewhat separated from the rest of the kernel code.
Rings 1 and 2 are in a way, “mostly” privileged. They can access supervisor pages, but if they attempt to use a privileged instruction, they still GPF like ring 3 would. So it is not a bad place for drivers as Intel planned…
That said, they definitely do have use in some designs. In fact, not always directly by the OS. For example, VirtualBox, a Virtual Machine, puts the guest kernel code in ring 1. I am also sure some operating systems do make use of them, I just don’t think it is a popular design at the moment.
大概意思就是,对于分页管理的Linux来说,只有非特权(ring3)和特权(ring0 1 2)之分,ring1和ring2用的很少。intel将操作系统的设备驱动程序置于该级别,使其拥有大部分的特权,但不是全部。
而从用户态到内核态,需要经过系统调用来完成。一次系统调用过程会发生两次CPU上下文的切换。不过这里其实并不设计到虚拟内存等进程用户态的资源,即不会切换进程。也就是不会发生进程的切换。
进程上下文切换
进程是由内核来管理和调度的,进程的切换只发生在内核态。而基础南横的上下文不仅包括虚拟内存、栈、全局变量等用户空间的资源,还包括内核堆栈、寄存器等内核空间的状态。
因此,进程的上下文切换就比系统调用时的CPU上下文切换多了一步:保存当前进程的内核状态和CPU寄存器之前,需要先把进程的虚拟内存、堆栈保存下来;而加载下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
线程上下文切换
线程的上下文切换主要分两种情况:
- 前后两个线程属于不同进程:因为资源不共享 ,所以切换过程就跟进程上下文切换是一样的
- 前后两个线程属于同一进程:因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据
中断上下文切换
为了快速响应硬件时间,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
跟进程上下文切换不同的是,中断上下文的切换其实并不涉及进程的用户态。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括内核态中断服务程序执行所必需的状态,包括CPU寄存器、内核堆栈、硬件中断参数。对于同一个CPU来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。
查看系统上下文切换情况:vmstat和pidstat
现在我们知道了,过多的上下文切换,会把CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正的运行时间,造成系统性能大幅下降。
我们可以在Linux中用 vmstat 来查看系统上下文切换情况:
我们需要在这里重点关注一下几个字段:
- cs:每秒上下文切换的次数
- in:每秒中断的次数
- r:就绪队列的长度,也就是正在运行和等待CPU的进程数
- b:处于不可中断状态等待进程数
vmstat 只给出了系统总体的上下文切换情况,想要查看每个进程的详细详情,就需要使用 pidstat,加上 -w选项,就可以查看进程上下文的情况。
这里我们关注以下两个字段:
- cswch:每秒自愿上下文切换的次数
- nvcswch:每秒非自愿上下文切换的次数
解释:
自愿上下文切换:指进程无法获取所需资源,导致的上下文切换
非自愿上下文切换:指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换
高负载模拟与排查:sysbench和pidstat
这次使用的工具是sysbench,是一个多线程的基准测试工具,用来评估不同参数下的数据库负载情况。
先安装一下:
ubuntu@VM-0-2-ubuntu:~/ByteTalk/LogServer$ sudo apt install sysbench
然后开始进行多线程模拟:
ubuntu@VM-0-2-ubuntu:~/ByteTalk/LogServer$ sysbench --threads=10 --max-time=300 threads run
使用vmstat查看上下文切换情况:
可以看到,每秒中断数高达2w,上下文切换数更是达到了94w。
然后我们可以使用pidstat命令查看是哪个进程引起的:
ubuntu@VM-0-2-ubuntu:~/ByteTalk/UserService$ pidstat -wt 1
很明显,就是sysbench
参考文献
[1] 倪朋飞.Linux性能优化实战.极客时间