Linux用户态和内核态

http://blog.chinaunix.net/uid-23069658-id-3344883.html

http://blog.csdn.net/ddna/article/details/4941373



如何看待操作系统的用户空间和内核空间?


一、前提摘要

1.操作系统(如linux,提供了用户模式和内核模式:将其分为了用户空间和内核空间)

                                  (使用cpu的ring0模式运行内核态代码;ring3模式运行用户态代码)

       所谓的操作系统,就是对硬件资源进行统一管理的系统,并向用户提供业务接口的软件系统。通俗来讲,【操作系统就是要能管理你的CPU和内存,驱动你的硬盘,管理你的网卡和显示器,然后当你点击鼠标和敲击键盘时能做出正确的响应。

        以CPU为例,如果你是一个操作系统的设计者,你会如何处理不同CPU体系架构的差异性(如Intel、ARM,抑或AMD、MIPS等)?也就是说不同的操作系统在处理CPU体系架构的差异化方面是不相同的,有些操作系统,就提供了用户模式和内核模式这种机制 (例如linux),而有些操作系统呢,又没有用户态和内核态之分(例如vxWorks)。

              ||(管理/操作)

              \/

2.CPU(如Intel x86的CPU,提供四种特权模式:ring0~ring3)

       Intel X86体系的CPU提供了四种特权模式ring0~ring3,其中ring0特权最高,ring3的特权最低,之所以要做这样的区分一个主要目的是保护资源,通俗来讲要保护的资源无非就是“内存、I/O端口以及执行特殊机器指令的能力”。任何一个时刻,x86CPU都是在一定的特权模式下运行。

        同样,对于ARM体系的CPU 一共有七种运行模式,分别是:用户模式(usr)、快速中断模式(fiq)、中断模式(irq)、管理模式(svc)、系统模式(sys)、数据访问终止模式(abt)和未定义指令终止模式(und)。除了用户模式外,其他6中工作模式都属于特权模式,而特权模式中除了系统模式外,其他5种模式又称为异常模式



二、实例分析


         以Linux系统和Intel x86的CPU为例,Linux使用了ring0模式来运行内核态的代码,ring3来运行用户态的代码,ring1和ring2没有使用。32为操作系统可寻址范围是0~4GB,为此Linux系统将其分为了用户空间和内核空间两部分:

                                  Linux用户态和内核态_第1张图片

        其中用户空间占低位的3GB空间,范围从虚拟地址的0x00000000到0xBFFFFFFFF,内核空间占用了高位的1GB空间,虚拟地址范围从0xC0000000到0xFFFFFFFF。内核空间里存放的是整个内核代码和所有内核模块,以及内核所维护的数据。

        当用户运行一个程序时,该程序所创建的进程一开始是运行在用户空间的,当它要执行网络发送数据等动作时,必须通过调用write、send等系统函数来完成,这些系统调用会去调用内核中的代码来完成用户的请求操作,这时CPU必须从ring3切换到ring0,然后进入内核地址空间去执行这些代码完成操作,完成后又切换回ring3,回到用户态。

        所以,我们可以看到,提供了用户模式和内核模式的操作系统,用户态的程序就不能随意操作内核地址空间里的数据,具有一定的安全保护作用;与此同时,在实时性方面稍微有所牺牲。

        也要就是说,只要内核代码没有问题,用户空间程序的错误和BUG一般来说不会导致系统崩溃,提高了系统的健壮性。而Linux也提供了一些用户空间和内核空间通讯的机制,使我们可以开发出更高效的程序。但一定要记住,内核中一点小小的错误都是会导致整个系统崩溃,所以开发内核程序时一定要非常小心谨慎。


操作系统用户态和内核态之间的切换过程

1. 用户态和内核态的概念区别

先看一个例子:

1)例子

   void testfork(){

       if(0 = = fork()){/////系统调用

       printf(“create new process success!/n”);

}

printf(“testfork ok/n”);

}

 

2)特权级

       fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。

      特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在0级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。Linux使用了ring0模式来运行内核态的代码,ring3来运行用户态的代码

 

3)用户态和内核态

        现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在0级特权级上时,就可以称之为运行在内核态。

       虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。

       当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发sys_fork()的执行时,就切换到了内核态。

 

2.   用户态和内核态的转换

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

a. 系统调用

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

b. 异常

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

c. 外围设备的中断

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

 

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

 

2)具体的切换操作

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

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

       [2] 使用ss0和esp0指向的内核栈将当前进程的 cs,eip,eflags,ss,esp 信息保存起来,这个

过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一

条指令。

       [3] 将先前由中断向量检索得到的中断处理程序的cs,eip 信息装入相应的寄存器,开始

执行中断处理程序,这时就转到了内核态的程序执行了。





你可能感兴趣的:(linux内核)