目录
1.CPU的工作过程
2.寄存器
3.CPU的上下文
4.系统调用
5.CPU的态
1.CPU的工作过程
CPU要执行的指令的地址存在寄存器中,指令存放在内存中,而CPU本质上就是一个去内存中根据地址取指令,然后执行指令的硬件。
举一个例子:
例如PC寄存器中存放50,CPU读到存放的50,发出一条取址指令,去取出地址为50的内存单元中的指令,再传回给CPU。
2.寄存器
众所周知,为了配平CPU和内存之间速率的差距,CPU和内存之间存在着一个由寄存器组成的中间层,寄存器种会存放着CPU接下来要执行的指令,以及后续可能要执行到的指令以及可能要用到的数据。只有预先装载进去这部分可能要用到的东西才能抹平CPU和内存之间的速率差距,不然每次都要去内存取内容,可能是会拉低CPU的效率的。
但该预先装载哪些内容进寄存器种喃?这里遵循了程序的局部性原理。
程序的局部性原理:
程序在执行的时候呈现出局部性规律,在一段时间内,整个程序的执行仅限于程序中的某一个部分,相应的,执行所访问的存储空间也局限于某个内存区域。局部性又分为时间局部性和空间局部性。时间局部性指的是,如果程序中的某条指令一旦执行,则不久后可能会被再次执行,执行指令时访问的数据单元在不久后会被再次访问。空间局部性指的是,一旦访问了某个存储单元,不久后,其附近的存储单元也将被访问。
3.CPU的上下文
上文聊过,为了抹平内存和CPU之间的速率差,给CPU配备了寄存器。寄存器中存储着当前执行的指令、数据、以及下一条指令在内存中的地址等等事关程序正常运行的关键信息。所以寄存器中存储的内容合称为CPU的上下文。
4.系统调用
系统中将一些对系统级别资源的调用封装成了一个个函数,称为系统调用,常见的系统调用有很多,比如IO操作就是个系统调用。
5.CPU的态
操作系统在启动后,内存被分为两部分(两段):
- 内核段从0地址开始编址,存放操作系统程序,里面包含系统调用。
- 用户段,从内核段之后开始编址,存放用户程,也就是各个进程的数据和指令。
由于内核段存放的是系统相关的内容,基于安全的考虑,肯定是不允许被CPU随意访问的,需要特权才行。因此将CPU的权限设计为了两种状态:
- 用户态,只能访问用户段
- 内核态,能访问用户段和内核段
所谓的态就是能访问用户段的上下文以及能访问内核段的上下文。当我们调用系统调用的时候会引起上下文的切换,也就是CPU态的切换。上下文切换的意思是,先把前一个任务的 CPU 上下文保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。之所以会切换上下文,这是因为寄存器加载数据和指令的时候遵照了程序的局部性原理。CPU访问用户段时,寄存器里预加载的是用户段的资源;CPU访问内核段时,寄存器里预加载的是内核段的资源。
所以CPU进行态切换的时候,上下文一定会完全换一套的。总的来说为了保证多数情况下程序执行的效率,“局部性原理”是必须存在的,为了内核的安全,CPU态的划分是必须存在的。所以,CPU上下文切换是不得不接受的一种代价。
CPU的上下文切换是种耗时的操作:
-
寄存器保存和恢复:在上下文切换过程中,需要保存当前任务的寄存器状态,并恢复下一个任务的寄存器状态。寄存器保存和恢复涉及将寄存器的值从CPU保存到内存(或者栈)中,以及从内存中恢复到CPU中。这涉及到数据的读写和复制操作,会引入一定的延迟和开销。
-
内存刷新和缓存失效:上下文切换可能涉及刷新CPU缓存和内存管理单元(MMU)的操作。当切换到一个新的任务时,之前的任务的缓存内容可能需要刷新,新任务的页表和内存映射需要加载和设置,这些操作可能导致缓存失效和内存访问延迟。
-
上下文数据复制:在上下文切换过程中,需要将当前任务的上下文数据保存到内存中,同时从内存中加载下一个任务的上下文数据。这包括寄存器状态、程序计数器、标志位和其他与任务执行相关的数据。数据的复制和加载需要占用CPU和内存带宽,并引入一定的延迟。
-
任务切换开销:上下文切换不仅仅涉及寄存器和内存的操作,还包括任务切换本身的开销。这包括切换内核栈、更新任务控制块(TCB)、更新调度器数据结构等操作。这些操作可能需要修改内核数据结构,增加了上下文切换的开销。
总的来说CPU上下文切换很耗时,我们常见的就是IO操作、进程切换这些都会引起CPU上下文切换。
为什么上下文切换前要先将前一个任务的上下文保存起来:
这里解答一个问题,就是为什么上下文切换前要先将前一个任务的上下文保存起来,而不是直接保存该任务执行到何处了?其实也是基于性能来考虑的。举个例子:
假设执行三条指令:
ABC
AC,用户级指令
B,内核级指令
执行A时,“局部性原理”驱使寄存器在用户段加载需要的数据以及可能会用到的数据。
执行B时,“局部性原理”驱使寄存器在内核段加载需要的数据以及可能会用到的数据,卸载掉原来的老数据。
执行C时,“局部性原理”驱使寄存器在用户段加载需要的数据以及可能会用到的数据,卸载掉原来的老数据。
根据“局部性原理”,ABC指令是连续执行的,AC之间的数据很可能是共用的,但是由于中间执行B指令,就造成了额外的一次重新寻址加载数据的时间。
所以把前一个不同级别的指令的上下文保存起来是个不错的优化方法,避免了额外的重复寻址,减少了额外的时间开销。