内核态和用户态的切换:
用户态到内核态的转换:1、进行系统调用,2、异步中断,3、外部硬件中断
检查特权级别的变化:当异常发生在用户态,而异常处理函数则必须运行在内核态,则此时必须调用内核态的堆栈(系统调用必然是发生特权级的变化),步骤是,将进程的TSS段中的esp0和ss0赋值给esp,ss寄存器
于是乎,当进程由用户态进入内核态时,必发生中断,因为内核态的CPL优先级高,所以要进行栈的切换。那么就会读tr寄存器以访问该进程(现在还是用户态)的TSS段。随后用TSS中内核态堆栈段ss0和栈指针esp0装载SS和esp寄存器,这样就实现了用户栈到内核栈的切换了。同时,内核用一组mov指令保存所有寄存器到内核态堆栈上,这也包括用户态中ss和esp这对寄存器的内容。
最后将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
进程切换:
这里再次强调一下,每个进程都有一个thread_struct结构的字段thread,用于保留进程的一部分硬件上下文:
这里再次强调一下,每个进程都有一个thread_struct结构的字段thread,用于保留进程的一部分硬件上下文: struct thread_struct { struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES]; unsigned long esp0; unsigned long sysenter_cs; unsigned long eip; unsigned long esp; unsigned long fs; unsigned long gs; unsigned long debugreg[8]; unsigned long cr2, trap_no, error_code; union i387_union i387; struct vm86_struct __user * vm86_info; unsigned long screen_bitmap; unsigned long v86flags, v86mask, saved_esp0; unsigned int saved_fs, saved_gs; unsigned long *io_bitmap_ptr; unsigned long io_bitmap_max; };
待会我们就将看到,其实里边最有用的就是eip和esp两个字段,分别表示保存的指令和堆栈栈顶的偏移地址。但这里不包括ss寄存器的值,因为所有的切换都在内核态,那么只用为内核态的堆栈基址ss0保存一次就行了,你们这些进程切来切去,我都不便应万变,把它保存在每个CPU所对应的那个TSS段中。这里也得出了一个很重要的结论,为了提高效率,所有内核态的进程堆栈段基地址都是相同的。
thread字段存放的都是内核栈的esp和eip,然后_switch_to()宏,此时已经进入要切换进来进程的内核栈中,把已经切换进来的进程的thread.esp0装入本地cpu的esp0字段
切换前:被切换进程的内核栈,压入ebp,eflag,然后把被切换进程内核栈的esp赋值给本进程的thread.esp。
切换进来的进程,把本进程的thread.ESP赋值给esp,此时便切换到本进程中,但是局部变量没有完成切换,(向prev->thread.eip存入标记为1的地址。当被替换的进程重新恢复执行时,进程执行我们下面标记为1的那条指令:)这里的prev变量依旧属于被切换的进程。
5. 宏把next->thread.eip的值(绝大多数情况下是上面所述标记为1的地址)压入next的内核栈:
pushl next->thread.eip
注意体会,当next执行完了以后的函数后,会回到这个栈的位置,执行eip对应的那条指令。
6. 跳到__switch_to()函数:
jmp __switch_to
7. 如干程序执行后,当A将再次获得CPU时,它执行一些保存eflags和ebp寄存器内容内容的指令,这两条指令的第一条指令被标记为1:
1:
popl %ebp
popfl