【Linux】之 上下文切换(排查实战)

一、简介


CPU 指令寄存器(IR)*:是 CPU 内置的容量小、但速度极快的内存

程序计数器(PC):用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置
这两个组合起来为:CPU 上下文

CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。


(1)问题


1)CPU为什么要进行上下文切换?

当多个进程竞争CPU的时候,CPU为了保证每个进程能公平被调度运行,采取了处理任务时间分片的机制,轮流处理多个进程,由于CPU处理速度非常快,在人类的感官上认为是并行处理,实际是"伪"并行,同一时间只有一个任务在运行处理。


2)上下文切换主要消耗什么资源,为什么说上下文切换次数过多不可取?

根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到到微秒的CPU时间,这些时间对CPU来说,就好比人类对1分钟或10分钟的感觉概念。

在分秒必争的计算机处理环境下,浪费太多时间在切换上,只能会降低真正处理任务的时间,表象上导致延时、排队、卡顿现象发生。


3)上下文切换分几种?

  • 进程上下文切换

  • 线程上下文切换

  • 中断上下文切换


4)什么情况下会触发上下文切换?

系统调用、进程状态转换(运行、就绪、阻塞)、时间片耗尽、系统资源不足、sleep、优先级调度、硬件中断等


5)线程上下文切换和进程上下文切换的最大区别?

线程是调度的基本单位,进程是资源拥有的基本单位,同属一个进程的线程,发生上下文切换,只切换线程的私有数据,共享数据不变,因此速度非常快。


6)中断上下文切换,如何理解?

为了快速响应硬件的事件(如USB接入),中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。

而打断其它进程执行时,需要进行上下文切换。

中断事件过多,会无谓的消耗CPU资源,导致进程处理时间延长。


7)有哪些减少上下文切换的技术用例?

  • 数据库连接池(复用连接)

  • 合理设置应用的最大进程,线程数

  • 直接内存访问DMA

  • 零拷贝技术


(2)上下文切换

根据上下文切换的类型,可分:

  • 自愿上下文切换:进程无法获取所需资源,导致上下文切换

例如,I/O、内存等系统资源不足时,就会发生。

  • 非自愿上下文切换:则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换

例如,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。

自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。



二、实战


vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数

  • cs(context switch)是每秒上下文切换的次数。
  • in(interrupt)则是每秒中断的次数。
  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
  • b(Blocked)则是处于不可中断睡眠状态的进程数。
# 每隔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 185824  39412 724164    0    0    58    52  221  125  1  0 99  0  0
 0  0      0 185428  39412 724196    0    0     0     0   18   29  0  0 100  0  0
 0  0      0 185428  39420 724196    0    0     0     2   17   25  0  0 100  0  0
 0  0      0 185428  39420 724196    0    0     0     0   19   29  0  0 100  0  0
 0  0      0 185428  39420 724196    0    0     0     2   16   24  0  0 100  0  0
 0  0      0 185428  39424 724196    0    0     0     2   18   32  0  0 100  0  0
 0  0      0 185428  39424 724196    0    0     0     0   26   32  0  0 100  0  0

查看每个进程的详细情况

  • cswch,表示每秒自愿上下文切换(voluntary content switches)的次数
  • nvcswch,表示每秒非自愿上下文切换(no voluntary content switches)的次数
# 每个5秒输出1组数据
$ pidstat -w 5
Linux 4.4.0-131-generic (ubuntu) 	01/19/2020 	_x86_64_	(1 CPU)

02:10:56 PM   UID       PID   cswch/s nvcswch/s  Command
02:11:01 PM     0         3      0.60      0.00  ksoftirqd/0
02:11:01 PM     0         7      1.80      0.00  rcu_sched
02:11:01 PM     0        10      0.20      0.00  watchdog/0
02:11:01 PM     0      1032      1.00      0.00  iscsid
02:11:01 PM     0      1033      3.79      0.00  iscsid
02:11:01 PM     0     26363      2.40      0.00  kworker/u2:2
02:11:01 PM     0     26461      1.80      0.00  kworker/0:0
02:11:01 PM     0     26472      0.40      0.00  kworker/u2:0
02:11:01 PM  1000     26499      0.20      0.20  pidstat
# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标

