伤透脑筋的CPU 上下文切换

目录

 

前言

上下文切换

1.什么是上下文

2. CPU上下文切换

进程上下文切换

线程上下文切换

1.同进程的线程上下文切换

2.不同进程的线程上下文切换

中断上下文切换


前言

很多像Kafka这种在磁盘上存取文件的系统来说,底层都喜欢用个0拷贝,概念都懂,其实就是避免了用户态和内核态的上下文切换。并不是真正意义上的0拷贝。上一篇写了平均负载,有个疑问,为什么出于准备运行和等待I/O的进程会增大系统负载呢?他们都还没用到CPU吧?其实负载之所以高,就是因为上下文切换导致的。

上下文切换

上面图中的kernel context 和 User context就是所谓的上下文了。

1.什么是上下文

对于上下文这个东西一直很困惑,它到底是个什么,我们经常遇到这个概念不是吗?尤其在Spring框架里,我们又叫容器又叫Bean的上下文。顾名思义吧,一品文章,你只取中间一个段落去看的话,肯定看不懂这篇文章在讲什么,你只有把这段的“上文”和“下文”都补齐了,勾画出一个完整的予以空间,完整的背景,你猜能读懂这一段的含义。也就是说如果没有“上下文”单独把这一段拎出来看是没有意义的,你不知道拿它做什么。也就是说上下文其实就是一个环境。

众所周知,一个系统是可以允许数量远远高于CPU数量的进程同时运行的,当然这个“同时运行”得打双引号。因为事实上并不是同时运行,一个进程运行一个CPU时间片后就会让出时间片,让另一个进程继续运行。我们感知上的同事运行,仅仅只不过是因为CPU时间片小的让我们无法察觉罢了。就像使用交流电的电脑屏幕灯泡其实都是以一定的频率闪烁的,只不过这个频率人类无法用眼睛感知到罢了(题外话)

既然有进程让出时间片,那它下次继续运行的时候CPU和程序怎么知道从哪个地方开始呢?当然是提前要将程序运行的位置存放CPU寄存器和程序计数器,CPU执行命令就指望他们了,CPU寄存器,顾名思义是一块存取速度很快的存储单元,用来存放CPU指令存放地址。程序计数器,其实就是存放程序运行的行号。这两个单元共同构成了CPU的上下文是CPU执行命令强依赖的环境。

2. CPU上下文切换

理解了CPU上下文没那么CPU上下文切换也就不难理解了。

就是进程A让出时间片的时候,要将计算结果和指令地址写入CPU寄存器,将程序运行位置参数写入程序计数器里面。再把新线程的这些信息从CPU寄存器和程序计数器中加载出来。而CPU片段切换是由操作系统任务调度来完成的。根据场景的不同这里的任务可以分为以下三种:

进程上下文切换

Linux根据CPU特权等价将进程运行的空间分为:用户空间和讷河空间

如图所示,内核空间和用户空间分别对应图中的Ring0和Ring3

  • 内核空间:享有最高CPU权限,可以直接访问任何数据
  • 用户空间:享有部分受限资源的访问权限,不能直接读取硬件设备上的数据。如需访问,要通过操作系统任务,深入内核空间去访问。

 也就是说进程既可以在用户空间运行也可以在内核空间运行。在用户空间运行叫做用户态,在内核空间运行叫做内核态。从用户态到内核态需要操作系统调度任务来完成。比如,要读一个文件操作系统就需要调用open()打开文件,调用read()读取文件到readbuffer再调用writ()写入标准输出最后调用close()关闭文件。

用户态和内核态上下文切换:

  1. CPU寄存器中用户态的指令地址要先保存起来
  2. CPU寄存器中读取内核态中运行程序的指令地址

恢复到用户态上下文切换:

  1. CPU寄存器中内核态的指令地址要先保存起来
  2. CPU寄存器中读取用户态中运行程序的指令地址

也就是说系统调用发生了两次上下文切换。但是这并不是进程上线文切换,因为是同一线程的上下文切换而不是一个进程,切换到另一进程。事实上这是特权模式切换,但不可避免的有上下文切换的存在。那么进程上线文切换又是什么样的呢?

进程之间的切换是内核来管理的,因此线程的切换是发生在内核态的。通过JVM还知道进程的切换CPU寄存器不仅要保存虚拟内存,栈的信息还需要保存内核态还需要保存内核空间的信息(堆,栈)。而且,加载了下一进程后还需要刷新虚拟堆栈信息。

 上下文切换并不是免费的,每次上下文切换都需要花费几微秒的时间,几十纳秒,几微妙也许不多,但如果像Kafka这种高吞吐量的系统呢?极少成多。这是我们接受不了的。

关于零拷贝技术我们知道,Linux是通过TLB(Translation Lookaside Buffer)来管理虚拟内存和真实内存之间的映射关系的。但是虚拟内存改变是必要的刷新TLB,内存访问就会因此变慢。内存读取便面不光是影响当前CPU的性能还影响共享内存的CPU的性能。总之进程上下文切换的场景有以下几种:

  1. 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。
  2. 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
  3. 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
  4. 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。最后一个,发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。了解这几个场景是非常有必要的,因为一旦出现上下文切换的性能问题,它们就是幕后凶手。

