内核态/用户态,内核栈/用户栈

2,774 阅读3分钟

一、用户态和内核态

intel的x86处理器是通过Ring级别来进行访问控制的,级别共分4层,Ring0-Ring3。在Linux操作系统中主要采用了0和3两个特权级,也就是我们通常所说的内核态和用户态。

CPU-RING
内核态也称为特权态,就是拥有资源和权限多的状态。相对来说,用户态就是非特权态,访问的资源受到限制。如果一个程序运行在特权态,该程序就可以访问计算机的任何资源,它的资源访问权限不受限制。如果一个程序运行在用户态,其资源需求将受到各种限制。如:要访问操作系统的内核数据结构,如进程表,则需要在特选态下才能办到。如果要访问用户程序里的数据,在用户态即可。

二、用户栈和内核栈

内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每一个进程都有两个栈,一个用户栈,存在于用户空间;一个内核栈,存在于内核空间。当进程在用户空间运行时,使用用户栈,CPU堆栈指针寄存器(sp寄存器)里面的内容是用户栈地址;当进程在内核空间时,CPU堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。

当进程因为中断或者系统调用或者异常陷入到内核态时,进程所使用的栈也要从用户栈转到内核栈。进程陷入到内核态后,先把用户栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之后时,在内核态执行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了用户栈和内核栈的互转。

那么,知道从内核转到用户态时,用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,如何知道内核栈的地址?关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为一旦进程返回到用户态后,内核栈中保存的信息全部失效,因此每次进程从用户态陷入内核态的时候得到的内核栈都是空的,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了

三、用户态/内核态切换

每一个系统调用的函数对应着内核里的一个具体实现,每一个系统函数都有一个相应的数字对应,即系统调用号,这个数字事实上是系统调用函数指针的偏移。

当我们运行一个系统调用时,运行时库通过查找这个表来决定对应的函数代码,即系统调用号,然后存入到寄存器中,通常为eax寄存器,然后当切换到到内核态后,内核根据系统调用号来查找到对应的系统调用处理例程的函数名,从而找到对应的代码入口地址。系统调用切换过程如图所示:

内核栈和用户栈分别处于内核空间和用户空间两个不同的空间中,是相互独立的,所以参数传递不能只是简单的压栈出栈,Linux内核中主要采用寄存器的方式来完成这个任务。