美文网首页
CPU上下文切换

CPU上下文切换

作者: kar_joe | 来源:发表于2020-01-28 21:30 被阅读0次

    栈空间

    1. 用户态函数栈
      主要用于用户态的函数调用


      image.png
    2. 内核栈
      Linux 给每个 task 都分配了内核栈,主要用于内核态的函数调用
      除了内核栈,还有内核寄存器,pt_regs主要应用于态转换内核态时,保护应用态上下文的(保存寄存器)。


      image.png
    3. 总结


      image.png

    上下文切换含义

    什么是上下文

    在每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好 CPU 寄存器和程序计数器(Program Counter,PC)。
    CPU 寄存器,是 CPU 内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU 上下文。
    下文是指某一时间点CPU寄存器(CPU register)和程序计数器(PC)的内容, 广义上还包括内存中进程的虚拟地址映射信息.

    什么是上下文切换

    CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
    而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

    上下文切换场景

    1. 系统调用
      Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中, CPU 特权等级的 Ring 0 和 Ring 3。
    • 内核空间(Ring 0)具有最高权限,可以直接访问所有资源;
    • 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。


      image.png

      换个角度看,也就是说,进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。从用户态到内核态的转变,需要通过系统调用来完成。内核空间态资源包括内核的堆栈、寄存器等;用户空间态资源包括虚拟内存、栈、变量、正文、数据等。


      image.png
      CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。
      而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。
      image.png
    1. 进程切换
      进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。因此,进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。即除了cpu上下文切换,还有进程空间虚拟内存切换。
      如下图所示,保存上下文和恢复上下文的过程并不是“免费”的,需要内核在 CPU 上运行才能完成。


      image.png

      每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。
      另外,Linux 通过 TLB(Translation Lookaside Buffer)来管理虚拟内存到物理内存的映射关系。当虚拟内存更新后,TLB 也需要刷新,内存的访问也会随之变慢。特别是在多处理器系统上,缓存是被多个处理器共享的,刷新缓存不仅会影响当前处理器的进程,还会影响共享缓存的其他处理器的进程。

    2. 同进程不同线程间切换
      在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

    3. 中断上下文切换
      为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
      跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
      对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。

    4. 总结
      正常情况下,损耗对比关系如下:
      进程上下文切换>同进程线程上下文切换>中断上下文切换>内核模式切换>协程上下文切换>用户态函数调用上下文切换

    上下文切换定量查看

    1. 系统层面
      vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 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)则是处于不可中断睡眠状态的进程数。
      上下文切换过多,体现在CPU使用率上是sys%升高。
    1. 进程线程层面
      vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,就需要使用我们前面提到过的 pidstat 了。给它加上 -w 选项,你就可以查看每个进程上下文切换的情况了。加上-t选项,可以查看线程上下文切换情况。
    # 每隔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)的次数,自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。

    • 另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。

    示例

    构造多线程应用并启动

    1. 查看系统cpu使用情况及上下文切换情况
    # 每隔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
    
    1. 查看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. 查看线程上下文切换状况
    # 每隔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
    ...
    
    1. 查看中断状况
    # -d 参数表示高亮显示变化的区域
    $ watch -d cat /proc/interrupts
               CPU0       CPU1
    ...
    RES:    2450431    5279697   Rescheduling interrupts
    ...
    

    变化速度最快的是重调度中断(RES),这个中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同 CPU 的机制,通常也被称为处理器间中断(Inter-Processor Interrupts,IPI)。所以,这里的中断升高还是因为过多任务的调度问题。

    1. 总结
      每秒上下文切换多少次才算正常呢?这个数值其实取决于系统本身的 CPU 性能。如果系统的上下文切换次数比较稳定,那么从数百到一万以内,都应该算是正常的。但当上下文切换次数超过一万次,或者切换次数出现数量级的增长时,就很可能已经出现了性能问题。
      这时,还需要根据上下文切换的类型,再做具体分析。比方说:
    • 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
    • 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;
    • 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。

    相关文章

      网友评论

          本文标题:CPU上下文切换

          本文链接:https://www.haomeiwen.com/subject/icsmthtx.html