CPU性能监控:从理解CPU上下文切换开始

文摘   2024-11-12 09:02   广东  

点击上方【蓝字】关注博主

 在计算机系统中,CPU上下文切换是实现多任务处理的核心机制之一,但其频繁发生可能导致显著的性能损失。本文将深入探讨上下文切换的定义、类型及其对系统性能的影响。我们将讨论造成上下文切换频繁的各种因素,并介绍有效的监控和优化策略。通过这些措施,系统管理员可以提升CPU利用率,从而优化整体系统性能与用户体验。

01

什么是CPU上下文切换?

CPU上下文切换是操作系统中一种重要的机制,它指的是在多个进程或线程之间切换CPU控制的过程。这个过程涉及保存当前正在执行的进程或线程的状态(称为“上下文”),并加载另一个进程或线程的状态。这种切换使得操作系统能够在多个任务间高效地分配CPU资源。

所谓的上下文切换,就是把上一个任务的寄存器和计数器保存起来,然后加载新任务的寄存器和计数器,最后跳转到新任务的位置开始执行新任务。

根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景,也就是进程上下文切换、线程上下文切换以及中断上下文切换。

具体步骤包括:

  1. 保存当前上下文:将当前进程或线程的寄存器值、程序计数器、堆栈指针等信息保存到进程控制块(PCB)或线程控制块(TCB)中。

  2. 选择下一个要运行的任务:操作系统根据调度算法选择下一个运行的进程或线程。

  3. 加载新上下文:从选定的进程或线程的PCB/TCB中恢复其状态信息,如寄存器值和程序计数器等。

  4. 切换到新任务:CPU开始执行新选择的进程或线程。


哪些因素会导致频繁的上下文切换?

  1. 过多的进程或线程:系统中同时存在过多的进程或线程,会导致操作系统频繁地切换任务,以满足每个任务的执行需求。

  2. 低优先级进程的竞争:如果多个低优先级进程在争用CPU资源,操作系统为平衡负载而频繁切换也可能导致频繁的上下文切换。

  3. 短时任务:频繁出现在短时间内的快速任务,例如信号处理程序或小型计算任务,会导致上下文切换次数显著增加。

  4. I/O操作:在需要等待I/O操作(例如磁盘读写或网络请求)时,进程会被挂起,操作系统会切换到其他进程,这种情形下,如果I/O操作非常频繁,将会导致上下文切换频繁发生。

  5. 进程优先级变化:动态调整进程优先级(比如实时系统中的高优先级任务抢占低优先级任务),可能导致频繁的上下文切换。

  6. 高频率的定时器中断:操作系统用定时器中断来实现时间片轮转调度,如果中断频率过高,会增加上下文切换的频率。

  7. 资源锁竞争:多线程环境中,如果多个线程在争抢共享资源,可能导致线程不断被挂起和恢复,增加上下文切换。

上下文切换是实现多任务处理的关键,但频繁的上下文切换可能导致性能下降,因为它涉及一定的开销,包括保存和加载状态信息的时间。因此,操作系统通常会优化调度策略,以减少不必要的上下文切换。

02

有哪些上下文切换?

2.1、系统调用上下文切换

Linux 进程既可以在用户空间运行,又可以在内核空间中运行。
当它在用户空间运行时,被称为进程的用户态;当它进入进入内核空间的时候,被称为进程的内核态
从用户态到内核态的转变过程,需要通过系统调用来完成
CPU 寄存器里原来的指令位置是在用户态。但是为了执行内核态代码,需要先把用户态的位置保存起来,然后寄存器更新为内核态指令的新位置。最后跳转到内核态运行内核任务。

当系统调用结束后,CPU 寄存器需要恢复原来保存的用户态位置,然后再切换到用户空间,继续运行进程。一次系统调用发生了两次 CPU 上下文切换!

系统调用过程中对用户态的资源没有任何影响,也不会切换进程,所以也称为特权模式切换。

2.2、进程上下文切换

进程是由内核来管理和调度的,所以进程的切换只发生在内核态。进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。

进程的上下文切换在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后再刷新进程的虚拟内存映射关系和用户栈,刷新虚拟内存映射就涉及到 TLB 快表 (虚拟地址缓存),因此会影响内存的访问速度。

单次进程上下文切换的 CPU 时间在几十纳秒到数微秒之间。特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而影响 CPU 的实际使用率。

进程上下文切换的原因

  1. 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。(被动切换)

  2. 进程在系统资源不足,这个时候进程也会被挂起,并由系统调度其他进程运行。(主动切换)

  3. 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。

  4. 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。

  5. 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

