前言
linux是一个多任务操作系统,它支持远大于CPU数量的任务同时运行。当然,这个同时运行不是真的同时运行,而是系统在很短的时间内轮流分配CPU资源,由于CPU的速度很快,所以给人一种同时运行的错觉。
每个任务运行前,CPU需要知道任务从哪加载、从哪开始运行,也就是需要系统设置好任务的CPU寄存器和程序计数器。这俩是CPU执行任何任务前所必须的依赖环境,因此也被叫做CPU上下文。而CPU上下文切换便是,先把前一个任务的CPU上下文保存起来,然后加载新任务的上下文到CPU寄存器和程序计数器中,最后再跳转到程序计数器所指向的新位置来运行新任务。
根据任务的不同,CPU的上下文切换可以分为几个不同的场景:进程上下文切换、线程上下文切换,以及中断上下文切换。
进程上下文切换
linux进程的运行空间分为内核空间和用户空间。内核空间具有最高权限,可以访问所有资源;用户空间只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入内核,才能访问特权资源。当进程在用户空间运行时,被称为进程的用户态;当进程陷入到内核空间,被称为进程的内核态。
用户态通过系统调用陷入内核态的时候,会发生CPU上下文切换。系统需要先保存用户态的上下文,然后加载内核态代码的上下文,执行完内核态任务后,再进行一次CPU上下文切换,切换回用户态的上下文。因此一次系统调用会发生两次CPU上下文切换。这里需要注意,系统调用过程一直是同一个进程在运行,不是一个进程切换到另一个进程。
进程是由内核管理和调度的,进程的切换只能在内核空间。因此,进程的上下文不仅包括虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。由于进程上下文较多,切换时候不仅包括用户态信息,还包括内核态信息,所以上下文切换时耗费的时间更多。虽然一般只需要纳秒级的时间,但进程上下文切换次数较多的时候,总体耗费的时间就比较可观了。在大量进程上下文切换的情况下,CPU将大量时间耗费在寄存器、内核栈、虚拟内存等资源的保存和恢复上,进而缩短了真正运行进程的时间。
Linux通过TLB(Translation Lookaside Buffer)来管理虚拟内存到物理内存的映射关系。当虚拟内存更新后,TLB也需要刷新,内存的访问也会随之变慢。
只有在进程调度的时候,才需要切换进程上下文。linux为每个CPU维护了一个就序队列,将活跃进程(在运行和等待中的进程)按照优先级和等待CPU的时间排序,然后选择最需要CPU的进程,也就是优先级最高和等待时间最长的进程来运行。
触发进程调度的常见场景:
- 进程的CPU时间片用完,就会被系统挂起,切换到其他进程。
- 进程所需的系统资源不足时,需要等到资源满足后才可以运行,这时候也会被挂起。
- 进程通过sleep()函数主动挂起。
- 有优先级更高的进程需要运行时,当前进程会被挂起。
- 硬件中断时,进程会被挂起。
线程上下文切换
进程是资源分配的基本单位,线程是调度的基本单位。进程内有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时是不需要修改的。线程也有自己的私有资源,比如栈和寄存器等,这些在上下文切换时也需要保存。
- 线程上下文切换前后的两个线程如果不属于同一个进程,此时资源不共享,切换过程同进程上下文切换。
- 切换前后的两个线程若属于同一个进程,共享的虚拟内存等资源保持不动,切换线程的私有资源即可。在这种情况下,切换消耗的资源更少。
中断上下文切换
为了快速响应硬件的时间,中断处理会打断进程的正常调度和执行,转而调用中断处理程序。
中断上下文切换并不涉及进程的用户态,当中断发生在一个正处于用户态的进程时,不需要保存和恢复这个进程的用户态资源。中断上下文值包括内核态中断服务程序执行所必需的状态,包括CPU寄存器、内核堆栈、硬件中断参数等。
查看系统的上下文切换情况
使用vmstat
(apt或yum安装sysstat
)可查看系统的上下文切换情况
# 每2秒输出一组报告,共1份报告 vmstat 2 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b 交换 空闲 缓冲 缓存 si so bi bo in cs us sy id wa st 1 0 0 2118592 100844 1011392 0 0 211 259 188 249 3 1 96 0 0
- cs(context switch):每秒上下文切换的次数。此处为249
- in(interrupt): 每秒中断的次数。此处为188
- r(running or runnable): 就绪队列的长度,即正在运行和等待CPU的进程数
- b(blocked): 处于不可中断睡眠状态的进程数
查看每个进程的上下文切换情况
使用pidstat
(同样依赖于sysstat)可查看进程的上下文切换情况。
# 每3秒输出一组报告,-w表示输出进程的上下文切换指标,-wt表示输出线程的上下文切换指标 pidstat -w 3 13时53分56秒 UID PID cswch/s nvcswch/s Command 13时53分59秒 0 7 4.95 0.00 kworker/0:1-ata_sff 13时53分59秒 0 13 2.64 0.00 rcu_sched 13时53分59秒 0 14 0.33 0.00 migration/0 13时53分59秒 0 19 0.33 0.00 migration/1 13时53分59秒 0 31 1.98 0.00 kcompactd0 13时53分59秒 0 122 1.32 0.00 kworker/1:1H-kblockd 13时53分59秒 0 247 0.99 0.33 jbd2/dm-0-8 13时53分59秒 116 501 0.66 0.00 avahi-daemon 13时53分59秒 0 505 0.99 0.00 NetworkManager 13时53分59秒 104 541 0.33 0.00 rsyslogd 13时53分59秒 0 549 0.33 0.00 wpa_supplicant 13时53分59秒 0 5102 2.31 0.33 kworker/1:1-events 13时53分59秒 0 5576 1.98 0.00 kworker/u4:0-events_freezable_power_ 13时53分59秒 1000 5707 0.33 0.00 pidstat
- cswch: 每秒自愿上下文切换的次数(资源不足时,进程自愿上下文切换)
- nvcswch: 每秒非自愿上下文切换的次数(时间片到期等原因,被系统强制调度而发生的上下文切换)
查看系统中断情况
# 持续查看/proc/interrupts, 并高亮变化的地方 watch -d cat /proc/interrupts CPU0 CPU1 0: 29 0 IO-APIC 2-edge timer 1: 602 0 IO-APIC 1-edge i8042 8: 0 0 IO-APIC 8-edge rtc0 9: 0 0 IO-APIC 9-fasteoi acpi 12: 0 366 IO-APIC 12-edge i8042 14: 0 0 IO-APIC 14-edge ata_piix 15: 0 2563 IO-APIC 15-edge ata_piix 18: 0 2 IO-APIC 18-fasteoi vboxvideo 19: 13747 23341 IO-APIC 19-fasteoi enp0s3 20: 8310 0 IO-APIC 20-fasteoi vboxguest 21: 17084 18354 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0 22: 25 0 IO-APIC 22-fasteoi ohci_hcd:usb1 NMI: 0 0 Non-maskable interrupts LOC: 309756 154510 Local timer interrupts SPU: 0 0 Spurious interrupts PMI: 0 0 Performance monitoring interrupts IWI: 0 0 IRQ work interrupts RTR: 0 0 APIC ICR read retries RES: 95556 93683 Rescheduling interrupts CAL: 7548 8726 Function call interrupts TLB: 7126 6564 TLB shootdowns TRM: 0 0 Thermal event interrupts THR: 0 0 Threshold APIC interrupts DFR: 0 0 Deferred Error APIC interrupts MCE: 0 0 Machine check exceptions MCP: 9 9 Machine check polls ERR: 0 MIS: 0 PIN: 0 0 Posted-interrupt notification event NPI: 0 0 Nested posted-interrupt event PIW: 0 0 Posted-interrupt wakeup event
- RES:重调度中断。表示唤醒空闲状态的CPU来调度新的任务运行。
参考
- 极客时间 - linux性能优化实战