操作系统特权级
操作系统将特权分为四层,分别为0、1、2、3,0是最高特权级别,3是最低特权级别。特权级别为0一般时由操作系统占有,特权级别1、2是由驱动程序和虚拟机占有,而用户程序一般都在特权级别3的级别下,也就是最低特权级别,能拥有的能力是最低的。
特权级的本质--内核态与用户态
CPU的保护机制
- 分段保护机制:将cr0寄存器的PE位开启时(置1),就开启了保护模式,也即开启了分段保护机制。如果PE=0,则在实模式下运行。
- 分页保护机制:将cr0寄存器的PG位(分页允许位,它表示芯片上的分页部件是否允许工作。)开启时(置1),就开启了分页模式,也即开启了分页保护机制。
段选择子与段描述符
- 段选择子:低端两位,表示 CPL,也就是当前所处的特权级,假如现在CS寄存器的后两位为3,二进制就是11,就表示当前处理器处于用户态。
- 段描述符:段描述符的高4字节的13-14位,表示段描述符特权级,只有想要加载这个段描述符的选择子的请求特权级别高于或者等于DPL才能加载这个段。
绝大多数情况下,要求CPL必须等于DPL,才会跳转成功,否则就会报错,即当前代码所处段的特权级,必须要等于要跳转过去的代码所处的段的特权级,那就只能用户态往用户态跳,内核态往内核态跳,这样就防止了处于用户态的程序,跳转到内核态的代码段。访问内存数据时也会有数据段的特权级检查。最终的效果是,处于内核态的代码可以访问任何特权级的数据段,处于用户态的代码则只可以访问用户态的数据段。
- 段选择子:低端两位,表示 CPL,也就是当前所处的特权级,假如现在CS寄存器的后两位为3,二进制就是11,就表示当前处理器处于用户态。
切换到用户态模式
void main(void)
{
......
move_to_user_mode(); // 移到用户模式下执行
......
}
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \
"pushl $0x17\n\t" \
"pushl %%eax\n\t" \
"pushfl\n\t" \
"pushl $0x0f\n\t" \
"pushl $1f\n\t" \
"iret\n" \
"1:\tmovl $0x17,%%eax\n\t" \
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::"ax")
五次的压栈操作:因为没有真正的产生中断,所以我们需要模拟一次中断,当真实的中断发生时,cpu会执行压栈操作,而中断返回时,CPU又会帮我们把压栈的这些值返序赋值给响应的寄存器。我们在代码中模仿CPU进行了五次压栈操作,这样在执行iretd指令时,硬件会按顺序将刚刚压入栈中的数据,分别赋值给SS,ESP,EFLAGS,CS,EIP这几个寄存器,让其误以为此内核态是通过中断进来的。特权级的转换体现在CS和SS寄存器的值里。
"pushl %%eax\n\t"
:给SS赋值0x00000017即二进制0b10111,最后两位11表示特权级为 3,即用户态。"pushl $0x0f\n\t"
:给CS赋值0x0f即二进制0b1111,最后两位11表示特权级为 3,即用户态。
- iretd指令:中断返回指令,执行后硬件会按顺序将刚刚压入栈中的数据,分别赋值给SS,ESP,EFLAGS,CS,EIP这几个寄存器。iretd之后CPU就去1处执行。所以其实从效果上看,就是顺序往下执行,只不过利用了 iretd做了特权级转换等工作。
Comments | NOTHING