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内核设计的艺术