UNIX 体系结构
从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为 内核(kernel),因为它相对较小,而且位于环境的核心。
内核的接口被称为 系统调用(system call)。公共函数库 构建在系统调用接口之上,应用程序 既可以使用公共函数库,也可以使用系统调用。shell 是一个特殊的应用程序,为运行其它应用程序提供了一个接口。
+----------------------------+
| Application |
| +---------+ |
| | Shell | |
| | +------+--------+ |
| | | System Call | |
| | | +--------+ | |
| +--| | Kernel | | |
| | | +--------+ | |
| | +---------------+ |
| | Public Library | |
| +------------------+ |
| |
+----------------------------+
从广义上说,操作系统包括了内核和一些其它软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特性。这里所说的其它软件包括系统实用程序(system utility)、应用程序、shell 以及公用函数库等。
例如,Linux 是 GNU 操作系统(比如 Ubuntu)使用的内核。
系统调用和库函数
所有的操作系统都提供多种服务的入口点,由程序向内核请求服务。各种版本的 UNIX 实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点被称为系统调用。
系统调用接口总是在《UNIX 程序员手册》的第 2 部分中说明,是用 C 语言定义的,与具体的 UNIX 实现技术无关。
UNIX 系统调用的使用方式是为每个系统在标准 C 库中设置一个具有相同名字的函数。用户进程用标准 C 函数的调用方式来调用这些函数,然后,这些函数会用系统所要求的技术来调用相应的内核服务。从应用角度考虑,可将系统调用看作为 C 函数。
《UNIX 程序员手册》的第 3 部分定义了程序员可以使用的公共函数库。虽然这些函数可能会调用一个或多个内核的系统调用,但是它们并不是内核的入口点。
从实现角度来看,系统调用和库函数之间有着本质的区别,但从应用角度来看,这些区别并不重要。如果可以的话,我们可以替换库函数,但是系统调用通常是不能被替换的。
以存储空间分配函数 malloc
为例。有多种方法可以进行存储空间分配,以及与其相关的无用空间的回收操作(最佳适应、首次适应等),并不存在对所有程序都是最优解的一种技术。UNIX 系统调用中处理存储空间分配的是 sbrk(2)
,它不是一个通用的存储管理器。它按指定字节数增加或减少进程地址空间,如何管理该地址空间却取决于进程的具体行为。存储空间分配函数 malloc(3)
实现一种特定类型的分配,如果我们不喜欢其操作方式,则可以定义自己的 malloc
函数,但它很可能会使用 sbrk
系统调用。事实上,有很多软件包(例如 Redis 中可使用的 jemalloc
)使用 sbrk
系统调用实现自己的存储空间分配算法。
内核中的 sbrk
系统调用分配一块空间给进程,而库函数 malloc
则在用户层次管理这一空间。
应用程序既可以调用系统调用,也可以调用库函数。很多库函数则会调用系统调用。
+---------------+ +------------------------+
| +-----------+ | | +-----------+ |
| |Application| | | |Application| |
| +-----------+ | | +-----------+ |
| ^ ^ ^ | User Process | +---------+ ^ |
| v v v | | |C Library| | |
| +--------+ | | +---------+ | |
| | malloc | | | ^ | |
| +--------+ | | | | |
+---------------+ +-----|---------|--------+
^ ^ ^ | |
| | | | |
v v v v v
+---------------+ +------------------------+
| |sbrk| | | |
| +----+ | Kernel | System Call |
| | | |
+---------------+ +------------------------+
系统调用和库函数之间的另一个区别是:系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。 从 sbrk
系统调用和 malloc
库函数之间的差别中可以看到这一点。比较不带缓冲的 I/O 函数和标准 I/O 函数时,也能看到这种差别。