cpu 上下文

This commit is contained in:
nick 2019-07-07 17:12:41 +08:00
parent 630f201217
commit dddb7e2330

View File

@ -2,7 +2,7 @@
Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行。当然,这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们造成多任务同时运行的错觉。而在每个任务运行前CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好 CPU 寄存器和程序计数器(Program CounterPC)
CPU 寄存器,是 CPU 内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何 任务前,必须的依赖环境,因此也被叫做 CPU 上下文。
CPU 寄存器,是 CPU 内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU 上下文。
CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
@ -23,17 +23,36 @@ Linux 按照特权等级,把进程的运行空间分为内核空间和用户
进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。
从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。
从用户态到内核态的转变,需要通过 __系统调用__ 来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。
系统调用的过程也会发生 CPU 上下文的切换
CPU 寄存器里原来用户态的指令位置需要先保存起来。接着为了执行内核态代码CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。
而系统调用结束后CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以, __一次系统调用的过程其实是发生了两次 CPU 上下文切换__。
而系统调用结束后CPU 寄存器需要 __恢复__原来保存的用户态,然后再切换到用户空间,继续运行进程。所以, __一次系统调用的过程其实是发生了两次 CPU 上下文切换__。
需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也
不会切换进程。这跟我们通常所说的进程上下文切换是不一样的: __进程上下文切换是指从一个进程切换到另一个进程运行。而系统调用过程中一直是同一个进程在运行__ 。所以__系统调用过程通常称为特权模式切换而不是上下文切换__。但实际上系统调用过程中CPU 的上下文切换还是无法避免的。
#### 进程在什么时候才会被调度到 CPU 上运行
最容易想到的一个时机,就是进程执行完终止了,它之前使用的 CPU 会释放出来,这个时候再从就绪队列里,拿一个新的进程过来运行。其实还有很多其他场景,也会触发进程调度
其一为了保证所有进程可以得到公平调度CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。
其二,进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时
候进程也会被挂起,并由系统调度其他进程运行。
其三,当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
其四,当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂
起,由高优先级进程来运行。
最后一个发生硬件中断时CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。
了解这几个场景是非常有必要的,因为一旦出现上下文切换的性能问题,它们就是幕后凶
手。
#### 线程上下文切换
__线程与进程最大的区别在于线程是调度的基本单位而进程则是资源拥有的基本单位__。说白了所谓内核中的任务调度实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。
@ -55,5 +74,60 @@ __线程与进程最大的区别在于线程是调度的基本单位而进
跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户 态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。
## 怎么查看系统的上下文切换情况
vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分 析 CPU 上下文切换和中断的次数。
下面就是一个 vmstat 的使用示例:
```
vmstat 5 #每隔 5 秒输出 1 组数据
```
```
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 1293264 180568 2046324 0 0 0 5 2 8 1 0 99 0 0
```
结果含义说明
- cs(context switch)是每秒上下文切换的次数。
- in(interrupt)则是每秒中断的次数。
- r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
- b(Blocked)则是处于不可中断睡眠状态的进程数。
可以看到,这个例子中的上下文切换次数 cs 是 8 次,而系统中断次数 in 则是 2 次,而 就绪队列长度 r 和不可中断状态进程数 b 都是 0。
vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,就需要使用我们前面提到过的 pidstat 了。给它加上 -w 选项,你就可以查看每个进程上下文切换的情况了。
示例:
```
pidstat -w 5 # 每隔 5 秒输出 1 组数据
```
```
Linux 4.4.0-142-generic (10-53-166-171) 07/07/2019 _x86_64_ (2 CPU)
04:05:53 PM UID PID cswch/s nvcswch/s Command
04:05:58 PM 0 1 1.80 0.00 systemd
04:05:58 PM 0 3 6.39 0.00 ksoftirqd/0
04:05:58 PM 0 7 33.33 0.00 rcu_sched
04:05:58 PM 0 9 0.40 0.00 migration/0
04:05:58 PM 0 10 0.20 0.00 watchdog/0
```
这个结果中有两列内容是我们的重点关注对象。一个是 cswch ,表示每秒自愿上下文切换 (voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。
这两个概念一定要牢牢记住,因为它们意味着不同的性能问题:
__自愿上下文切换__是指进程无法获取所需资源导致的上下文切换。比如说 I/O、内存等系统资源不足时就会发生自愿上下文切换。
__非自愿上下文切换__则是指进程由于时间片已到等原因被系统强制调度进而发生的上下文切换。比如说大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。