操作系统的内核态与用户态

   内核态与用户态是操作系统的两种运行级别,一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器位置。用户模式中的进程不允许执行特权指令,比如停止处理器、改变模式位,或者发起一个I/O操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。

  换句话说,当一个进程通过系统调用、中断、异常陷入执行异常代码时,我们就称进程处于内核状态。

  intel cpu提供Ring0-Ring3三种级别的运行模式。Ring0级别最高,Ring3最低。其中特权级0(Ring0)是留给操作系统代码,设备驱动程 序代码使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可 以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可 访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特 权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。
  当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级) 内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户 运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。
  在内核态下CPU可执行任何指令,在用户态下CPU只能执行非特权指令。当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态时,用户从用 户态切换到内核态只有在系统调用和中断两种情况下发生,一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态。
  Linux使用了Ring3级别运行用户态,Ring0作为内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间, 包括代码和数据。Linux进程的4GB地址空间,3G-4G部分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内 核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过 write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执 行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。
处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体 的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式 切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模 式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将 结果传给用户程序。

  

 

用户态切换到内核态的3种方式

1) 系统调用:这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。

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

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

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

具体的切换步骤:

从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括:

    [1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。

    [2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。

    [3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

    从用户态切换到核心态,要耗掉>100 cycle的CPU时钟。

用户栈与内核栈

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

内核空间和用户空间
Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~ 4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

参考资料:

  1.  http://blog.csdn.net/xifeijian/article/details/9080895

  2. http://blog.chinaunix.net/uid-24517549-id-4209397.html

  3.  http://www.cnblogs.com/shengge/archive/2011/08/29/2158748.html

  4. http://www.360doc.com/content/11/0428/21/6580811_113033942.shtml

原文地址:https://www.cnblogs.com/vincently/p/4773338.html