总之上下文切换就不是什么好事,在研发过程中尽量要避免上下文切换的事情发生。保证性能!

线程上下文切换

进程上下文切换,我们说是操作系统的的任务调度,发生在内核空间。事实上,这个任务是线程,而不是进程。因为进程是资源拥有的基本单位,而线程才是任务调度的基本单位

进程是线程的集合,那么线程的上下文切换就可以分为两种情况:

1.同进程的线程上下文切换

由于是同一进程,共享的想虚拟堆内存就不用做修改,保持原样。只切换线程私有的像虚拟栈,CPU寄存器不共享的数据。

2.不同进程的线程上下文切换

由于不同进程,那就是进程切换。跟之前说的进程上下文切换一样。

中断上下文切换

为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。

特点就是跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。另外,跟进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的 CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。

上下文切换查看


# 每隔5秒输出1组数据
$ vmstat 5
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 7005360  91564 818900    0    0     0     0   25   33  0  0 100  0  0
  • cs(context switch)是每秒上下文切换的次数。
  • in(interrupt)则是每秒中断的次数。
  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
  • b(Blocked)则是处于不可中断睡眠状态的进程数。

每个进程的上下文切换情况

用前面提到过的pidstat加 -w


# 每隔5秒输出1组数据
$ pidstat -w 5
Linux 4.15.0 (ubuntu)  09/23/18  _x86_64_  (2 CPU)

08:18:26      UID       PID   cswch/s nvcswch/s  Command
08:18:31        0         1      0.20      0.00  systemd
08:18:31        0         8      5.40      0.00  rcu_sched
...
  • cswch (voluntary context switches):表示每秒自愿上下文切换的次数 
  • nvcswch(non voluntary context switches):表示每秒非自愿上下文切换的次数。
  • 自愿上下文切换:程序无法获取资源导致的上下文切换,比如I/O,比如内存资源不够时就会发生上下文切换。
  • 非自愿上下文切换:进程让出CPU时间段发生的上下文切换

上下文切换频率是多少次才算正常

运行 vmstat,观察其中参数:


# 间隔1秒后输出1组数据
$ vmstat 1 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 6984064  92668 830896    0    0     2    19   19   35  1  0 99  0  0

 


# 每隔1秒输出1组数据(需要Ctrl+C才结束)
$ 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
 6  0      0 6487428 118240 1292772    0    0     0     0 9019 1398830 16 84  0  0  0
 8  0      0 6487428 118240 1292772    0    0     0     0 10191 1392312 16 84  0  0  0
  • cs列:参数上下文切换次数从之前的 35 骤然上升到了 139 万
  • r 列:就绪队列的长度已经到了 8,远远超过了系统 CPU 的个数 2,所以肯定会有大量的 CPU 竞争。
  • us(user)和 sy(system)列:这两列的 CPU 使用率加起来上升到了 100%,其中系统 CPU 使用率,也就是 sy 列高达 84%,说明 CPU 主要是被内核占用了。
  • in 列:中断次数也上升到了 1 万左右,说明中断处理也是个潜在的问题。

分析结论:系统的就绪队列过长,也就是正在运行和等待 CPU 的进程数过多,导致了大量的上下文切换,而上下文切换又导致了系统 CPU 的占用率升高


# 每隔1秒输出1组数据(需要 Ctrl+C 才结束)
# -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标
$ pidstat -w -u 1
08:06:33      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
08:06:34        0     10488   30.00  100.00    0.00    0.00  100.00     0  sysbench
08:06:34        0     26326    0.00    1.00    0.00    0.00    1.00     0  kworker/u4:2

08:06:33      UID       PID   cswch/s nvcswch/s  Command
08:06:34        0         8     11.00      0.00  rcu_sched
08:06:34        0        16      1.00      0.00  ksoftirqd/1
08:06:34        0       471      1.00      0.00  hv_balloon
08:06:34        0      1230      1.00      0.00  iscsid
08:06:34        0      4089      1.00      0.00  kworker/1:5
08:06:34        0      4333      1.00      0.00  kworker/0:3
08:06:34        0     10499      1.00    224.00  pidstat
08:06:34        0     26326    236.00      0.00  kworker/u4:2
08:06:34     1000     26784    223.00      0.00  sshd



# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
08:14:05      UID      TGID       TID   cswch/s nvcswch/s  Command
...
08:14:05        0     10551         -      6.00      0.00  sysbench
08:14:05        0         -     10551      6.00      0.00  |__sysbench
08:14:05        0         -     10552  18911.00 103740.00  |__sysbench
08:14:05        0         -     10553  18915.00 100955.00  |__sysbench
08:14:05        0         -     10554  18827.00 103954.00  |__sysbench
...

 碰到上下文切换次数过多的问题时,我们可以借助 vmstat 、 pidstat 和 /proc/interrupts 等工具,来辅助排查性能问题的根源

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值