Linux用户态与内核态、系统调用与库函数

x33g5p2x  于2021-10-09 转载在 Linux  
字(1.9k)|赞(0)|评价(0)|浏览(384)

一、Unix/Linux的体系架构:

如上图所示,从宏观上来看,Linux操作系统的体系架构f分为用户态和内核态(或者称“用户空间”和“内核空间”)。
内核从本质上说是一种软件 : 控制计算机的 硬件资源,并提供上层应用程序运行的环境。
用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括:CPU资源、存储资源、I/O资源等。
为了使上层应用能够访问这些资源,内核必须为上层应用提供访问的接口:系统调用

系统调用是操作系统的最小功能单位,这些系统调用根据不同的应用场景可以进行扩展和裁剪,现在各种版本的Unix实现都提供了不同数量的系统调用,如Linux的不同版本提供了240~260个系统调用,FreeBSD大约提供了320个(reference:《Unix环境高级编程》)。有时候为了实现一个完整的应用功能,就必须调用很多的系统调用。
然而从程序员开发者的角度来看,众多的系统调用势必会增加程序员的负担,良好的程序设计方法应该是:重视上层的业务逻辑实现,而尽可能避免底层复杂的实现细节。
库函数正是为了将程序员从复杂的细节中解脱出来而提出的一种有效方法。 它实现对系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用。显然,这样的库函数依据不同的标准也可以有不同的实现版本,如:ISO C标准库,POSIX标准库等。

从这个角度来看,系统调用类似于汉字的一个“笔画”,库函数就像是组成汉字的“偏旁”,而一个上层应用就是一个“汉字”。

Shell是一个特殊的应用程序,俗称“命令行”,本质上是一个命令解释器,它下通系统调用,上通各种应用,通常充当着一种“胶水”的角色,来连接各个小功能程序,让不同程序能够以一个清晰的接口协同工作,从而增强各个程序的功能。

同时,Shell是可编程的,它可以执行符合Shell语法的文本,这样的文本称为“Shell脚本”,通常短短的几行Shell脚本就可以实现一个非常大的功能,原因就是这些Shell语句通常对系统调用做了一层封装。

为了方便用户和系统交互,一般情况下,一个Shell对应一个终端,终端是一个硬件设备,呈现给用户一个图形化窗口。我们可以通过这个窗口输入或者输出文本。这个文本直接传递给Shell进行解释,然后执行。

总结一下,用户态的应用程序可以通过三种方式来访问内核态的资源:

  1. 系统调用;
  2. 库函数;
  3. Shell脚本。

下图(图二)是对上图(图一)的一个细分结构,从这个图中可以更进一步的对内核所做的事有一个“全景式”的印象:
向下控制硬件资源,向内管理操作系统资源,向上为应用程序提供系统调用的接口。

Kernel向内管理的操作系统资源包括:进程的调度和管理、内的管理、文件系统的管理、设备驱动程序的管理、网络资源的管理。

从整体上来看,整个操作系统分为两层:用户态和内核态,这种分层架构极大的提高了资源管理的可扩展性和灵活性,而且方便用户对资源的调用和集中式的管理,带来一定的安全性。

二、用户态和内核态的切换:

因为操作系统的资源是有限的,如果访问资源的应用程序过多,必然会消耗过多的资源而造成资源访问的冲突。因此,为了解决冲突而有序的访问系统资源,Unix/Linux的设计哲学之一是:对不同的操作赋予不同的执行等级,即所谓“特权”的概念。例如在Inter的X86架构的CPU提供了 0 到 3 四个特权级,数字越小特权越高;Linux操作系统中主要采用了 0 和 3 两个特权级,分别对应的是内核态和用户态。

运行与用户态的进程可以执行的操作和访问的资源都会受到极大的限制,而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制。

很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,折旧涉及到一个 “从用户态切换到内核态的过程”。

比如C函数库中的内存分配函数 malloc(),它具体是使用 sbrk() 系统调用来分配内存,当malloc()调用sbrk() 的时候就涉及一次从用户态到内核态的切换,类似的函数还有prinf(),调用的是write() 系统调用来输出字符串,等等。

一般存在以下三种情况 会发生 用户态到内核态的切换:

  1. 系统调用;
  2. 异常事件;
  3. 外围设备的中断。

其中,异常事件是指: 当CPU正在执行运行与用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如 缺页异常

外围设备的中断 是指:当外围设备执行完成用户的请求操作后,会向CPU发出中断信号,此时,CPU会暂停执行下一条即将执行的语句,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就会发生用户态到内核态的切换(Signal软中断是否也属于这种情况?)。

相关文章