操作系统的两种CPU状态

cpu根据操作系统的设置(设置程序状态寄存器PSW),拥有两种不同的运行状态:

  1. 内核态(Kernel Mode):运行操作系统程序,操作硬件。内核态下cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
  2. 用户态(User Mode):运行用户程序。用户态下只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

两种状态所赋予指令的权限不同:

  1. 特权指令:只能由操作系统使用、用户程序不能使用的指令。如:IO操作、内存清零等
  2. 非特权指令:用户程序可以使用的指令。 如:控制转移/算数运算,有一个特殊的非特权指令:访管指令(使用户程序从用户态陷入内核态)


    划分这两种状态时为了防止一个程序非法的获取另一个程序的内存空间、数据等等,通过权限划分来阻止一些可能出现的危险操作。

用户态与内核态的转化

系统调用

所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作。

所以,需要一种机制能够使得用户程序切换到内核态, 但是不能控制在内核态中执行的指令,这种机制在操作系统中就叫做系统调用,在CPU中叫做陷阱指令(Trap Instruction)

这种机制的工作流程如下:

  1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
  2. 用户态程序执行陷阱指令
  3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
  4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
  5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果

内核态、用户态、CPU运行级别

CPU在设计的时候设置了3种运行状态,三种状态表示了不同的权限等级,称为CPU的特权环。

CPU特权环:R0、R1、R2和R3,不同级别能够运行不同的指令集合;

当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系, intel cpu提供Ring0-Ring3四种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为 内核态,没有使用Ring1Ring2

ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部 分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运 行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必 须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。

例如:C库接口malloc申请动态内存,malloc的实现内部最终还是会调用brk()或者mmap()系统调用来分配内存。

用户态—->内核态: 只有三种方式可以从用户态转化为内核态,分别是中断异常陷入机制(访管指令)

  1. 中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
  2. 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  3. 系统调用:这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。

内核态—->用户态:设置程序状态字PSW

总结

通过看系统调用的整个过程来看:系统调用的开销很大。所以才会出现缓存机制,先将数据写到缓冲区中,等到缓冲区满的时候,再通过系统调用的方式来从缓冲区中读入读出,这样就能避免频繁的系统调用。