2.14 进程0由0特权级翻转到3特权级,成为真正的进程
Linux操作系统规定,除进程0之外,所有进程都要由一个已有进程在3特权级下创建。在Linux 0.11中,进程0的代码和数据都是由操作系统的设计者写在内核代码、数据区,并且,此前处在0特权级,严格说还不是真正意义上的进程。为了遵守规则,在进程0正式创建进程1之前,要将进程0由0特权级转变为3特权级。方法是调用move_to_user_mode()函数,模仿中断返回动作,实现进程0的特权级从0转变为3。
执行代码如下:
//代码路径:init/main.c:
void main(void)
{
…
move_to_user_mode();
…
}
//代码路径:include/system.h: //参看1.3.4节
#define move_to_user_mode() \ //模仿中断硬件压栈,顺序是ss、esp、eflags、cs、eip
__asm__("movl %%esp,%%eax\n\t" \
"pushl $0x17\n\t" \ //SS进栈,0x17即二进制的10111(3特权级、LDT、数据段)
"pushl %%eax\n\t" \ //ESP进栈
"pushfl\n\t" \ //EFLAGS进栈
"pushl $0x0f\n\t" \ //CS进栈,0x0f即1111(3特权级、LDT、代码段)
"pushl $1f\n\t" \ //EIP进栈
"iret\n" \ //出栈恢复现场、翻转特权级从0到3
"1:\tmovl $0x17,%%eax\n\t" \ //下面的代码使ds、es、fs、gs与ss一致
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::»ax»)
IA-32体系结构翻转特权级的方法之一是用中断。第1章介绍过,当CPU接到中断请求时,能中断当前程序的执行序,将CS:EIP切换到相应的中断服务程序去执行,执行完毕又执行iret指令返回被中断的程序继续执行。
这期间,CPU硬件还做了两件事:一件是硬件保护现场和恢复现场,另一件是可以翻转特权级。
从代码的执行序上看,中断类似函数调用,都是从一段正在执行的代码跳转到另一段代码执行,执行之后返回原来的那段代码继续执行。为了保证执行完函数或中断服务程序的代码之后能准确返回原来的代码继续执行,需要在跳转到函数或中断服务程序代码之前,将起跳点的下一行代码的CS、EIP的值压栈保存,保护现场。函数或中断服务程序的代码执行完毕,再将栈中保存的值出栈给CS、EIP,此时的CS、EIP指向的就是起跳点的下一行,恢复现场。所以,CPU能准确地执行主调程序或被中断的程序。实际需要保护的寄存器还有EFLAGS等。
中断与函数调用不同的是,函数调用是程序员事先设计好的,知道在代码的哪个地方调用,编译器可以预先编译出压栈保护现场和出栈恢复现场的代码;而中断的发生是不可预见的,无法预先编译出保护、恢复的代码,只好由硬件完成保护、恢复的压栈、出栈动作。所以,int指令会引发CPU硬件完成SS、ESP、EFLAGS、CS、EIP的值按序进栈,同理,CPU执行iret指令会将栈中的值自动按反序恢复给这5个寄存器。
CPU响应中断的时候,根据DPL的设置,可以实现指定的特权级之间的翻转。前面的sched_init函数中的set_system_gate(0x80,&system_call)就是设置的int 0x80中断由3特权级翻转到0特权级,3特权级的进程做了系统调用int 0x80,CPU就会翻转到0特权级执行系统代码。同理,iret又会从0特权级的系统代码翻转回3特权级执行进程代码。
move_to_user_mode()函数就是根据这个原理,利用iret实现从0特权级翻转到3特权级。
由于进程0的代码到现在一直处在0特权级,并不是从3特权级通过int翻转到0特权级的,栈中并没有int自动压栈的5个寄存器的值。为了iret的正确使用,设计者手工写压栈代码模拟int的压栈,当执行iret指令时,CPU自动将这5个寄存器的值按序恢复给CPU,CPU就会翻转到3特权级的段,执行3特权级的进程代码。
为了iret能翻转到3特权级,不仅手工模拟的压栈顺序必须正确,而且SS、CS的特权级还必须正确。注意:栈中的SS值是0x17,用二进制表示就是00010111,最后两位表示3,是用户特权级,倒数第3位是1,表示从LDT中获取段描述符,第4~5位的10表示从LDT的第3项中得到进程栈段的描述符。
当执行iret时,硬件会按序将5个push压栈的数据分别出栈给SS、ESP、EFLAGS、CS、EIP。压栈顺序与通常中断返回时硬件的出栈动作一样,返回的效果也是一样的。
执行完move_to_user_mode( ),相当于进行了一次中断返回,进程0的特权级从0翻转为3,成为名副其实的进程。