文章目录
一、简介
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
- 模拟系统多线程调度
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
- 观察上下文切换情况:
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
:中断次数
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