move_to_user_mode

move_to_user_mode

Content #

在进程0正式创建进程1之前,要将进程0由0特权级转变为3特权级。方法是调用 move_to_user_mode()函数,模仿中断返回动作,实现进程0的特权级从0转变为3。 //代码路径:include/system.h:

#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)
            "movw %%ax,%%ds\n\t" \      //设置为用户数据段选择子0x17,
            "movw %%ax,%%es\n\t" \      //确保用户程序正确访问数据段。
            "movw %%ax,%%fs\n\t" \
            "movw %%ax,%%gs" \
        :::"ax")

IA-32体系结构翻转特权级的方法之一是用中断。当CPU接到中断请求时,能中断当前程序的执行序,将CS:EIP切换到相应的中断服务程序去执行,执行完毕又执行iret指令返回被中断的程序继续执行。

栈中的SS值是0x17,用二进制表示就是00010111,最后两位表示3,是用户特权级,倒数第3位是1,表示从LDT中获取段描述符,第4~5位的10表示从LDT的第3 项中得到进程栈段的描述符。 进程0中GDT、LDT、TSS关系示意图

iret从栈中依次弹出EIP、CS、EFLAGS、ESP、SS,将CPU切换至用户模式。

中断与函数调用不同的是,函数调用是程序员事先设计好的,知道在代码的哪个地方调用,编译器可以预先编译出压栈保护现场和出栈恢复现场的代码;而中断的发生是不可预见的,无法预先编译出保护、恢复的代码,只好由硬件完成保护、恢复的压栈、出栈动作。所以,int指令会引发CPU硬件完成SS、ESP、EFLAGS、 CS、EIP的值按序进栈,同理,CPU执行iret指令会将栈中的值自动按反序恢复给这5个寄存器。

From #

Linux内核设计的艺术

段选择子格式