为什么进程需要从用户态进入内核态?
这里我不去细细区分什么是属于内核空间,什么又是属于用户空间。我讲一个逻辑:在用户空间,我们对进程不加限制,想干啥干啥,如果此时所有的资源都处于用户空间中,那么如果进程对那种系统级的资源随意修改,显然极易造成系统的崩溃。但同时又要保证进程有着足够的自由度,不然不利于开发。所以内核空间的开辟就显得极为重要,我们将不能随意修改的资源,放入内核空间中,比并创建一些函数,通过这些定死函数,对资源进行操作(我们可以把这些函数理解为一些具体的规则,为了防止不正规的操作) ,当进程进入内核空间,也只能在一定规则之内比内核资源进行操作)。
用户态进入内核态的三种场景
1、系统调用(软中断)
2、异常
3、硬件中断
系统调用
1、系统调用实际上是发出一个int 80 的软中断,并给出系统调用号
2、内核响应80中断,调用syscall中断处理函数
3、然后调用GET_THREAD_INFO(%ebp), 获得thread_info结构体,就拿到了内核栈空间
4、此时进入内核栈,保存应用程序栈空间,返回时恢复使用
5、后续根据获得的系统调用号,进入不同的处理函数
..从这里我们也可以看出,进程进入内核空间的操作,都是有明确清晰的路径,其并不可以在内核空间中为所欲为,这也保证了,虽说,所有进程共同使用内核空间,但进程在内核空间操作都是通过系统的处理函数,在规则之内进行。这也就保证了操作的安全性
而每一个系统调用背后的功能都涉及到对应的资源管理和进程控制, 这些指令都是一些特权指令, 所以需要进行用户态到内核态的改变
这样可以保证系统的稳定性和安全性,防止用户进行非法操作。
系统调用按功能分配分为如下几种
用户态进入内核态的具体实现
1、用户运行C库函数fork()。函数里面会执行int 0x80指令。
在执行中断指令之前会先把系统调用号保存在eax寄存器中
然后才会去执行int 0x80指令
2、查找中断向量表,找到80号中断对应的中断处理程序
处理器临时保存用寄存器中SS和ESP的值,记作SS_old,ESP_old
从TSS中找到当前程序对应的内核栈加载到寄存器SS和ESP中
3、在内核栈中一次性压入用户状态寄存器SS_old、ESP_old、eflags、cs、ip
进入中断处理程序(切换cs和ip)(完成用户态到内核太切换)
执行中断处理程序(system_call)
检查系统调用号(存于eax中)的有效性
将ds,es,fs,edx,ecx,ebx等寄存器压入内核栈
根据系统调用号(2)从系统调用表上找到系统调用sys_fork()
执行系统调用sys_fork()
将内核栈的数据弹出到ds,es,fs,edx,ecx,ebx等寄存器中(数据寄存器和段寄存器)
中断返回iret
由CPU硬件完成SS_old、ESP_old、eflags、cs、ip弹出到指定寄存器。
总结: 1、通过eax寄存器保存系统调用号,在内核中根据这个系统调用号找到对应的系统调用函数。2、切换前,用户态进程用的所有寄存器都已保存在内核态堆栈,这也包括ss和esp这对寄存器的内容(ss和esp寄存器中的内容决定进程处于用户栈还是内核栈,关于堆栈请看下一节)。3、返回时,将开始保存的各个寄存器进行恢复,包括数据寄存器,段寄存器,状态寄存器,堆栈指针寄存器等
用户栈和内核栈
1、每个进程被创时,
在生成task_struct的同时,生成两个栈,
用户栈,位于用户地址空间;
内核栈,位于内核空间。
当进程在用户地址空间中执行的时候,使用的是用户栈,CPU堆栈指针寄存器中存的是用户栈的地址;
当进程在内核空间执行时,CPU堆栈指针寄存器中放的是内核栈的地址。
2、当位于用户空间的进程系统调用时,它会陷入内核,让内核代其执行。
进程用户栈的地址会被存进内核栈中,CPU堆栈指针寄存器中的内容也会变为内核栈的地址。
系统调用执行完,进程从内核栈找到用户栈地址,继续在用户空间中执行,此时CPU堆栈指针寄存器就变为了用户栈的地址
异常和硬件中断
硬件中断是指外部硬件产生的一个电信号从CPU的引脚进入打断CPU的运行。
异常是指软件运行的过程中,发生一些必须做处理事件,CPU自动产生一个陷入来打断CPU的运行。异常在处理的时候必须考虑与处理器的时钟同步,实际上异常也称为同步中断,在处理器执行到因编译错误而导致的错误指令,或者在执行期间出现特殊错误,必须依靠内核处理的时候,处理器就会产生一个异常
无论是异常,系统调用还是硬件中断,进入如内核的过程可以由一张流程图所示,如下
1、CPU每次执行完一条指令后,总会先检查有无中断请求,无则执行下一条指令,有则在总线上读取中断向量号
2、通过查找IDT表定位到该中断向量号对应的中断描述符。
3、设当前进程的代码段特权级为CPL,找到的中断描述符的特权级为DPLd,中断服务程序所在段的特权级为DPLs,中断的特权级检查有两道关卡,只有通过了这两道检查后才能进入中断服务函数
CPL与DPLs进行比较,若CPL>=DPLs则检查通过,否则异常
CPL与DPLd进行比较,若CPL<=DPLd则检查通过,否则异常
第一次比较一般都能通过,因为中断服务函数所在段都是内核代码段,所以DPLs为0;而大多数中断描述符的特权级是为0,只有CPL=0时才能让CPL<=DPLd成立,即大多数情况下能通过第二次比较的只有内核代码段。所以Linux中提供了少量的中断描述符的特权级为3,这些中断描述符对应的中断向量号分别是0x03,0x04,0x05,x080。对应的四个终端服务函数分别为断点,溢出,边界检查,系统调用。
4、如果此时处于用户态,则通过当前线程的数据结构找到内核栈地址,从CPU唯一的TSS段找到该内核栈的栈顶指针esp0,然后即可将用户态下的CPU各个寄存器保存到内核栈中。
5、如果此时处于内核态,那么需要保存当前进程的状态信息
6、CPU利用中断服务程序的段描述符将其第一条指令的地址加载到cs和eip寄存器中,开始执行中断服务程序。
鸣谢单位
操作系统~用户态进入内核态的方式(中断、异常、系统调用)_Listen-Y的博客-CSDN博客_用户态访问内核态方法
如何从用户态进入内核态_每一个不曾起舞的日子,都是对生命的辜负的博客-CSDN博客_如何进入内核态
轻量级进程 +SS寄存器和ESP寄存器+怎么理解linux内核栈?+用户态/内核态、用户栈/内核栈_fgh431的博客-CSDN博客_ss和esp