什么是cpu上下文切换
cpu上下文切换就是先把一个任务的cpu上下文保存起来,然后加载新任务的上下文到寄存器和程序计数器中,然后在跳转到程序计数器所指的新位置,运行新任务。
上下文就是指某一个时间点cpu寄存器和程序计数器的内容。一般来说,CPU的上下文切换可以分为三大类:
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
进程的上下文切换
linux按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着CPU的特权等级ring0和ring3。内核空间ring0具有最高权限,可以直接访问所有资源;用户空间ring3只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
<br />进程既可以在用户空间运行,又可以在内核空间运行。进程在用户空间运行,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态度。
系统调用
从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成;首先调用open()打开文件,然后调用read()读文件,并调用write()将内容写到标准输出,最后再调用close()关闭文件。
在这个过程中就发生了CPU的上下文切换,整个过程是这样的:
- 保存CPU寄存器里原来用户态的指令位
- 为了执行内核态的代码。CPU寄存器需要更新为内核态指令的新位置
- 跳转到内核态运行的内核任务
- 当系统调用结束后,CPU寄存器需要回复原来保存的用户态,然后再切换到用户空间,继续运行进程
所以。一次系统调用的过程,其实是发生了两次CPU的上下文切换(用户态-内核态-用户态)
不过,需要注意的是,系统调用的过程中,并不会涉及到虚拟内存等进程用户态度的资源,也不会切换进程。
进程上下文切换和系统调用的区别
首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程上下文不仅包括虚拟内存等用户空间的资源,还包括内核栈,寄存器,程序计数器等内核空间的状态。
因此,进程的上下文切换就比系统调用多了一步;在保存的内核态资源(当前进程的内核状态和CPU寄存器)之前,需要先把该进程的用户态资源保存下来;而加载了下一进程的内核态后,还需要刷新新进程的虚拟内存。
发生进程上下文切换的场景
- 为了保证所有进程可以得到公平调度,CPU时间被划分为一段段的时间片,这些时间片再被流转分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统中断,切换到其它正在等待CPU的进程运行。
- 进程在系统资源不足,比如说内存不足时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其它进程运行。
- 当进程通过睡眠函数sleep这样的方法将自己主动挂起时候,自然也会重新调度
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行。当前进程会被挂起,由高优先级进程来运行
- 发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序
线程上下文切换
线程和进程最大区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。说白了,所谓内核中的任务调度,实际上的调度对象是线程。而进程只是给线程提供了共享虚拟内存。
所以,对于线程和进程,我们可以这么理解;当进程只有一个线程时,可以认为进程等于线程。当进程拥有多个线程时,这些线程会共享相同的虚拟内存等资源,这些资源在上下文切换时不需要修改。需要交换的只是用户线程的栈。
中断上下文切换
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其它进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
