聊聊Linux中CPU上下文切换
来源:良许Linux教程网
时间:2025-01-08 14:18:57 181浏览 收藏
来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习文章相关编程知识。下面本篇文章就来带大家聊聊《聊聊Linux中CPU上下文切换》,介绍一下,希望对大家的知识积累有所帮助,助力实战开发!
在操作系统中,特别是在 Linux 这样的多任务操作系统中,CPU 上下文是一个重要的概念。多任务操作系统允许多个进程在一个 CPU 上运行,这些进程之间相互独立,互不干扰,给用户造成了多任务“同时运行”的错觉。实际上,操作系统会在很短的时间内让 CPU 在各个任务之间轮流执行,从而给用户创造出多任务“同时运行”的假象。
在每次 CPU 执行任务之前,必须确定从哪里加载任务,以及加载后从哪里开始运行。为了实现这一点,操作系统通过 CPU 中的寄存器和程序计数器来保存和恢复任务的执行进度信息。
CPU 寄存器是 CPU 内部的高速缓存,存储着非常快速的内存数据;而程序计数器则记录了 CPU 正在执行或即将执行的指令位置。
在任务调度过程中,这些关键信息都保存在 CPU 的寄存器中。其中,下一条即将执行的指令地址保存在程序计数器中。这些信息的集合被称为 CPU 上下文,有时也被称为硬件上下文。
当某个进程主动放弃 CPU 时间,或者系统分配的时间片用完时,就会发生 CPU 上下文切换。
CPU上下文切换
操作系统OS在切换运行任务时,将上一任务的上下文保存起来,然后加载新任务的上下文到CPU寄存器,最后再跳转到程序计数器所指的新位置上执行新任务
的这一动作,被称为CPU上下文切换。
CPU上下文切换的步骤:
- 将前一个 CPU 的上下文(也就是 CPU 寄存器和程序计数器里边的内容)保存起来;
- 然后加载新任务的上下文到寄存器和程序计数器;
- 最后跳转到程序计数器所指的新位置,运行新任务。
- 被保存起来的上下文会存储到系统内核中,等待任务重新调度执行时再次加载进来。
CPU 的上下文切换分三种:进程上下文切换、线程上下文切换、中断上下文切换
。
上一任务的CPU上下文保存在哪?
我们知道因为CPU过于昂贵,其性能与其他储存设备有数量级的差距,为了充分压榨其性能,计算机将CPU的时间进行分片,让各个程序在CPU上轮转执行,被剥夺执行权的程序,等后面CPU继续执行它的时候,这时需要一个数据结构来保存相关信息,以便之后恢复继续执行,这个其实就是进程。
CPU上下文会被保存在进程的内核空间(kernel space)上。OS在给每个进程分配虚拟内存空间时,会分配一个内核空间,这部分内存只能由内核代码访问。OS在切换CPU上下文前,会先将当前CPU的通用寄存器、PC等进程现场信息保存在进程的内核空间上,待下次切换时,再取出重新装载到CPU上,以恢复任务的运行。
进程上下文切换
内核空间和用户空间
我们知道为了限制不同的指令的访问能力,提升安全,Linux 按照特权等级,把进程的运行空间分为内核空间
和用户空间
。进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态
,而陷入内核空间的时候,被称为进程的内核态
。
- 内核空间(Ring 0):具有最高权限,可以直接访问所有资源(读取文件)
常见的内核操作:分配内存、IO操作、创建子进程……
- 用户空间(Ring 3):只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用进入到内核中,才能访问这些特权资源
常见的用户态空间程序:数据库、web服务器、shell脚本、Java程序或者其他常见语言的程序……
我们一起看下Linux整体架构图:
top命令查看CPU资源
在linux系统使用top
命令查看cpu时,能看到用户态和内核态占用的cpu资源
其中各项数据表示内容:
us | 用户空间占用CPU百分比 |
---|---|
sy | 内核空间占用CPU百分比 |
ni | 用户进程空间内改变过优先级的进程占用CPU百分比 |
id | 空闲CPU百分比 |
wa | 等待输入输出的CPU时间百分比 |
hi | 硬件中断 |
si | 软件中断 |
st | 实时 |
系统调用
对于一个进程来说,比如web服务的进程,一般是运行在用户态的,但是当需要访问内存、磁盘等硬件设备的时候需要先进入到内核态中,也就是从用户态到内核态的转变,而这种转变需要借助系统调用来实现。系统调用是内核向用户进程提供服务的唯一方法。
比如查看文件时,需要执行多次系统调用:open()打开文件,read()读取文件内容,write()将文件内容输出到控制台,最后close()关闭文件等。系统调用的过程如下:
- 把 CPU 寄存器里原来用户态的指令位置保存起来;
- 为了执行内核代码,CPU 寄存器需要更新为内核态指令的新位置,最后跳转到内核态运行内核任务;
- 系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程;
我们可以发现一次系统调用的过程,其实是发生了两次 CPU 上下文切换
(用户态-内核态-用户态)。
需要注意的是:系统调用过程中,不涉及虚拟内存等进程用户态的资源,也不会切换进程,也就是系统调用过程中一直是同一个进程在运行。系统调用过程也通常称为特权模式切换。
进程上下文切换 和 系统调用的区别?
- 进程上下文切换是指,从一个进程切换到另一个进程;系统调用过程一直是同一个进程在运行,属于进程之内的上下文切换
需要注意的是:进程是由内核来管理和调度的,进程的切换只能发生在内核态,保存上下文和恢复上下文的过程并不免费,需要消耗一定资源
- 进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。而系统调用这里没有涉及到虚拟内存等这些进程用户态的资源
- 因此进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
进程切换的常见场景
进程切换时需要切换上下文,换句话说,只有在进程调度的时候,才需要切换上下文。Linux 为每个 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。进程切换的场景有:
- 进程时间片耗尽,为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。
- 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
- 进程通过睡眠函数 sleep 主动把自己挂起,CPU会重新调度;
- 当有CPU发现优先级更高的进程运行时,为了去运行高优先级进程,当前进程会被挂起;
- 发生硬中断,CPU 上的进程会被挂起,然后去执行内核中的中断服务进程。
线程上下文切换
对操作系统来说,进程是资源分配的基本单位,而线程则是任务调度的基本单位。内核中的任务调度实际是在调度线程,进程只是给线程提供虚拟内存、全局变量等资源。线程上下文切换时,共享相同的虚拟内存和全局变量等资源不需要修改。而线程自己的私有数据,如栈和寄存器等,上下文切换时需要保存。
关于进程和线程的区别:
- 当进程中只有一个线程时,可以认为进程就等于线程。
- 当进程拥有多个线程时,这些线程会共享父进程的资源(即共享相同的虚拟内存和全局变量等资源)。这些资源在上下文切换时是不需要修改的。
- 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
因此线程上下文切换有两种情况:
- 前后两个线程属于不同进程,因为资源不共享,所以切换过程就跟进程上下文切换是一样的;
- 前后两个线程属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。
中断上下文切换
上下文切换有时也因硬件中断而触发。硬件中断是指硬件设备(如键盘、鼠标、调试解调器、系统时钟)给内核发送的一个信号,该信号表示一个事件(如按键、鼠标移动、从网络连接接收到数据)发生了。
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,然后调用中断处理程序,响应设备事件。在打断其他进程时,需要先将进程当前的状态保存下来,等中断结束后,进程仍然可以恢复回来。
跟进程上下文不同,中断上下文切换不涉及进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,只包括内核态中断服务程序执行所必需的状态,也就是 CPU 寄存器、内核堆栈、硬件中断参数等。
中断上下文切换并不涉及到进程的用户态。所以即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必须的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换不会与进程上下文切换同时发生。并且,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便可以尽快完成。
上下文切换的消耗
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
根据Tsuna的测试报告,每次上下文切换都需要几十纳秒到数微妙的CPU时间,这个时间还是相当可观的。不管是哪种场景导致的上下文切换,你都应该知道:
- CPU上下文切换,是保证Linux系统正常工作的核心功能,一般情况下不需要开发人员特别关注。
- 但过多的上下文切换,会把CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,耗费大量的 CPU,甚至严重降低系统的整体性能。
补充:vmstat命令查看整体CPU上下文切换情况
上面已经介绍到CPU上下文切换分为进程上下文切换、线程上下文切换、中断上下文切换,那么过多的上下文切换会把CPU的时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成为系统性能大幅下降的一个因素
所以我们可以使用vmstat这个工具来查询系统的上下文切换情况,vmstat是一个常用的系统性能分析工具,可以用来分析CPU上下文切换和中断的次数 执行如下的命令:vmstat 5
(每隔5s输出一组数据)
该命令输出信息中,各个字段以及含义:
procs:procs 中有 r 和 b 列,它报告进程统计信息。在上面的输出中,在运行队列(r)中有两个进程在等待 CPU 并有零个休眠进程(b)。通常,它不应该超过处理器(或核心)的数量,如果你发现异常,最好使用 top 命令进一步地排除故障。
- r:等待运行的进程数。
- b:休眠状态下的进程数。
memory:memory 下有报告内存统计的 swpd、free、buff 和 cache 列。你可以用 free -m 命令看到同样的信息。在上面的内存统计中,统计数据以千字节表示,这有点难以理解,最好添加 M 参数来看到以兆字节为单位的统计数据。
- swpd:使用的虚拟内存量。
- free:空闲内存量。
- buff:用作缓冲区的内存量。
- cache:用作高速缓存的内存量。
- inact:非活动内存的数量。
- active:活动内存量。
swap:swap 有 si 和 so 列,用于报告交换内存统计信息。你可以用 free -m 命令看到相同的信息。
- si:从磁盘交换的内存量(换入,从 swap 移到实际内存的内存)。
- so:交换到磁盘的内存量(换出,从实际内存移动到 swap 的内存)。
I/O:I/O 有 bi 和 bo 列,它以“块读取”和“块写入”的单位来报告每秒磁盘读取和写入的块的统计信息。如果你发现有巨大的 I/O 读写,最好使用 iotop 和 iostat 命令来查看。
- bi:从块设备接收的块数。
- bo:发送到块设备的块数。
system:system 有 in 和 cs 列,它报告每秒的系统操作。
- in:每秒的系统中断数,包括时钟中断。
- cs:系统为了处理所以任务而上下文切换的数量。
CPU:CPU 有 us、sy、id 和 wa 列,报告(所用的) CPU 资源占总 CPU 时间的百分比。如果你发现异常,最好使用 top 和 free 命令。
- us:处理器在非内核程序消耗的时间。
- sy:处理器在内核相关任务上消耗的时间。
- id:处理器的空闲时间。
- wa:处理器在等待IO操作完成以继续处理任务上的时间。
补充:pidstat命令查看进程的CPU上下文切换情况
执行如下的命令:pidstat
,查看进程的CPU上下文切换情况 如果没有安装,yum install sysstat
安装即可
在结果中你能看到如下内容:
- PID – 被监控的任务的进程号
- %usr – 当在用户层执行(应用程序)时这个任务的cpu使用率,和 nice 优先级无关。注意这个字段计算的cpu时间不包括在虚拟处理器中花去的时间。
- %system – 这个任务在系统层使用时的cpu使用率。
- %guest – 任务花费在虚拟机上的cpu使用率(运行在虚拟处理器)。
- %CPU – 任务总的cpu使用率。在SMP环境(多处理器)中,如果在命令行中输入-I参数的话,cpu使用率会除以你的cpu数量。
- CPU – 正在运行这个任务的处理器编号。
- Command – 这个任务的命令名称。
参考资料:
《Linux内核设计与实现》
《Linux性能优化实战》
http://ifeve.com/context-switch-definition https://www.it610.com/article/1289356670568308736.htm
终于介绍完啦!小伙伴们,这篇关于《聊聊Linux中CPU上下文切换》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
500 收藏
-
322 收藏
-
480 收藏
-
454 收藏
-
499 收藏
-
309 收藏
-
195 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习