2.3、线程上下文切换

线程与进程的区别在于:线程是调度的基本单位,而进程是资源分配基本单位。内核中的任务调度,实际调度的是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

  1. 当进程只有一个线程时,可以认为进程就等于线程。

  2. 当进程拥有多个线程时,共享虚拟内存和全局变量等资源。这些资源在上下文切换时不需要修改。

  3. 线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时需要保存。


线程的上下文切换分为两种:

  1. 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。

  2. 前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

2.4、中断上下文切换

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

跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。中断上下文只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

03

怎么查看上下文切换?

过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成了系统性能大幅下降的一个元凶。

常用的CPU性能监控工具有:perf, iostat, top, vmstat 等。

  1. perf 是Linux内核自带的性能分析工具,能够对CPU的性能进行采样,分析应用的性能瓶颈。特点:

  • 支持多种事件的监控,包括 CPU 周期、缓存命中率、分支预测等。

  • 可生成详细的性能报告,帮助开发者找到瓶颈。

  • 提供命令如 perf statperf recordperf report 等用于收集和分析数据。

  • iostat 用于监控系统输入/输出设备的统计信息。特点:

    • 提供 CPU 使用情况和各个设备的输入/输出统计。

    • 有助于识别I/O瓶颈,优化存储设备的性能。

    • 使用简单,常用于系统性能监视。

  • top 是一个实时系统监控工具,显示 CPU、内存和进程的使用情况。特点:

    • 显示活动进程的实时列表,包括 CPU 使用率、内存使用率等。

    • 可以按 CPU、内存等指标进行排序,方便用户监控系统负载。

    • 提供交互式界面,可以杀死进程或改变其优先级。

  • vmstat 提供关于虚拟内存、进程、CPU活动的统计信息。特点:

    • 报告系统的虚拟内存信息、进程、CPU活动等,帮助评估系统的性能。

    • 提供内存、交换、I/O、系统和CPU等多维度的信息。

    • 适合长时间监控和诊断。

  • htop 是 top 的增强版,提供更友好的用户界面。

  • mpstat 提供CPU的各个核心的使用情况统计。特点:

    • 可以监控各个CPU核心的使用情况,适合多核CPU的性能监控。

    • 提供详细的用户、系统、空闲等CPU活动信息。

    • 适合分析多核处理器的性能瓶颈。

  • sar 提供系统活动报告,用于收集和报告各类系统性能数据。特点:

    • 可以定期收集CPU、内存、网络、I/O等各方面的性能数据。

    • 能够生成历史数据,适合长期监控和分析。

    • 一般配合 sysstat 包使用,支持多种统计项。

  • dstat 是一个多功能的系统监控工具,整合了多种监控工具的功能。特点:

    • 同时显示 CPU、I/O、网络、内存等统计信息,提供全面的视图。

    • 提供实时数据,易于观察系统的当前状态。

    • 可以输出成CSV格式,方便做后续分析。

    通常,最常使用的是 vmstat 这个工具,用来查询系统的上下文切换情况。此命令显示关于内核线程、虚拟内存、磁盘I/O 、陷阱和CPU占用率的统计信息。所有,这里将特地说明vmstat工具。

    vmstat基本使用:

    vmstat [选项] [间隔] [次数]
    选项说明
    -a显示活动的内存信息。
    -m显示内存页信息。
    -s显示内存的系统统计信息。
    -d显示块设备的统计信息。
    -p显示每个分区的统计信息。
    -t显示时间戳。

    间隔: 指定输出更新的秒数。
    次数: 指定输出的行数。

    例如:

    # 每隔5秒输出1组数据
    ubuntu:~$ 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
    17 0 12 1179508 469892 3315788 0 0 6 20 49 74 0 0 99 0 0
    16 0 12 1179376 469892 3315788 0 0 0 0 1048 678 100 0 0 0 0
    16 0 12 1179376 469892 3315788 0 0 0 0 1045 675 100 0 0 0 0
    16 0 12 1179376 469892 3315788 0 0 0 0 1047 685 100 0 0 0 0

    运行vmstat命令后,输出的字段代表了不同的系统状态。以下是输出字段的具体含义:


    类别字段描述
    procsr就绪队列的长度,正在运行和等待CPU的进程数 (Runnable processes)

    b等待I/O的进程数 (Blocked processes),处于不可中断睡眠状态的进程数
    memoryswpd使用的交换空间大小 (Swap space used)

    free空闲物理内存 (Free memory)

    buff使用的缓冲区内存 (Buffered memory) ,是I/O系统存储的磁盘块文件的元数据的统计信息。

    cache使用的缓存内存 (Cached memory),是操作系统用来缓存磁盘数据的缓冲区,操作系统会自动一调节这个参数,在内存紧张时操作系统会减少cache的占用空间来保证其他进程可用。
    swapsi从交换空间交换到物理内存 (Swap in) 。值较大时,说明系统频繁使用交换区,应该查看操作系统的内存是否够用。

    so从物理内存交换到交换空间 (Swap out),值较大时,说明系统频繁使用交换区,应该查看操作系统的内存是否够用。
    iobi每秒钟从块设备读取的字节数 (Blocks in),代表I/O活动,根据其大小可以知道磁盘I/O的负载情况。

    bo每秒钟写入块设备的字节数 (Blocks out),代表I/O活动,根据其大小可以知道磁盘I/O的负载情况。
    systemin每秒钟中断的次数 (Interrupts),是每秒中断的次数。

    cs每秒钟上下文切换的次数 (Context switches) ,参数表示线程环境的切换次数,此数据太大时表明线程的同步机制有问题。
    cpuus用户空间使用CPU的时间比率 (User time)

    sy内核空间使用CPU的时间比率 (System time)

    idCPU空闲时间比率 (Idle time)

    wa等待I/O的时间比率 (I/O wait time)

    st从虚拟机偷取的CPU时间 (Steal time)
    04

    案例分析

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

    使用sysbench来模拟系统多线程调度切换的情况。有命令,都默认以 root 用户运行,运行 sudo su root 命令切换到 root 用户。

    1. 测试工具sysbench的安装:

      apt install sysbench
    2. 使用vmstat查看空闲系统的上下文切换次数:

      # 间隔1秒后输出1组数据
      root@ubuntu:/home/fly# 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 12 1173896 470200 3318136 0 0 6 20 49 74 0 0 99 0 0
    3. 在另一个终端开始压测:

      # 以10个线程运行5分钟的基准测试,模拟多线程切换的问题
      sysbench --num-threads=10 --max-time=300 --max-requests=10000000 --test=threads run
    4. 使用vmstat查看:

      # 每隔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
      9 0 0 206356 67344 548616 0 0 0 14 1666 1596479 7 93 0 0 0
      7 0 0 206356 67344 548612 0 0 0 0 1680 1542861 10 90 0 0 0
      7 0 0 206356 67344 548612 0 0 0 0 1700 1550600 11 90 0 0 0
      7 0 0 206356 67344 548612 0 0 0 0 1704 1586618 9 91 0 0 0
      8 0 0 206356 67348 548612 0 0 0 8 1700 1493243 8 93 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 的占用率升高。

  • 使用pidstat查看。什么进程导致了这些问题呢?在第三个终端再用 pidstat 来看一下, CPU 和进程上下文切换的情况:

    # 每隔1秒输出1组数据(需要 Ctrl+C 才结束)
    # -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标
    $ pidstat -w -u 1

    从 pidstat 的输出你可以发现,CPU 使用率的升高果然是 sysbench 导致的,它的 CPU 使用率已经达到了 100%。但上下文切换则是来自其他进程,包括非自愿上下文切换频率最高的 pidstat ,以及自愿上下文切换频率最高的内核线程 kworker 和 sshd

  • 怪异的事儿:pidstat 输出的上下文切换次数,加起来也就几百,比 vmstat 的 139 万明显小了太多。这是怎么回事呢?难道是工具本身出了错吗?别着急,在怀疑工具之前,我们再来回想一下,前面讲到的几种上下文切换场景。其中有一点提到, Linux 调度的基本单位实际上是线程,而我们的场景 sysbench 模拟的也是线程的调度问题,那么,是不是 pidstat 忽略了线程的数据呢?

    通过运行 man pidstat ,你会发现,pidstat 默认显示进程的指标数据,加上 -t 参数后,才会输出线程的指标。所以,我们可以在第三个终端里, Ctrl+C 停止刚才的 pidstat 命令,再加上 -t 参数,重试一下看看:

    # 每隔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

    现在你就能看到了,虽然 sysbench 进程(也就是主线程)的上下文切换次数看起来并不多,但它的子线程的上下文切换次数却有很多。看来,上下文切换罪魁祸首,还是过多的 sysbench 线程,这也解释了:对于线程池而言,并不是开启的线程越多越好

    每秒上下文切换多少次才算正常呢?这个数值其实取决于系统本身的 CPU 性能。如果系统的上下文切换次数比较稳定,那么从数百到一万以内,都应该算是正常的。但当上下文切换次数超过一万次,或者切换次数出现数量级的增长时,就很可能已经出现了性能问题。

    这时,你还需要根据上下文切换的类型,再做具体分析。比方说:

    1. 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;

    2. 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;

    3. 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。


    05

    遇到CPU利用率高该如何排查?

    遇到CPU使用率高时,首先确认CPU是消耗在哪一块,如果是内核态占用CPU较高:

    1. %iowait 高,这时要重点关注磁盘IO的相关操作,是否存在不合理的写日志操作,数据库操作等;

    2. %soft%cs 高,观察CPU负载是否较高、网卡流量是否较大,可不可以精简数据、代码在是否在多线程操作上存在不合适的中断操作等;

    3. %steal 高,这种情况一般发生在虚拟机上,这时要查看宿主机是否资源超限;

    如果是用户态较高,且没有达到预期的性能,说明应用程序需要优化。

    根据指标查找工具:

    性能指标工具说明
    平均负载uptime, topuptime最简单;top提供了更全的指标
    系统整体CPU使用率vmstat, mpstat, top, sar, /proc/stattop、vmstat、mpstat 只可以动态查看,而sar 还可以记录历史数据
    进程CPU使用率top, pidstat, ps, htop, atoptop和ps可以按CPU使用率给进程排序,而pidstat只是显示当前CPU的进程
    系统上下文切换vmstat除了上下文切换次数,还提供运行状态和可阻塞状态进程的数量
    进程上下文切换pidstat注意加上 -w 选项
    软件中断top, /proc/softirqs, mpstattop提供软件中断CPU使用率,/proc/softirqs和mpstat提供了各种中断在每个CPU上的运行次数
    硬件中断vmstat, /proc/interruptsvmstat提供总的中断次数,/proc/interrupts提供各个中断在每个CPU上运行的累积次数
    网络dstat, sar, tcpdumpdstat和sar提供网络接收和发送情况,而tcpdump则是实时抓取正在进行的网络通信
    I/Odstat, sardstat和sar都提供了I/O的整体情况
    CPU 个数/proc/cpuinfo, lscpulscpu更简单
    事件分析perf, execsnoopperf可以用来分析CPU的缓慢及内部状态,execsnoop需注意控制选项

    根据工具查指标:


    性能工具CPU性能指标
    uptime平均负载
    top平均负载、运行队列、整体的CPU使用率以及每个进程的状态和CPU使用率
    htoptop增强版,以不同颜色区分不同类型的进程,更新频率
    atopCPU、内存、磁盘和网络等各种资源的全面监控
    vmstat系统整体的CPU使用率,上下文切换次数、中断次数,还包括正在运行和可中断状态的进程数
    mpstat每个CPU的使用率和软中断次数
    pidstat进程和线程的CPU使用率,中断上下文切换次数
    /proc/softirqs软件中断类型在每个CPU上的累积中断次数
    /proc/interrups硬件中断类型在每个CPU上的累积中断次数
    ps每个进程的状态和CPU使用率
    pstree进程的父子关系树
    dstat系统整体的CPU使用率
    sar系统整体的CPU使用率,包括可配置的历史数据
    strace进程级系统调用使用
    perfCPU性能事件采样,如调谐性能分析、CPU缓冲等
    execsnoop监控跟踪进程


    06

    总结

    在现代计算系统中,CPU上下文切换的理解与管理至关重要。上下文切换是操作系统在多个进程或线程之间转换执行状态的过程,虽然这种机制使得多任务处理成为可能,但频繁的上下文切换会导致显著的性能损失。 


    通过监控上下文切换的频率和模式,可以识别出性能瓶颈,并采取合适的优化措施。优化的方法可能包括调整进程优先级、减小进程数量或合理配置系统资源等。这不仅能够提高CPU利用率,还能减少由于上下文切换带来的额外开销,从而显著增强系统的整体性能和响应速度。

    公众号: Lion 莱恩呀

    微信号: 关注获取

    扫码关注 了解更多内容

    点个 在看 你最好看


    Lion 莱恩呀
    专注分享高性能服务器后台开发技术知识,涵盖多个领域,包括C/C++、Linux、网络协议、设计模式、中间件、云原生、数据库、分布式架构等。目标是通过理论与代码实践的结合,让世界上看似难以掌握的技术变得易于理解与掌握。
     最新文章