暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

CPU上下文切换

直截了当 2021-06-25
890

一不小心距离上一篇文章已有近一个月的时间了:),主要是最近菜鸟我在忙一件人生的大事,所以拖了这么长时间没更,对不住了您嘞!不过,菜鸟我出品的,必然是精品(吹个小牛,嘿嘿:o!!!)

上一篇文章给大家简单介绍了一下负载以及在Linux中如何查看这些数据,帮助你来判断机器的使用情况。没看过的小伙伴可以回去复习一下,接下来我们继续学习CPU上下文。

我们上一篇文章中讲到,现代操作系统大多是多任务操作系统。直白点讲就是,我就算只有一个CPU,这个CPU也可以“同时”运行两个任务(例如同时运行微信和钉钉)。当然,这里的“同时”并不是真的同时,CPU也是轮流执行这两个任务的,只是速度太快,在我们人类看来就误以为是同时运行。


任务指的是什么?

答案:这里的任务指的是:进程、线程或是信号触发的中断程序调用

CPU是怎样切换任务的呢?

答案:CPU通过切换CPU上下文切换来调度不同的任务。

那啥又是CPU上下文呢?

答案:CPU在运行一个任务之前,要知道任务从哪里加载、从哪里开始运行,这些数据都存放在CPU寄存器和程序计数器(Program Counter,PC)中。CPU寄存器,是CPU本身含有的容量小,速度快的内存模块。PC是用来存储CPU正在执行的或即将执行的指令的位置。这些数据都是CPU执行一个任务依赖的必要环境,称之为CPU上下文


搞明白了什么是CPU上下文,那么CPU上下文也就一目了然了,就是先把当前任务的CPU上下文保存起来,然后加载即将运行任务的上下文的过程,称为CPU上下文切换。这些保存下来的上下文,将被存储在内核中,在CPU重新执行其对应的任务时,再次加载。这样保证了逻辑执行的连续性。


根据任务的不同,CPU上下文切换也分为:线程上下文切换、进程上下文切换和中断上下文切换

线程上下文切换

线程是调度的基本单位,进程是资源拥有的基本单位(官方文档)用人话讲就是,内核中的任务调度都是对线程的操作;进程只是给线程提供了虚拟内存、全局变量等资源。

菜鸟君提醒:如果你想要彻底搞明白 ”线程是调度的基本单位,进程是资源拥有的基本单位“ 这句话,需要了解计算机系统的虚拟内存机制这又是另一个比较大的话题,我们以后详细讲。如果你迫不及待想要了解,推荐《计算机操作系统》第九章,《虚拟内存》

目前我们可以简单这么理解:

  • 当进程只有一个线时,进程就等于线程
  • 当进程拥有较多的线程时,其中的线程会共享虚拟内存和全局变量等资源,这些资源在线程上下文切换时不需要更新;但是对于线程私有的数据——栈和寄存器以及局部变量等,需要更新。

那么上下文切换就分为两种:

  • 两个线程属于不同的进程,那么和进程上下文切换没有区别(文章稍后涉及)
  • 前后两线程属于同一个进程,上下文切换时,只需要更新私有数据、寄存器等不共享的数据

进程上下文切换

首先按照Linux特权等级,将进程分为内核空间进程和用户空间进程。分别对应下图中的Ring 0 和 Ring 3(这点常年搞逆向的同学肯定很清楚)

  • 内核空间(Ring 0)进程,具有最高权限,可以直接访问所有资源
  • 用户空间(Ring 3)进程,只能访问特定的资源,如需访问内存等硬件设备,需要通过系统调用(System Call)陷入到内核中,才能访问这类特权资源。

这里所谓的陷入到内核中 这一过程就是一次CPU上下文切换。CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。待内核任务执行完成后,CPU需要恢复到用户态,这又是一次CPU上下切换。

菜鸟提醒:系统调用不会涉及虚拟内存等进程用户态资源。该过程始终是同一个进程在运行。所以有些同学可能会讲系统调用过程通常称为特权模式切换,而不是上下文切换,但现实中,CPU上下文切换无法避免。

提醒中讲到,系统调用不涉及进程切换,那么涉及进程切换又是怎样的呢?

: 进程是由内核管理、调度的,所以进程上下文切换只能发生在内核态。进而推出进程上下文不仅包括虚拟内存、栈、全局变量 还包含 内核堆栈、寄存器等内核空间状态。如下图,整个过程比系统调用多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

从上文中我们看出,进程间的CPU上下文切换需要在内核中完成。这一过程需要数十纳秒到数微妙的CPU时间。


如果进程上下文切换次数太多,CPU将大量的时间消耗在寄存器、内核栈以及虚拟内存等资源的保存和恢复上。而真正执行进程逻辑的时间会大比例下降,将导致平均负载升高。

此外Linux通过TLB(Translation Lookaside Buffer)管理管理虚拟内存,(TLB将虚拟内存的最新数据转换存储到物理内存,可以称为地址转换缓存)所以当虚拟内存更新后,TLB也需要刷新,在刷新过程中,内存访问也会变慢。特别是多处理器共享缓存的情况下。


接下来的问题是,进程什么时候发生切换?

Linux系统为每个CPU维护一个就绪的队列,(上一篇文章中我们提到htop/top可以查看这个队列),将活跃进程按照优先级排序,选择优先级最高的进程来运行。

  • 当然CPU并不是执行完一个进程再执行下一个进程。而是将CPU时间划分为一段一段的时间片段。将这些时间片段分配给不同的进程,当一个时间片段耗尽后,其对应的进程就会别挂起,CPU转而执行另一个进程。
  • 当进程所需的资源不足时也会被内核挂起,这是内核调度其他进程让CPU执行
  • 进程也可以通过sleep函数将自己挂起
  • 还有一种情况是,当更高优先级的进程入场时,当前的进程就要被挂起
  • 还有就是硬件中断,信号中断等也可以让进程被挂起或中断(例如Ctrl+C, :))

中断上下文切换

书接上文,我们来看 中断上下文切换。有时候为了对硬件时间做出反应,就需要一个机制来让进程被动地响应硬件事件,这就是中断机制。中断会打断进程的正常调度和执行,让内核转而调用中断处理程序来响应硬件事件。中断处理比进程处理优先级更高

重点来喽:

和进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的 CPU,甚至严重降低系统的整体性能。

和进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。


总结

本文主要讲了:

  • CPU上下文的定义
  • 3中CPU上下文切换的情况

预告

接下来将会出一个小视频,带着大家看看具体用哪些系统工具来查看CPU上下文切换相关的数据,以及这些切换是如何影响系统性能的。


长按关注一下吧


文章转载自直截了当,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论