通常我们写程序时的关注重点都放在了实现功能,但如果将代码转换成CPU执行的指令时,那么我们所写的代码就是一个动态执行的CPU指令序列。而硬件设备对指令的执行有严格的控制,例如如下代码:
void testfork()
{ if(0 = = fork()){ printf(“create new process success! ”); } printf(“testfork ok ”); }
- 静态观察
从功能的角度来看: 就是实际执行了一个fork(),生成一个新的进程;
从逻辑的角度看:就是判断了如果fork()返回的是则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。
- 动态观察
由操作系统来管理这段代码转换成CPU可执行的指令序列。
对Unix/Linux系统来说,fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。
特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有 CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于 Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。
从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在级特权级上时,就可以称之为运行在内核态。
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用 sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发 sys_fork()的执行时,就切换到了内核态。
- 用户态和内核态的转换主要有以下几种:
1、系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
2. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
综上,这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
以上内容参考:http://blog.chinaunix.net/uid-1829236-id-3182279.html ,感谢作者的贡献!
一、名词
1.1 硬件对程序指令的检查(CPL、RPL、DPL)
Intel设置DPL、RPL、CPL以实现分级和权限检查。
CPL:当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于 cs 寄存器的低两位。
内核态的时候,CPL = 0;用户态的时候,CPL = 3;linux中只用了这两个,并且它的值存在CS或者SS的低两位。
DPL:规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定;存储在段描述符中。
RPL:进程对段访问的请求权限(Request Privilege Level),RPL的作用是----能够用来确保具有特权级的代码不会代表另一个应用程序去访问一个段,除非那个应用程序具有访问那个段的权限。
- 三者之间的关系(CPL、RPL、DPL)
当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}。
【例子1】
中国官员分为6级:国家主席1、总理2、省长3、市长4、县长5、乡长6。假设我是当前进程,级别总理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省长的级别(RPL=3 )去访问,是可以的;但如果我用县长的级别,人家就不理咱了。
为什么采用RPL,是考虑到安全的问题,就好像你明明对一个文件用有写权限,为什么用只读打开它呢,还不是为了安全!也就是说我先要有权去访问它,至于用哪种身份去访问,就另当别论了。
【例子2】
A进程的 DPL为0,C进程的 DPL为 1,现在有一个B进程他的 DPL为2,这B进程想委托A进程去访问C的数据时,如果没有RPL的话这样的委托访问是可以成功的,但这样是非常不安全的。
有了RPL以后A进程在访问C的时候还要受到RPL的约束,此时可以将A中访问C的选择子的RPL设为B的DPL,这样A对C的访问权限就相当为EPL=max(RPL,DPL)=2,这样他就无法代表B去越权访问C了。