感谢前辈,光荣属于前辈。
1进程概念
进程定义
进程是并发环境下,一个具有独立功能的程序在某个数据集上的一次执行活动,它是操作系统进行资源分配和保护的基本单位,也是执行的单位。
PCB
进程控制块(Process Control Block,PCB)是为了描述和控制进程的运行而定义的一种数表结构,它是进程存在的唯一标志,也是进程实体的一部分。操作系统对进程的管理和控制主要以PCB为依据。PCB中包括了操作系统所需要的进程运行的所有信息。
进程的上下文
用户级上下文: 正文、数据、用户堆栈以及共享存储区;寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
2线程概念
线程上下文
多线程环境下的进程定义,很显然,进程是系统进行资源分配和保护的基本单位。进程包括容纳进程映像的一个虚拟地址空间,以及对CPU、I/O资源、文件以及其他资源的有保护有控制的访问。
可见,一个进程可以划分为两个部分:一部分是资源部分,一部分是线程部分。
TCB
线程由线程控制块(Thread Control Block,TCB)、用户堆栈、系统堆栈以及一组处理器状态寄存器和一个私用内存存储区组成。
3上下文
cpu上下文
CPU 寄存器,是 CPU 内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU 上下文。
进程上下文切换
每当内核压入一个新的系统上下文层时,它就要保存一个进程的上下文。特别是当系统收到一个中断,或一个进程执行系统调用,或当内核做上下文切换时,就要对进程的上下文进行保存。上下文切换情况:
- 一个进程结束,需要从队列中重新选择一个进程运行。
- 如果进程在系统资源不足(内存不足等),则返回到运行队列,并由系统调度其他进程运行。
- 如果不访问磁盘I/O等资源就不能继续,它会自己主动挂起时,自然也会重新调度。
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
- 发生硬中断,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。
了解这几个场景是非常有必要的,因为一旦出现上下文切换的性能问题,它们就是幕后凶手。
线程上下文切换
- 第一种, 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。
- 第二种,前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。到这里你应该也发现了,虽然同为上下文切换,但同进程内的线程切换,要比多进程间的切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。
虽然同为上下文切换,但同进程内的线程切换,要比多进程间的切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。
系统调用
系统调用(system call),指运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。大多数系统交互式操作需求在内核态运行。
典型实现(Linux)
Linux 在x86上的系统调用通过 int 80h 实现,用系统调用号来区分入口函数。操作系统实现系统调用的基本过程是:
- 应用程序调用库函数(API);
- API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
- 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
- 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
- 中断处理函数返回到 API 中;
- API 将 EAX 返回给应用程序。
CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。
总结:一次系统调用的过程,其实是发生了两次 CPU 上下文切换。但是主要是CPU寄存器,不会涉及到虚拟内存等资源。
中断上下文切换
无论是硬件中断(如来自时钟和外设)、可编程中断(programmed interrupt)(执行引起“软件中断”(software interrupt)的指令),还是例外中断(如页面错),都由系统负责处理。
- 硬件通过触发信号,向CPU发送中断信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核, 内核通过这些参数进行中断处理。
当发生一个中断时,如果CPU正在比该中断级低的处理机运行级上运行,它就在解码下条指令之前,接收该中断,并提高处理机执行级。这样,在它处理当前的中断时,就不会响应该级别或更低级别的中断,从而维护了内核数据结构的完整性。内核处理中断的操作顺序如下。
- 切换到到内核线程并对正在执行的进程,保存其当前寄存器上下文并创建(压入)一个新上下文。
- 确定中断源,识别中断类型(如时钟或磁盘)。若可以的话,还识别中断的单元号(如哪个磁盘机引起中断)。当系统接收一个中断时,它从机器中得到一个数,系统把这个数用作查表的偏移量。这个表通常被称为中断向量(interrupt vector)。中断向量的内容因机器而异。但一般都含有每种中断源的中断处理程序的地址以及中断处理程序取得参数的方式。内核调用中断处理程序。从逻辑上讲,新上下文层的核心栈不同于前一上下文层的核心栈。在实现方法上,有些是用正在运行的进程的核心栈存放中断处理程序的栈结构,另一些则是使用全局中断栈存放中断处理程序的栈结构,后者能保证中断处理程序不用进行上下文切换就能返回。
- 中断处理程序工作完毕前返回。内核执行一系列特殊机器指令。这些指令恢复前一上下文层的寄存器上下文和核心栈,使它们和中断发生时的情况一样,并恢复该上下文层的运行。相应的进程的工作情况可能会受到中断处理程序的影响,因为中断处理程序可能已修改过内核的全局数据结构,并唤醒过睡眠的进程。但在一般情况下,进程会像从来未发生过中断一样继续运行。
4分析linux系统的cpu上下文切换
工具
vmstat
vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。
代码语言:javascript复制 $ 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
2 0 0 27458868 145996 4781912 0 0 0 1 0 0 0 0 99 0 0
0 0 0 27459388 145996 4781928 0 0 0 3 8937 15791 1 1 99 0 0
0 0 0 27457272 145996 4781948 0 0 0 10 9022 15774 1 1 99 0 0
你可以通过man手册解读每列的含义。现在我们重点强调:
- cs(context switch)是每秒上下文切换的次数。
- in(interrupt)是每秒中断的次数。
- r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
- b(Blocked)是处于不可中断睡眠状态的进程数。
vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,就需要使用我们前面提到过的 pidstat 了。给它加上 -w 选项,你就可以查看每个进程上下文切换的情况了。
代码语言:javascript复制
# 每隔5秒输出1组数据
$ pidstat -wu -t 5
Linux 4.4.0-142-generic (i-0nxoa13q) 07/22/2021 _x86_64_ (16 CPU)
03:54:53 PM UID TGID TID %usr %system %guest %CPU CPU Command
03:54:58 PM 0 3 - 0.00 0.20 0.00 0.20 0 ksoftirqd/0
03:54:58 PM 0 - 3 0.00 0.20 0.00 0.20 0 |__ksoftirqd/0
03:54:58 PM 0 7 - 0.00 0.20 0.00 0.20 12 rcu_sched
03:54:58 PM 0 6849 - 0.80 0.00 0.00 0.80 10 dockerd
...
03:54:53 PM UID TGID TID cswch/s nvcswch/s Command
03:54:58 PM 0 3 - 64.21 0.00 ksoftirqd/0
03:54:58 PM 0 - 3 64.21 0.00 |__ksoftirqd/0
03:54:58 PM 0 7 - 98.01 0.00 rcu_sched
03:54:58 PM 0 - 7 98.01 0.00 |__rcu_sched
...
这个结果中有两列内容是我们的重点关注对象。一个是 cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。这两个概念你一定要牢牢记住,因为它们意味着不同的性能问题:
- 自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
- 非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。
proc 文件系统
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。深入了解可以去看这篇文章:https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。
sysbench压力测试
sysbench是一个开源的、模块化的、跨平台的多线程性能测试工具,可以用来进行CPU、内存、磁盘I/O、线程、数据库的性能测试。Sysbench的测试主要包括以下几个方面:
- 1、磁盘io性能
- 2、cpu性能
- 3、内存分配及传输速度
- 4、POSIX线程性能
- 5、调度程序性能
- 6、数据库性能(OLTP基准测试)
下面的案例基于 Ubuntu 18.04,当然,其他的 Linux 系统同样适用。我使用的案例环境如下所示:
- 机器配置:4 CPU,8GB
- 预先安装 sysbench 和 sysstat 包,如 apt install sysbench sysstat。
# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题
sysbench --threads=10 --max-time=300 threads run
top命令:
代码语言:javascript复制top - 11:10:05 up 10 days, 35 min, 3 users, load average: 2.72, 1.27, 0.64
Tasks: 120 total, 1 running, 119 sleeping, 0 stopped, 0 zombie
%Cpu0 : 22.3 us, 69.4 sy, 0.0 ni, 8.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 23.2 us, 66.3 sy, 0.0 ni, 10.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 22.3 us, 67.1 sy, 0.0 ni, 10.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 22.8 us, 68.8 sy, 0.0 ni, 8.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8008272 total, 2646040 free, 3650548 used, 1711684 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 4048240 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
18770 root 20 0 94368 3672 2696 S 378.4 0.0 2:43.48 sysbench
看到内核cpu使用率很高。可疑进程为sysbench。此时可以使用pidstat命令查看:
代码语言:javascript复制pidstat -wu -t 5 -p 18770
Linux 3.10.0-1160.15.2.el7.x86_64 (pulsar-1-348d-0002-f741-0002) 2021年07月23日 _x86_64_ (4 CPU)
11时11分26秒 UID TGID TID %usr %system %guest %CPU CPU Command
11时11分31秒 0 18770 - 98.60 100.00 0.00 100.00 3 sysbench
11时11分31秒 0 - 18770 0.00 0.00 0.00 0.00 3 |__sysbench
11时11分31秒 0 - 18771 9.00 28.60 0.00 37.60 2 |__sysbench
11时11分31秒 0 - 18772 9.60 27.40 0.00 37.00 3 |__sysbench
11时11分31秒 0 - 18773 9.80 29.00 0.00 38.80 3 |__sysbench
11时11分31秒 0 - 18774 9.60 27.80 0.00 37.40 2 |__sysbench
11时11分31秒 0 - 18775 9.80 29.00 0.00 38.80 1 |__sysbench
11时11分31秒 0 - 18776 10.40 27.80 0.00 38.20 2 |__sysbench
11时11分31秒 0 - 18777 11.20 27.00 0.00 38.20 1 |__sysbench
11时11分31秒 0 - 18778 9.80 27.40 0.00 37.20 2 |__sysbench
11时11分31秒 0 - 18779 9.80 28.20 0.00 38.00 2 |__sysbench
11时11分31秒 0 - 18780 9.80 28.40 0.00 38.20 0 |__sysbench
11时11分26秒 UID TGID TID cswch/s nvcswch/s Command
11时11分31秒 0 18770 - 0.00 0.00 sysbench
11时11分31秒 0 - 18770 0.00 0.00 |__sysbench
11时11分31秒 0 - 18771 47647.40 132455.60 |__sysbench
11时11分31秒 0 - 18772 47523.60 148473.80 |__sysbench
11时11分31秒 0 - 18773 44959.80 162137.20 |__sysbench
11时11分31秒 0 - 18774 46765.00 138126.60 |__sysbench
11时11分31秒 0 - 18775 47758.40 157361.60 |__sysbench
11时11分31秒 0 - 18776 47324.00 142616.40 |__sysbench
11时11分31秒 0 - 18777 48346.40 145743.60 |__sysbench
11时11分31秒 0 - 18778 49163.60 140320.40 |__sysbench
11时11分31秒 0 - 18779 47157.00 144586.60 |__sysbench
11时11分31秒 0 - 18780 46882.20 144493.00 |__sysbench
非自愿上下文切换为15万左右,说明系统存在强制调度,都在争抢cpu时间,说明 CPU 为系统瓶颈。
上下文切换多少次才算正常?
这个数值其实取决于系统本身的 CPU 性能。如果系统的上下文切换次数比较稳定,且内核cpu使用率很低,都应该算是正常的。当上下文切换在一万左右波动,且内核cpu使用率偏高,就很可能出现了性能问题。
总结
上下文切换发生在操作系统内核中。当看到内核cpu使用率过大,考虑在发生上下文切换。
自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 为系统瓶颈;
中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。