内核态与用户态

在32位操作系统下,虚拟地址空间为4G,也就是说一个进程的最大的地址空间为4G。操作系统的核心是内核,它独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证内核的安全,现在的操作系统一般都强制用户进程不能直接操作内核。具体的实现方式基本都是由操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。对Linux操作系统而言,最高的1G字节由内核使用。低3G字节由各个进程使用

换句话说,最高1G内核空间是被所有进程共享,剩余3G才归进程自己使用

区分内核空间与用户空间的意义

CPU的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令

内核态与用户态

Linux系统只使用了Ring0和Ring3两个运行级别。当进程运行在Ring3级别时被称为运行在用户态,而运行在Ring0级别时被称为运行在内核态。运行在Ring3级别时被称为运行在用户态,运行在Ring0级别时被称为运行在内核态
在内核态下,进程运行在内核地址空间中,此时CPU可以执行任何指令。运行的代码也不受任何的限制,可以自由地访问任何有效地址,也可以直接进行端口的访问
在用户态下,进程运行在用户地址空间中,被执行的代码要受到CPU的检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段中I/O许可位图中规定的可访问端口进行直接访问。

我们可以将每个处理器在任何指定时间点上的活动概括为下列三者之一:

  • 运行于用户空间,执行用户进程

  • 运行于内核空间,处于进程上下文,代表某个特定的进程执行

  • 运行于内核空间,处于中断上下文(保证中断服务程序能够在第一时间响应和处理中断请求,然后快速地退出),与任何进程无关,处理某个特定的中断

用户态到内核态的转换概况来说有三种方式分别为系统调用、软中断和硬件中断

系统调用
linux中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。

软中断
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常

硬件中断
外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的

具体的切换步骤:
从当前进程的描述符中提取其内核栈的ss0及esp0信息
使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令
将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了

Linux系统结构图

从内核空间和用户空间的角度看整个Linux系统的结构,从下往上依次为:硬件 -> 内核空间 -> 用户空间

img1