$ pidstat -wt 1



(1)模拟多线程调度的瓶颈

预先安装:
apt install sysbench sysstat


  1. 模拟系统多线程调度
    sysbench --threads=10 --max-time=300 threads run
# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题

$ sysbench --test=threads --num-threads=10 --max-time=300 run
sysbench 0.4.12:  multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 10

Doing thread subsystem performance test
Thread yields per test: 1000 Locks used: 8
Threads started!
Done.


Test execution summary:
    total time:                          7.4051s
    total number of events:              10000
    total time taken by event execution: 73.9974
    per-request statistics:
         min:                                  1.12ms
         avg:                                  7.40ms
         max:                                 41.17ms
         approx.  95 percentile:              16.78ms

Threads fairness:
    events (avg/stddev):           1000.0000/27.10
    execution time (avg/stddev):   7.3997/0.01

  1. 观察上下文切换情况:
    vmstat 1
# 每隔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
 0  0      0 465948  18460 460016    0    0    44    18   20 1235  0  0 100  0  0
 0  0      0 465948  18460 460048    0    0     0     0   23   41  0  0 100  0  0
 0  0      0 465948  18460 460048    0    0     0     0   18   34  0  0 100  0  0
 8  0      0 465144  18460 460048    0    0     0     0   66 678790  3 17 80  0  0
 7  0      0 465144  18460 460048    0    0     0     0   72 1633038 19 81  0  0  0
 8  0      0 465144  18460 460048    0    0     0     0   74 1545122 19 81  0  0  0
 8  0      0 465144  18468 460040    0    0     0    12   76 1617658 19 81  0  0  0
 7  0      0 465144  18468 460048    0    0     0     0   75 1601454 19 81  0  0  0
 9  0      0 465144  18468 460048    0    0     0     0   73 1637995 14 86  0  0  0
 8  0      0 465144  18468 460048    0    0     0     0   75 1634001 22 78  0  0  0
 0  0      0 465684  18468 460048    0    0     0     0   83 1445609 18 53 29  0  0
 0  0      0 465700  18468 460048    0    0     0     0   17   26  0  0 100  0  0
 0  0      0 465700  18468 460048    0    0     0     0   18   28  0  0 100  0  0

观察指标:

  • r列:就绪队列的长度,有达到 9, 远超过系统 CPU 的个数 1, 肯定存在大量的 CPU 竞争
  • us(user)和sy(system)列:相加达到 100%,sy列 系统CPU使用率,说明CPU主要被内核占用了
  • in:中断次数
  1. pidstat查看 CPU 和 进程上下文切换的情况
# 每隔1秒输出1组数据(需要 Ctrl+C 才结束)
# -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标
$ pidstat -w -u 1
Linux 4.4.0-171-generic (ubuntu) 	01/20/2020 	_x86_64_	(1 CPU)

02:22:48 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
02:22:49 PM     0      2535   53.85  334.62    0.00  388.46     0  sysbench

02:22:48 PM   UID       PID   cswch/s nvcswch/s  Command
02:22:49 PM     0         3      7.69      0.00  ksoftirqd/0
02:22:49 PM     0         7     23.08      0.00  rcu_sched
02:22:49 PM     0      1094      3.85      0.00  iscsid
02:22:49 PM     0      1095     15.38      0.00  iscsid
02:22:49 PM     0      1432      7.69      0.00  kworker/0:1
02:22:49 PM     0      2165      3.85      0.00  dhclient
02:22:49 PM     0      2343      7.69      0.00  kworker/u2:2
02:22:49 PM     0      2546      3.85      0.00  pidstat
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
Linux 4.4.0-171-generic (ubuntu) 	01/20/2020 	_x86_64_	(1 CPU)

