Linux - 上下文切换

原创
2020/06/14 10:37
阅读数 309

开发高并发应用时, 通常是基于 “降低 用户空间IO等待 &上下文切换  对CPU资源的消耗 ” 进行思考设计。

对于JVM应用来说, 常见的耗CPU的有:

  1. 频繁GC,访问量高时,有可能造成频繁的GC,甚至FGC。当调用量大时,内存分配过快,就会造成GC线程不停的执行,进行大量分析计算,导致CPU飙高 。
  2. 计算密集:  序列化与反序列化 、 加解密 、 正则表达式 等 大量计算。( Java 正则表达式使用的引擎实现是 NFA 自动机,这种引擎在进行字符匹配会发生回溯(backtracking)  
  3. 线程上下文切换过频: 大量线程都处于不断的阻塞状态(锁等待、IO等待等)、锁竞争激烈等执行状态的变化过程中。 线程数高的应用 Runable 和 Running 状态的线程不多,这时 CPU 使用率不一定会高 ; BLOCKED  和 WAITING 状态的,这种线程是不会占用 CPU 的 , 这时候又得考虑是否有死锁现象。
  4. 某些线程在做无阻塞的运算,eg. while(true)中不停的做运算,没有任何阻塞 。 死循环会调用 CPU 寄存器进行计数,这个操作就会占用 CPU。其次,如果线程一直处于死循环状态, 不会让出 CPU,除非操作系统时间片到期,但死循环会不断向系统申请时间片,直到系统没有空闲时间做别的事情。

用户态 & 内核态

操作系统都是采用虚拟存储器, 对32位Linux操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。

将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间

  1. 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF,也就是 3G-4G部分 ) 给大家共享使用,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。
  2. 而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)称为用户空间, 用来运行用户程序。

操作系统的核心是内核(kernel),可以访问受保护的内存空间,也有访问底层硬件设备( 硬盘、网卡等)的所有权限 ; 处在内核空间称为内核态

用户进程只能受限的访问内存,且不允许访问外设,占用cpu的能力被剥夺,cpu资源可以被其他程序获取 ; 也就是说不能直接操作内核,保证了内核的安全 。处在用户空间称为用户态通过内存页表  page cache  操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程的地址空间中的数据。

intel cpu提供Ring0- Ring3三种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行的进程(获得CPU),才可能将其转为阻塞状态, 此时是不占用CPU资源

Linux系统中,线程就是能并行运行且与fork创建它们的父进程)共享同一地址空间(内存区域)和其他资源的 轻量级的进程, 每个进程都有自己的内核栈

模式切换

系统调用:  在类 Unix 系统中是指活跃(正在运行在 CPU 上)的进程对于内核所提供的服务的请求。eg.(I/O)和新进程创建, I/O 可以被定义为任何信息流入或流出 CPU 与主内存(RAM)。

当用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作、网络数据发送等操作,必须通过writesend等系统调用命令来执行内核中的代码来完成操作;这时,必须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后再切换回Ring3回到用户态。

  • 当进程运行在内核态时: CPU堆栈指针寄存器指向的是内核堆栈地址,使用的是内核堆栈。 内核栈是属于操作系统空间的一块固定区域,可以用于保存中断现场、保存操作系统子程序间相互调用的参数、返回值等。
  • 当进程运行在用户态时:CPU堆栈指针寄存器指向的是用户堆栈地址,使用的是用户堆栈。  用户栈是属于用户进程空间的一块区域,用户保存用户进程子程序间的相互调用的参数、返回值等。

当进程由于中断进入内核态时,系统会把一些用户态的堆栈地址保存到内核中, 然后设置CPU堆栈指针寄存器的地址为内核栈地址 ; 这样就完成了用户栈向内核栈的切换当返回用户态时,取出内核栈中得信息( 用户栈地址 )恢复到CPU栈指针寄存器 ,这样就返回到程序原来执行的地方。

用户态和内核态 在类 Unix 系统中共存意味着当系统调用发生时 CPU 切换到内核态是必要的。这应该叫做模式切换而不是上下文切换,因为没有改变当前的进程。

上下文切换

上下文指的是: 某一时间点 CPU 寄存器和程序计数器的内容。

寄存器是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存), 寄存器通过对常用值(运算的中间值)的快速访问来提高计算机程序运行的速度。程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在(或下一个将要)执行的指令的位置,具体依赖于特定的系统。

