0x00 前言
上一篇笔记中我讲到了,在寻找 CPU 的性能瓶颈的问题的时候,首先会查看整台机器的平均负载是否高,然后再使用 pidstat 等工具判断到底是哪种情况导致的平均负载升高,主要情况有三种:
- CPU 密集型
- IO 密集型
- 大量进程的场景
前面两种情况我们都很好理解,但是大量的进程怎么也会导致 CPU 出现瓶颈呢?这是因为我们的 CPU 核心在执行任务的时候,仍然是单任务的,只不过它执行的很快,让每个进程交替着执行,所以在人类眼中看上去像是多个线程在并行执行。然而不同的线程交替的切换着执行时需要成本的,当进程数量很多的时候,CPU Context Swith 就很频繁了,那么就会导致 CPU 出现性能瓶颈。CPU Context Swith 可以分为以下几种场景:
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
0x01 进程上下文切换
首先,Linux 按照特权等级,把进程的运行空间分为内核态和用户态,CPU 特权等级为 0-3 数字越小权限越高。
那么问题来了,由于 Linux 现在只用到了两个特权等级,分别是 Ring 0 内核态和 Ring 3 用户态,所以运行在不同的特权等级上的进程上下文切换主要有两种:
- 用户态进程之间的切换
- 用户态进程陷入到内核态,即系统调用
那用户态进程陷入内核态进程与用户态空间之间的切换有何不同呢?不同之处在于用户态在进行系统调用的时候,会发生两次 CPU 的上下文切换,分别是:
- 用户态切陷入到内核态执行
- 内核态进程执行完成后再恢复为原来的用户态的进程执行
那么说明了在系统调用的时候并不会切换原本的用户态的进程,它只是进行了系统调用(即切换到内核态)又恢复了,所以我们把系统调用成为 特权模式切换 ,用户态之间的进程成为 上下文切换。
系统调用与上下文切换有何区别?
上下文切换相比系统调用会保存进程的虚拟内存和栈,所以会消耗更多的时间
何时会进行进程上下文切换?
- CPU 时间片结束,CPU 是划分为多个时间片的给不同进程使用的
- 进程运行需要的资源不够,如等待 IO,Memory insufficient 等等
- 进程主动挂起, 如 sleep 函数
- 有优先级更高的进程执行,如硬中断
0x02 线程上下文切换
线程与进程的区别是什么?线程是调度的基本单位,进程是资源拥有的基本单位。也就是说线程是实际上干活的,也是内核实际调度的对象,但是干活你需要工具和场地,而进程就是给线程提供了虚拟内存、全局变量等资源。所以可以这么理解:
- 进程 = 所有线程 + 资源
- 如果进程只有一个线程,那么可以认为这个线程就代表整个进程
- 如果一个进程有多个线程,所有线程共享这个进程的资源, 在上下文切换的时候这些资源也不需要切换
- 线程有自己的私有数据,如 stack,Register 等,这些需要保存
那么线程的情况分为两种:
- 前后线程属于一个进程,那么资源是共享的,所以只需要切换线程的私有数据
- 如果前后线程属于不同的进程,那么切换的情况和进程是一样的
0x03 中断上下文切换
中断是外部事件对 CPU 执行过程打的打断,中断程序也是需要执行的,它会保存被打断的进程的状态,这样中断程序执行结束了后原来的进程就还能接着执行,那么自然中断也需要上下文切换,也会消耗 CPU 的资源。但是由于中断并不涉及到切换用户态进程,所以进程的资源是不需要保存和恢复的。
0x04 总结
从上个笔记中我说到了根据 CPU 升高的情况下,会有三种原因导致 CPU 使用情况升高。这里我接着针对的是第三种大量进程切换的情况,而这种情况又可以分为三种情况,分别是:
-
进程上下文切换(进程切换就是特殊情况的线程切换)
- 系统调用
- 进程上下文切换
- 线程上线文切换
- 中断上下文切换