一、Linux系统结构
Linux特权级别:Linux的架构中,很重要的一个能力就是操纵系统资源的能力。系统资源是有限的,如果不加限制的允许任何程序以任何方式去操纵系统资源,必然会造成资源的浪费,发生资源不足等情况。为了减少这种情况的发生,Linux制定了一个等级制定,即特权。Linux将特权分成两个层次,以0和3标识。0特权级操纵系统资源上是没有任何限制的,可以执行任何操作,而3,则会受到极大的限制。Linux中,还存在R1和R2两个级别,一般归属驱动程序的级别。在Windows平台没有R1和R2两个级别,只用R0内核态和R3用户态 ; 0级特权可以访问0-3,1特权级可以访问1-3...; 我们把特权级0称之为内核态,特权级3称之为用户态。
内核态:内核,实质是一个软件,内核具备直接操纵系统资源的能力(CPU资源,I/O资源)等的能力。
用户态:上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,不具备直接操作系统资源的能力
系统调用:为了使上层应用能够访问使用系统资源,内核为上层应用提供访问的接口。系统调用可以理解为操作系统的最小功能单位,所有有应用程序,都是通过系统调用完成系统资源的操作。
Shell:它是一个特殊的应用程序,俗称命令行。类似于windows的cmd,是一个命令解释器。有些应用程序是基于Shell做的,而不是基于系统调用
公用函数库:一个Linux系统,根据版本不同,大约包含240~260个系统调用。为了使得操作更为简单,更加便于应用程序使用,Linux系统对系统调用的部分功能进行了再次封装,形成了公用函数库,以供应用程序调用。公用函数库中的一个方法,实质上是若干个系统调用以特定的逻辑组合而成。
用户态的应用程序可以通过三种方式来访问内核态的资源:
- 1)系统调用
- 2)库函数
- 3)Shell脚本
二、用户态与内核态的切换
应用程序一般会在以下几种情况下切换到内核态:
1. 系统调用:C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存,当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换,类似的函数还有printf(),调用的是wirte()系统调用来输出字符串。
2. 异常事件。当发生某些预先不可知的异常时,就会切换到内核态,以执行相关的异常事件。
3. 设备中断。在使用外围设备时,如外围设备完成了用户请求,就会向CPU发送一个中断信号,此时,CPU就会暂停执行原本的下一条指令,转去处理中断事件。此时,如果原来在用户态,则自然就会切换到内核态。
系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断。从触发方式和效果上来看,这三种切换方式是完全一样的,都相当于是执行了一个中断响应的过程。但是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。
用户态如何切换到内核态
1、若中断时,进程在执行用户态的代码,该中断会引起CPU特权级从3级到0级的切换,此时CPU会进行堆栈的切换,CPU会从当前任务的TSS中取到新堆栈的段选择符和偏移值;CPU首先会把原用户态的堆栈指针ss和esp压入内核态堆栈,随后把标志寄存器eflags的内容和此次中断的返回位置cs,eip压入内核态堆栈。当中断处理函数结束后,将恢复内核栈中的数据,并继续处理被中断的进程。
2、若中断时,进程正在执行内核态的代码,则不需要堆栈的切换,CPU仅把eflags的内容和此次中断的返回位置cs,eip压入内核态堆栈,然后执行中断服务程序。
用户态切换到内核态的开销
1、当用户态切换到内核态时系统调用一般都需要保存用户程序上下文(context), 在进入内核的时候需要保存用户态的寄存器
2、内核态返回用户态的时候会恢复这些寄存器的内容
3、 如果需要在不同用户程序间切换的话,那么还要更新cr3寄存器,这样会更换每个程序的虚拟内存到物理内存映射表的地址,也是一个比较高负担的操作
4、每个进程都会有两个栈,一个内核态栈和一个用户态栈。当int中断执行时就会由用户态栈转向内核态栈。系统调用时需要进行栈的切换。而且内核代码对用户不信任,需要进行额外的检查。系统调用的返回过程有很多额外工作,比如检查是否需要调度等。