02:53:59 PM   UID      TGID       TID   cswch/s nvcswch/s  Command
02:54:00 PM     0         3         -      3.70      0.00  ksoftirqd/0
02:54:00 PM     0         -         3      3.70      0.00  |__ksoftirqd/0
02:54:00 PM     0         7         -     11.11      0.00  rcu_sched
02:54:00 PM     0         -         7     11.11      0.00  |__rcu_sched
02:54:00 PM     0         -       910      3.70      0.00  |__gmain
02:54:00 PM     0      1094         -      3.70      0.00  iscsid
02:54:00 PM     0         -      1094      3.70      0.00  |__iscsid
02:54:00 PM     0      1095         -     14.81      0.00  iscsid
02:54:00 PM     0         -      1095     14.81      0.00  |__iscsid
02:54:00 PM     0      1432         -      3.70      0.00  kworker/0:1
02:54:00 PM     0         -      1432      3.70      0.00  |__kworker/0:1
02:54:00 PM     0      2528         -      3.70      0.00  kworker/u2:1
02:54:00 PM     0         -      2528      3.70      0.00  |__kworker/u2:1
02:54:00 PM     0         -      2571 106903.70 500133.33  |__sysbench
02:54:00 PM     0         -      2572  86100.00 535525.93  |__sysbench
02:54:00 PM     0         -      2573  75851.85 534374.07  |__sysbench
02:54:00 PM     0         -      2574  73192.59 530318.52  |__sysbench
02:54:00 PM     0         -      2575 102440.74 500244.44  |__sysbench
02:54:00 PM     0         -      2576  70218.52 522592.59  |__sysbench
02:54:00 PM     0         -      2577 101592.59 508733.33  |__sysbench
02:54:00 PM     0         -      2578  85703.70 519040.74  |__sysbench
02:54:00 PM     0         -      2579  92181.48 511122.22  |__sysbench
02:54:00 PM     0         -      2580  62811.11 548103.70  |__sysbench
02:54:00 PM     0      2581         -      3.70      0.00  pidstat
02:54:00 PM     0         -      2581      3.70      0.00  |__pidstat

可以看到 sysbench进程(主线程)的上下切换次数并不多,但其子线程上下文切换次数很多

pidstat 只是一个进程的性能分析工具,并不提供任何关于中断的详细信息。

/proc实际上是 Linux的一个虚拟文件系统,用于内核空间与用户空间之间的通信。
/proc/interrupts,提供了一个只读的中断使用情况。

# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
Every 2.0s: cat /proc/interrupts                                                       Mon Jan 20 14:59:14 2020

           CPU0
  0:         47   IO-APIC   2-edge      timer
  1:        644   IO-APIC   1-edge      i8042
  8:          0   IO-APIC   8-edge      rtc0
  9:          8   IO-APIC   9-fasteoi   acpi
 12:        156   IO-APIC  12-edge      i8042
 14:          0   IO-APIC  14-edge      ata_piix
 15:      12827   IO-APIC  15-edge      ata_piix
 18:          0   IO-APIC  18-fasteoi   vmwgfx
 19:      23449   IO-APIC  19-fasteoi   enp0s3
 20:          0   IO-APIC  20-fasteoi   vboxguest
 21:       9271   IO-APIC  21-fasteoi   0000:00:0d.0, snd_intel8x0
 22:         29   IO-APIC  22-fasteoi   ohci_hcd:usb1
NMI:          0   Non-maskable interrupts
LOC:     204680   Local timer interrupts
SPU:          0   Spurious interrupts
PMI:          0   Performance monitoring interrupts
IWI:          0   IRQ work interrupts
RTR:          0   APIC ICR read retries
RES:          0   Rescheduling interrupts
CAL:          0   Function call interrupts
TLB:          0   TLB shootdowns
TRM:          0   Thermal event interrupts
发布了404 篇原创文章 · 获赞 270 · 访问量 42万+

猜你喜欢

转载自blog.csdn.net/fanfan4569/article/details/104051961