先引用一篇博文,再总结一下相关问题。
所谓上下文切换,实质就是寄存器堆的切换过程。这其中一部分需要硬件来切换,一部分需要软件来处理。
当在用户空间发生中断时,首先由 x86 CPU 从硬件角度进行处理,然后才是 linux 内核的处理。当中断处理完毕,返回到用户空间时,最后的步骤也是交给 CPU 硬件来处理的。
1、 X86 CPU 对中断的硬件支持
CPU 从中断控制器取得中断向量
根据中断向量从 IDT 中找到对应的中断门
根据中断门,找到中断处理程序
在进入中断处理程序前,需要将堆栈切换到内核堆栈。也就是将 TSS 中的 SS0、ESP0装入SS、ESP
然后将原来的用户空间堆栈(SS, ESP)、EFLAGS、返回地址(CS, EIP)压入新的堆栈。
以上这一系列动作都由硬件完成
最后,才进入中断处理程序,接下来,由 linux 内核处理
2、 Linux 内核对中断的处理
保存中断来源号
调用 SAVE_ALL,保存各种寄存器
将 DS、ES 指向 __KERNEL_DS
将返回地址 ret_from_intr入栈
调用 do_IRQ进行中断处理
中断处理完毕,返回到 ret_from_intr
3、 ret_from_intr
所有的中断处理程序在处理完之后都要走到这里;
判断进入中断前是用户空间还是系统空间
如果进入中断前是系统空间,则直接调用 RESTORE_ALL
如果进入中断前是用户空间,则可能需要进行一次调度;如果不调度,则可能有信号需要处理;最后,还是走到 RESTORE_ALL
RESOTRE_ALL 和 SAVE_ALL 是相反的操作,将堆栈中的寄存器恢复
最后,调用 iret 指令 ,将处理权交给 CPU
4、 iret 指令使 CPU 从中断返回
此时,系统空间的堆栈和CPU在第1步处理完之后,交给 linux 内核时的情形是一样的,也就是保存着用户空间的返回地址(CS、EIP)、EFLAGS、用户空间的堆栈(SS、ESP)。
CPU将 CS、EIP、EFLAGS 、SS、ESP恢复,从而返回到用户空间。
总结一下:
一个CPU同一时刻只能在用户空间和内核空间的一个中运行。在CPU运行在用户空间的过程中,可能以以下两种方式陷入内核 1)系统调用:这其实是一种同步中断,也称“软件中断”(注意,不是软中断),或者称为“异常”,通过int指令来实现;陷入内核后,内核代码运行在进程上下文中。2)中断(也称异步中断),是由I/O设备产生的中断,它会触发中断服务例程的执行,并往往伴随着软中断的执行,此时,CPU运行在中断上下文中。上文中描述了CPU从中断返回用户空间的过程。
当内核即将返回用户空间时,内核会检查need_resched是否设置,如果设置,则调用schedule(),此时,发生用户抢占。一般来说,用户抢占发生几下情况:
(1)从系统调用返回用户空间;
(2)从中断(异常)处理程序返回用户空间。
与用户抢占相对应的是内核抢占,在内核2.6版本以前并不支持内核抢占,现在我们用的默认一般都是抢占式内核,这可以通过编译时设定编译选项来选择是否使用抢占式内核。对于非内核抢占系统,内核代码可以一直执行,直到完成,也就是说当进程处于内核态时,是不能被抢占的。而对于抢占式内核,在硬中断返回,软中断重新使能,解锁函数,以及开启了内核抢占的系统调用中等一系列情况下都可能发生抢占和CPU的重新调度。详细内容见下一篇博客。