上下文切换在多任务操作系统中是一个必须的特性。多任务操作系统是指多个进程运行在一个 CPU 中互不打扰,看起来像同时运行一样。这个并行的错觉是由于上下文在高速的切换(每秒几十上百次),当某一进程自愿放弃它的 CPU 时间或者系统分配的时间片用完时,就会发生上下文切换。

上下文切换有时也因硬件中断而触发,硬件中断是指硬件设备(如键盘、鼠标、调试解调器、系统时钟)给内核发送的一个信号,该信号表示一个事件(如按键、鼠标移动、从网络连接接收到数据)发生了。

上下文切换只能发生在内核态中,分为 进程(线程)上下文切换、中断上下文切换。可以认为是:内核在 CPU 上 挂起 正在执行进程(含线程)的 运行,然后继续执行之前挂起的众多进程中的某一个。

  1. 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内核中的某处;
  2. 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复;
  3. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。

上下文切换通常是计算密集型的,也就是说,它需要相当可观的处理器时间;在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

查看系统的上下文切换

如果当上下文切换次数超过一万次,或者切换次数出现数量级增长时,很可能已经出现了性能问题。

vmstat主要用来分析系统内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。

# 每隔 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
 1  0      0 190952 156956 2456464    0    0    9    40    1    1 11  2 87  0  0
 0  0      0 190640 156960 2456532    0    0    0    33 4880 9497  4  3 93  0  0
  • cs(context switch) 是每秒上下文切换的次数。

  • in(interrupt) 是每秒中断的次数。

  • r(Running or Runnable) 是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。

  • b(Blocked) 是处于不可中断睡眠状态的进程数。

查看每个进程的详细情况,使用 pidstat

# 每隔 5 秒输出 1 组数据
# -w 参数表示输出进程切换指标,而 -u 参数则表示输出 CPU 使用指标
$ pidstat -w -u 5
Linux 3.10.0-1062.9.1.el7.x86_64 (iZwz9hzc7pd8k5wat4x4wiZ) 	12/10/2020 	_x86_64_	(2 CPU)

03:52:00 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
03:52:01 PM  1000      1496    0.00    0.99    0.00    0.99     0  pidstat
03:52:01 PM  1000      6515    0.00    0.99    0.00    0.99     1  java
03:52:01 PM     0     12805    0.99    0.99    0.00    1.98     0  AliYunDun
03:52:01 PM  1000     21522    0.00    0.99    0.00    0.99     0  java
03:52:01 PM  1000     27459    0.99    0.00    0.00    0.99     0  java

03:52:00 PM   UID       PID   cswch/s nvcswch/s  Command
03:52:01 PM     0         1      0.99      0.00  systemd
03:52:01 PM     0         6      3.96      0.00  ksoftirqd/0
03:52:01 PM     0         9     97.03      0.00  rcu_sched
03:52:01 PM     0        14      1.98      0.00  ksoftirqd/1
03:52:01 PM     0       308      0.99      0.00  crond
03:52:01 PM  1000       391      0.99      0.00  sshd
  • cswch  表示每秒自愿上下文切换的次数。 指进程无法获取所需资源,导致的上下文切换。比如,IO、内存等系统资源不足时,就会发生自愿上下文切换。
  • nvcswch  表示每秒非自愿上下文切换的次数。 指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在抢占 CPU 时,就容易发生非自愿上下文切换。

默认 pidstat 显示进程的指标数据,加上 -t 参数后,才会输出线程的指标 。

​通过 /proc/interrupts来读取中断的使用情况,通过运行下面的命令: 

# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
... ...
           CPU0       CPU1
  0:         82          0   IO-APIC-edge      timer
  1:         10          0   IO-APIC-edge      i8042
  4:        742          0   IO-APIC-edge      serial
  6:          3          0   IO-APIC-edge      floppy
  8:          0          0   IO-APIC-edge      rtc0
  9:          0          0   IO-APIC-fasteoi   acpi
 10:          0          0   IO-APIC-fasteoi   virtio4
 11:         34          0   IO-APIC-fasteoi   uhci_hcd:usb1
 12:         15          0   IO-APIC-edge      i8042
 14:          0          0   IO-APIC-edge      ata_piix
 15:   28445440          0   IO-APIC-edge      ata_piix
 24:          0          0   PCI-MSI-edge      virtio2-config
 25:   17640933   53622308   PCI-MSI-edge      virtio2-req.0
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部