一、什么是cpu上下文切换

  cpu上下文切换就是先把前一个任务的cpu上下文保存起来,然后加载新任务的上下文到寄存器和程序计数器中,最后再跳转到程序计数器所指的新位置,运行新任务。

• 那么是如何切换的呢?

  首先我们要知道上下文是什么,上下文是指某一时间点cpu寄存器和程序计数器的内容。寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂存指令、数据和地址的,也可以理解为CPU 内部的数量较少但是速度很快的内存。程序计数器(pc)则是用来存放下一条指令的地址。

  如何切换,就是先把前一个任务的cpu上下文保存到内存中,然后在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复,随后跳转到程序计数器所指向的位置,以恢复该任务或者运行新任务。

  一般来说,CPU上下文切换可以分为三大类
    • 进程上下文切换
    • 线程上下文切换
    • 中断上下文切换

二、 进程上下文切换

 进程上下文就是从一个进程切换到另一个进程运行。
 要明白进程上下文,我们要明白几个概念:


• 什么是特权模式切换

  就是进程从用户态切换到内核态。

  对于x86的cpu而言,一共有0-3四个特权级,0级最高,3级最低。Linux操作系统中主要采用了0和3两个特权级,可分为内核态和用户态。
   ◇ 内核态:对应0级,拥有最高权限,可以直接使用所有资源。
   ◇ 用户态:对应3级,只能使用受限资源。

  一般而言,进程大多数时候是运行在用户态的,当进程想要使用用户态不能使用的资源,就要通过中断信号触发系统调用陷入内核态来运行部分内核的代码去访问使用资源。


• 系统调用是什么

  是指运行在用户态的进程向操作系统内核请求需要更高权限运行的服务。系统调用提供了用户进程与操作系统之间的接口,可以说系统调用是用户态和内核态的接口。

  系统调用需要从用户态陷入内核态,处理完后,又需要返回用户态,肯定是要消耗cpu时间的。不比进程上下文切换,因为系统调用是在一个进程中运行,首先cpu寄存器肯定是要保存的,同时要知道用户态和内核态使用的是不同的栈(对应是用户栈和内核栈),那么就要进行栈的切换,也是要保存的。总体来说系统调用消耗的cpu时间是可接受范围内,相比其它操作系统,linux在上下文切换和模式切换上消耗的时间是非常少的。

  回到进程上下文切换,上面提到了保存,那么进程上下文切换要保存又要什么。首先上下文切换只能发生在内核态中,那么就要保存内核栈和上下文内容等当前进程内核空间状态。不同于系统调用不涉及进程改变,这里还要保存虚拟内存和用户栈等用户空间的资源。同时切换到下一进程后,还需要刷新进程的虚拟内存和用户栈。

  说到刷新虚拟内存,这里就要涉及到一个东西就是TLB(Translation Lookaside Buffer)


• TLB是什么

  TLB是一种高速缓存,内存管理硬件使用它来改善虚拟地址到物理地址的转换速度,就像域名和ip间的映射关系,输入IP麻烦,而输入域名就很方便。

  要知道进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,那么虚拟地址转换为物理地址就会变慢。

  当处理器在获取指令或者读取数据的时候,每当遇到一个虚拟地址,会首先通过TLB等地址翻译缓存机制查找该虚拟地址对应的物理地址,如果没有找到对应的条目,则通过内存翻译硬件机制MMU通过走页表的方式查找相应的物理地址,当得到确切的物理地址之后,则会通过cache找到物理地址对应的内存数据,如果cache中没有相应的数据,再去内存中读取。


• cpu时间片

  当多个进程竞争cpu的时候,cpu为了保证每个进程能公平调度运行,把CPU运行时间划分成若干个时间片,分配给了各个进程,即该进程允许运行的时间,每个进程轮流使用,使各个进程从表面上看是并行的,其实只是并发。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程;如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。

  根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。

  •   那么什么时候才会发生进程上下文切换
        ◇ 进程执行完毕
        ◇ 时间片耗尽
        ◇ 进程在系统的资源不足
        ◇ 进程通过睡眠函数  sleep 将自己主动挂起时
        ◇ 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先
        级进程来运行。

三、 线程上下文切换

线程上下文切换就是从一个线程切换到另一个线程运行。


• 线程和进程的区别

  进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。进程有自己的独立虚拟地址空间,而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

  所谓内核中的任务调度,实际上的调度对象是线程,而进程只是给线程提供了虚拟内存、全局变量等资源。

   线程上下文切换可分为两种情况
        •  两个线程属于不同进程,此时线程上下文切换相当于进程上下文切换。
        •  两个线程属于同一个进程,因为在同一个的进程下,虚拟内存、全局变量和堆栈等资源是共享
        的,只需要保存切换线程自身的私有数据、栈、cpu寄存器和程序计数器等。

四、 中断上下文切换

  为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。

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

发表评论

验证码: + 51 = 61