版权 © 1996 Addison-Wesley Longman,Inc
4.4BSD 内核提供四个基本功能:进程、文件系统、通信和系统启动。本节概述了这四个基本服务在本书中的描述位置。
进程构成地址空间中的控制线程。有关创建、终止和其他控制进程的机制,请参阅第 4 章。系统为每个进程多路复用单独的虚拟地址空间;这种内存管理在第 5 章中讨论。
文件系统和设备的用户界面是相似的;共同之处在第 6 章中进行了讨论。文件系统是一组具有名称的文件,以目录树形式组织,以及操作来操作它们,如第 7 章所述。文件驻留在诸如磁盘之类的物理介质上。4.4BSD 支持在磁盘上的数据的几种组织形式,如第 8 章所述。访问远程机器上的文件是第 9 章的主题。终端用于访问系统;它们的操作是第 10 章的主题。
传统 UNIX 系统提供的通信机制包括相关进程之间的可靠的单向字节流(见管道,第 11.1 节),以及异常事件的通知(见信号,第 4.7 节)。4.4BSD 还具有一般的进程间通信设施。该设施在第 11 章中描述,使用与文件系统不同的访问机制,但是一旦建立连接,进程可以像使用管道一样访问它。有一个一般的网络框架,如第 12 章所述,通常被用作 IPC 设施的底层层。第 13 章详细描述了一种特定的网络实现。
任何真正的操作系统都有操作上的问题,比如如何启动它运行。启动和操作问题在第 14 章中描述。
第 2.3 节到第 2.14 节介绍了与第 3 章到第 14 章相关的入门材料。我们将定义术语,提及基本系统调用,并探讨历史发展。最后,我们将阐明许多重要设计决策的原因。
内核是系统中在受保护模式下运行的部分,调解所有用户程序对底层硬件(例如 CPU、磁盘、终端、网络链接)和软件构造(例如文件系统、网络协议)的访问。内核提供基本的系统设施;它创建和管理进程,并提供访问文件系统和通信设施的功能。这些功能被称为系统调用,对用户进程而言,它们看起来像是库子程序。这些系统调用是进程与这些设施之间唯一的接口。系统调用机制的详细信息在第 3 章中给出,描述了几个内核机制,这些机制不是作为进程执行系统调用的直接结果而执行的。
传统操作系统术语中的内核是提供实现额外操作系统服务所需的最小功能的软件小核心。在当代研究操作系统中- 诸如 Chorus [Rozier et al, 1988],Mach [Accetta et al, 1986],Tunis [Ewens et al, 1985]和 V Kernel [Cheriton, 1988] - 这种功能的划分不仅仅是一个逻辑划分。文件系统和网络协议等服务作为内核或核心的客户端应用程序来实现。
4.4BSD 内核未分成多个进程。这个基本设计决策是在 UNIX 的最早版本中做出的。Ken Thompson 的前两次实现没有内存映射,因此在用户和内核空间之间没有硬件强制区别。消息传递系统可以像实际实现的内核和用户进程模型一样容易实现。选择了单片内核是为了简单性和性能。早期的内核很小;将网络等设施包含到内核中已经增加了其大小。操作系统研究的当前趋势是通过将这些服务放在用户空间来减小内核大小。
用户通常通过一个命令语言解释器(称为shell)和可能通过其他用户应用程序与系统交互。这类程序和shell是通过进程实现的。这类程序的详细信息不在本书的范围内,本书几乎完全集中在内核上。
第 2.3 节和 2.4 节描述了 4.4BSD 内核提供的服务,并概述了后者的设计。后面的章节描述了这些服务的详细设计和实现,就像它们出现在 4.4BSD 中一样。
在本节中,我们以两种方式查看 4.4BSD 内核的组织方式:
作为一种静态软件主体,通过构成内核的模块所提供的功能进行分类
通过其动态操作,根据提供给用户的服务进行分类
内核的最大部分实现了应用程序通过系统调用访问的系统服务。在 4.4BSD 中,这个软件根据以下方式进行了组织:
基本内核设施:定时器和系统时钟处理,描述符管理和进程管理
内存管理支持:分页和交换
通用系统接口:在描述符上执行的 I/O、控制和多路复用操作
文件系统:文件、目录、路径名转换、文件锁定和 I/O 缓冲区管理
终端处理支持:终端接口驱动程序和终端线规则
进程间通信设施:套接字
支持网络通信:通信协议和通用网络设施,如路由
表 1. 4.4BSD 内核中的机器无关软件
头文件
9,393
4.6
初始化
1,107
0.6
内核设施
8,793
4.4
通用接口
4,782
2.4
进程间通信
4,540
2.2
终端处理
3,911
1.9
虚拟内存
11,813
5.8
vnode 管理
7,954
3.9
文件系统命名
6,550
3.2
快速文件存储
4,365
2.2
日志结构文件存储
4,337
2.1
基于内存的文件存储
645
0.3
cd9660 文件系统
4,177
2.1
杂项文件系统(10)
12,695
6.3
网络文件系统
17,199
8.5
网络通信
8,630
4.3
互联网协议
11,984
5.9
ISO 协议
23,924
11.8
X.25 协议
10,626
5.3
XNS 协议
5,192
2.6
这些类别中的大多数软件是与机器无关的,并且可以在不同的硬件架构之间移植。
内核的与机器有关的方面与主流代码隔离开来。特别是,与特定架构的条件代码没有包含在与机器无关的代码中。当需要特定于架构的操作时,与机器无关的代码调用位于与机器有关的代码中的特定于架构的函数。与机器相关的软件包括
低级系统启动操作
陷阱和故障处理
进程运行时环境的低级操作
配置和初始化硬件设备
I/O 设备的运行时支持
表 2. HP300 机器相关软件在 4.4BSD 内核中的分类
|代码行数|内核百分比| | -------------------------------------------------| ---------------| ----------------------| |机器相关头文件|1,562|0.8| |设备驱动头文件|3,495|1.7| |设备驱动源代码|17,506|8.7| |虚拟内存|3,087|1.5| |其他机器相关|6,287|3.1| |汇编语言例程|3,014|1.5| |HP/UX 兼容性|4,683|2.3|
4.4BSD 内核中的机器无关软件概述了构成 HP300 的 4.4BSD 内核的机器无关软件。第二列中的数字是 C 源代码、头文件和汇编语言的代码行数。几乎所有内核软件都是用 C 编程语言编写的;用汇编语言编写的不到 2%。如 4.4BSD 内核中 HP300 的机器相关软件中的统计数据所示,除去 HP/UX 和设备支持,机器相关软件仅占内核的 6.9%。
内核中只有一小部分用于初始化系统。这些代码在系统启动时使用,负责设置内核的硬件和软件环境(见第 14 章)。一些操作系统(尤其是那些物理内存有限的操作系统)在执行这些功能后,会丢弃或覆盖执行这些功能的软件。4.4BSD 内核不会回收启动代码使用的内存,因为在典型机器上,这部分内存资源仅占内核资源的 0.5%。此外,启动代码不会集中出现在内核的某一个地方——它分散在整个内核中,通常出现在与被初始化内容逻辑相关的位置。
内核级和用户级代码之间的边界由底层硬件提供的硬件保护设施强制执行。内核在一个对用户进程不可访问的单独地址空间中运行。特权操作(如启动 I/O 和停止中央处理单元(CPU))仅对内核可用。应用程序通过系统调用从内核请求服务。系统调用用于导致内核执行复杂操作,如将数据写入二级存储,以及简单操作,如返回当前时间。所有系统调用对应用程序都是同步的:应用程序在内核执行与系统调用相关的操作时不运行。内核可能在返回后完成与系统调用相关的一些操作。例如,写入系统调用将在进程等待时将要写入的数据从用户进程复制到内核缓冲区,但通常会在内核缓冲区写入磁盘之前从系统调用返回。
系统调用通常被实现为改变 CPU 执行模式和当前地址空间映射的硬件陷阱。系统调用中由用户提供的参数在被使用之前会被内核验证。这样的检查确保了系统的完整性。所有传递到内核的参数都会被复制到内核的地址空间中,以确保经过验证的参数不会在系统调用的副作用中被更改。系统调用的结果由内核返回,可以通过硬件寄存器或将它们的值复制到用户指定的内存地址来返回。与传递到内核的参数类似,用于返回结果的地址必须经过验证,以确保它们是应用程序地址空间的一部分。如果内核在处理系统调用时遇到错误,它会向用户返回错误代码。对于 C 编程语言,此错误代码存储在全局变量 errno 中,并且执行系统调用的函数返回值为 -1。
用户应用程序和内核相互独立运行。4.4BSD 不会将 I/O 控制块或其他操作系统相关的数据结构存储在应用程序的地址空间中。每个用户级应用程序都在其执行的独立地址空间中提供。内核会使大多数状态更改,例如在另一个进程运行时挂起进程,对涉及的进程不可见。
4.4BSD 支持多任务环境。每个任务或执行线程称为一个进程。4.4BSD 进程的上下文包括用户级状态,包括其地址空间的内容和运行时环境,以及内核级状态,其中包括调度参数、资源控制和标识信息。上下文包括内核在为进程提供服务时使用的所有内容。用户可以创建进程,控制进程的执行,并在进程的执行状态发生变化时接收通知。每个进程被分配一个唯一值,称为进程标识符(PID)。内核在向用户报告状态更改时使用此值来标识进程,并且用户在系统调用中引用进程时也使用该值。
内核通过复制另一个进程的上下文来创建进程。新进程称为原始父进程的子进程。进程创建中复制的上下文包括进程的用户级执行状态和内核管理的进程系统状态。内核状态的重要组件在第 4 章中描述。
进程生命周期
进程生命周期使用进程生命周期中描述的。一个进程可以使用 fork 系统调用创建一个与原始进程相同的新进程。fork 调用两次返回:一次在父进程中,其中返回值是子进程的进程标识符,一次在子进程中,其中返回值是 0。父子关系在系统中的一组进程上引入了一个分层结构。新进程共享其父进程的所有资源,如文件描述符、信号处理状态和内存布局。
尽管有时新进程被设计为父进程的副本,但加载和执行不同程序是一种更有用和典型的操作。一个进程可以用 execve 系统调用将自己覆盖另一个程序的内存映像,传递一组参数给新创建的映像。其中一个参数是一个文件的名称,该文件的内容以系统识别的格式存在——要么是一个二进制可执行文件,要么是一个导致执行指定解释程序处理其内容的文件。
一个进程可以通过执行 exit 系统调用终止,向其父进程发送 8 位退出状态。如果一个进程想要与其父进程通信超过一个字节的信息,它必须设置一个使用管道或套接字的进程间通信通道,或使用一个中间文件。进程间通信在第 11 章中有详细讨论。
一个进程可以使用 wait 系统调用暂停执行,直到其任何子进程终止。该系统调用返回终止的子进程的 PID 和退出状态。父进程可以安排在子进程退出或异常终止时通过信号通知。使用 wait4 系统调用,父进程可以检索导致子进程终止的事件信息以及进程在其生命周期中消耗的资源信息。如果一个进程变成孤儿,因为其父进程在其完成之前退出,那么内核会安排将子进程的退出状态传递回一个特殊的系统进程 init:参见第 3.1 节和第 14.6 节。
内核如何创建和销毁进程的详细信息在第 5 章中给出。
进程根据进程优先级参数进行调度执行。这个优先级由基于内核的调度算法管理。用户可以通过指定一个参数(nice)来影响进程的调度,该参数权衡了整体调度优先级,但仍然需要根据内核的调度策略共享底层 CPU 资源。
系统定义了一组可能传递给进程的信号。4.4BSD 中的信号是模拟硬件中断的。进程可以指定一个用户级子程序作为信号的处理程序,信号应该传递到该处理程序。当信号生成时,在信号被处理程序捕获时,它会被阻止再次发生。捕获信号涉及保存当前进程上下文并构建一个新的上下文来运行处理程序。然后将信号传递给处理程序,处理程序可以中止进程或返回到执行进程(也许在设置全局变量后)。如果处理程序返回,信号将被解除阻止,可以再次生成(和捕获)。
或者,进程可以指定忽略信号,或者采取由内核确定的默认操作。某些信号的默认操作是终止进程。此终止可能伴随着创建包含进程当前内存映像的核心文件,用于事后调试。
有些信号无法被捕获或忽略。这些信号包括 SIGKILL,用于终止无法控制的进程,以及作业控制信号 SIGSTOP。
进程可以选择在特殊堆栈上接收信号,以便进行复杂的软件堆栈操作。例如,支持协程的语言需要为每个协程提供一个堆栈。语言运行时系统可以通过将 4.4BSD 提供的单个堆栈分配给这些堆栈来分配这些堆栈。如果内核不支持单独的信号堆栈,则必须通过扩展为每个协程分配的空间来捕获信号所需的空间。
所有信号具有相同的优先级。如果多个信号同时待处理,信号传递给进程的顺序是由实现指定的。信号处理程序执行时被阻止的信号引起其调用,但其他信号可能会发生。提供机制以便进程可以保护代码的关键部分免受指定信号的发生。
信号的详细设计和实现在第 4.7 节中描述。
进程被组织成进程组。进程组用于控制对终端的访问,并提供一种将信号分发给相关进程集合的方法。进程从其父进程继承其进程组。内核提供机制允许进程更改其进程组或其后代的进程组。创建新的进程组很容易;新进程组的值通常是创建进程的进程标识符。
进程组中的一组进程有时被称为作业,并由高级系统软件(如shell)操纵。shell创建的一种常见作业是由多个进程通过管道连接而成的管道,使得第一个进程的输出成为第二个进程的输入,第二个进程的输出成为第三个进程的输入,依此类推。shell通过为管道的每个阶段复制一个进程,然后将所有这些进程放入单独的进程组来创建这样的作业。
用户进程可以向进程组中的每个进程发送信号,也可以向单个进程发送信号。特定进程组中的进程可能会接收影响该组的软件中断,导致该组暂停或恢复执行,或者被中断或终止。
终端分配了一个进程组标识符。该标识符通常设置为与终端关联的进程组的标识符。作业控制shell可能会创建与同一终端关联的多个进程组;终端是这些组中每个进程的控制终端。如果进程的控制终端描述符与进程的进程组标识符不匹配,则进程只能从其控制终端读取。如果标识符不匹配,则进程尝试从终端读取时将被阻止。通过更改终端的进程组标识符,shell可以在几个不同的作业之间仲裁终端。这种仲裁称为作业控制,并在第 4.8 节中描述了进程组。
就像一组相关的进程可以被收集到一个进程组中一样,一组进程组可以被收集到一个会话中。会话的主要用途是为守护进程及其子进程创建一个与外部隔离的环境,并集中存放用户登录shell和shell生成的作业。
每个进程有自己的私有地址空间。地址空间最初被划分为三个逻辑段:文本、数据和栈。文本段是只读的,包含程序的机器指令。数据和栈段既可读又可写。数据段包含程序的已初始化和未初始化数据部分,而栈段保存应用程序的运行时栈。在大多数机器上,随着进程的执行,内核会自动扩展栈段。一个进程可以通过系统调用扩展或收缩其数据段,而一个进程只能在段的内容与来自文件系统的数据重叠,或进行调试时改变其文本段的大小。子进程的段的初始内容是父进程段的副本。
执行进程不需要整个进程地址空间都驻留在内存中。如果一个进程引用了其地址空间中未驻留在主存中的部分,系统会将必要的信息分页到内存中。当系统资源紧张时,系统采用两级方法来维护可用资源。如果有适量的内存可用,系统会从进程中取走内存资源,如果这些资源最近没有被使用。如果资源严重短缺,系统将会将进程的整个上下文换入到辅助存储器。系统进行的需求分页和换页对进程来说是透明的。然而,一个进程可以告知系统关于未来预期内存利用的信息,以提高性能。
4.2BSD 要求支持大型稀疏地址空间、映射文件和共享内存。制定了一个被称为 mmap 的接口,允许不相关的进程请求将文件映射到它们的地址空间中。如果多个进程将同一文件映射到它们的地址空间中,一个进程对文件部分地址空间的更改将反映在其他进程的映射区域,以及文件本身中。最终,4.2BSD 没有使用 mmap 接口进行发布,因为要使其他功能(如网络)可用而受到压力。
进一步发展了 mmap 接口,这一工作在 4.3BSD 的开发过程中继续进行。超过 40 家公司和研究团体参与了讨论,导致了在伯克利软件体系结构手册中描述的修订架构 [McKusick 等,1994 年]。其中一些公司已经实现了修订后的接口 [Gingell 等,1987 年]。
再次,时间紧迫,使得 4.3BSD 无法提供接口的实现。尽管后者可以构建到现有的 4.3BSD 虚拟内存系统中,但开发人员决定不这样做,因为该实现已接近 10 年。此外,原始虚拟内存设计是基于计算机内存较小、昂贵,而磁盘连接本地、快速、大型且廉价的假设。因此,虚拟内存系统被设计为在使用内存时节俭,以换取产生额外的磁盘流量。此外,4.3BSD 实现中存在大量依赖于 VAX 内存管理硬件的问题,这阻碍了其在其他计算机架构上的可移植性。最后,虚拟内存系统并不是为了支持日益普遍和重要的紧密耦合多处理器而设计的。
尝试逐步改进旧实现似乎注定失败。另一方面,完全新的设计可以利用大容量内存,节省磁盘传输,并有潜力在多处理器上运行。因此,在 4.4BSD 中完全替换了虚拟内存系统。4.4BSD 虚拟内存系统基于 Mach 2.0 VM 系统[Tevanian, 1987],并从 Mach 2.5 和 Mach 3.0 进行了更新。它具有有效的共享支持,清晰地分离了机器无关和机器相关的特性,以及(目前未使用的)多处理器支持。进程可以在其地址空间中的任何位置映射文件。它们可以通过对同一文件进行共享映射来共享其地址空间的部分。一个进程所做的更改在另一个进程的地址空间中可见,并且也会写回到文件本身。进程还可以请求文件的私有映射,这可以防止它们所做的任何更改对映射文件的其他进程可见或写回到文件本身。
虚拟内存系统的另一个问题是在进行系统调用时将信息传递到内核的方式。4.4BSD 总是将数据从进程地址空间复制到内核中的缓冲区。对于传输大量数据的读取或写入操作,进行复制可能是耗时的。进行复制的替代方法是将进程内存重新映射到内核。4.4BSD 内核总是复制数据有几个原因:
通常,用户数据未对齐页,并且不是硬件页长度的倍数。
如果页面从进程中被取走,它将无法再引用该页面。一些程序依赖于数据即使在被写入后仍然保留在缓冲区中。
如果允许进程保留页面的副本(如当前 4.4BSD 语义中一样),则页面必须设置为写时复制。写时复制页面是通过设置为只读来保护不被写入的页面。如果进程试图修改该页面,内核会收到写入错误。然后内核会制作一个进程可以修改的页面副本。不幸的是,典型的进程会立即尝试将新数据写入其输出缓冲区,从而迫使数据无论如何都要被复制。
当页面重新映射到新的虚拟内存地址时,大多数内存管理硬件要求选择性地清除硬件地址转换缓存。缓存清除通常很慢。净效应是对于小于 4 到 8 K 字节的数据块,重新映射比复制更慢。
访问大文件和在进程之间传递大量数据的最大动机是内存映射。mmap 接口提供了一种在不复制数据的情况下完成这两项任务的方法。
内核通常会为仅在单个系统调用期间需要的内存进行分配。在用户进程中,这种短期内存会在运行时堆栈上分配。由于内核具有有限的运行时堆栈,因此在其上分配中等大小的内存块是不可行的。因此,这样的内存必须通过更动态的机制进行分配。例如,当系统必须翻译路径名时,它必须分配一个 1K 字节的缓冲区来保存名称。其他内存块必须比单个系统调用更持久,因此即使有空间也不应在堆栈上分配。一个例子是在网络连接的整个持续时间内保持的协议控制块。
内核中对动态内存分配的需求随着更多服务的添加而增加。通用内存分配器降低了内核内部编写代码的复杂性。因此,4.4BSD 内核具有一个可以被系统中任何部分使用的单一内存分配器。它具有类似于 C 库例程 malloc 和 free 的接口,为应用程序提供内存分配[McKusick&Karels,1988]。与 C 库接口类似,分配例程接受指定所需内存大小的参数。内存请求的大小范围不受限制;但是,分配的是物理内存,而不是分页。free 例程接受指向被释放存储的指针,但不需要被释放的内存块的大小。
UNIX I/O 系统的基本模型是一个可以随机或顺序访问的字节序列。在典型的 UNIX 用户进程中,没有访问方法和控制块。
不同的程序期望不同级别的结构,但内核不会对 I/O 施加结构。例如,文本文件的约定是由 ASCII 字符组成的行,由单个换行符(ASCII 换行符)分隔,但内核对这种约定一无所知。对于大多数程序来说,模型进一步简化为数据字节流,或者 I/O 流。正是这种单一的通用数据形式使得特征 UNIX 工具为基础的方法起作用[Kernighan&Pike,1984]。一个程序的 I/O 流可以作为输入馈送到几乎任何其他程序中。(这种传统 UNIX I/O 流不应与第八版流 I/O 系统或 System V,Release 3 STREAMS 混淆,这两者都可以作为传统 I/O 流访问。)
UNIX 进程使用描述符引用 I/O 流。描述符是从 open 和 socket 系统调用中获得的小的无符号整数。open 系统调用的参数是文件名和权限模式,用于指定文件是应该用于读取还是写入,或者两者兼而有之。这个系统调用还可以用于创建一个新的空文件。可以对描述符应用读取或写入系统调用来传输数据。close 系统调用可用于释放任何描述符。
描述符代表内核支持的基础对象,并由特定于对象类型的系统调用创建。在 4.4BSD 中,描述符可以表示三种对象:文件、管道和套接字。
文件是具有至少一个名称的字节线性数组。文件存在直到其所有名称都被显式删除,并且没有进程持有其描述符。进程通过使用 open 系统调用打开文件的名称来获取文件的描述符。I/O 设备被视为文件。
管道是字节线性数组,与文件相同,但仅用作 I/O 流,是单向的。它也没有名称,因此无法使用 open 打开。相反,它是通过 pipe 系统调用创建的,该调用返回两个描述符,其中一个接受输入,可可靠地将其发送到另一个描述符,无重复且有序。系统还支持命名管道或 FIFO。FIFO 具有与管道相同的属性,只是它出现在文件系统中;因此,可以使用 open 系统调用打开它。希望通信的两个进程各自打开 FIFO:一个用于读取,另一个用于写入。
套接字是一个瞬时对象,用于进程间通信;它只存在于某个进程持有指向它的描述符的时间。套接字是通过套接字系统调用创建的,该调用返回一个描述符。有不同类型的套接字,支持各种通信语义,如可靠传递数据,保留消息顺序和保留消息边界。
在 4.2BSD 之前的系统中,使用文件系统实现了管道;当在 4.2BSD 中引入套接字时,管道被重新实现为套接字。
内核为每个进程保留一个描述符表,这是内核用于将描述符的外部表示转换为内部表示的表。(描述符仅仅是这个表中的一个索引。)进程的描述符表是从该进程的父进程继承而来的,因此对描述符引用的对象的访问也是继承的。进程可以获取描述符的主要方式是通过打开或创建对象,并通过从父进程继承。此外,套接字 IPC 允许在同一台机器上的不相关进程之间在消息中传递描述符。
每个有效的描述符都有与对象开头的字节偏移量相关联。读写操作从该偏移量开始,每次数据传输后更新。对于允许随机访问的对象,还可以使用 lseek 系统调用设置文件偏移量。普通文件允许随机访问,有些设备也允许。管道和套接字则不允许。
当进程终止时,内核会回收该进程使用的所有描述符。如果进程持有对象的最后一个引用,对象的管理器会收到通知,以便执行任何必要的清理操作,例如最终删除文件或释放套接字。
大多数进程在启动运行时都希望已经打开了三个描述符。这些描述符分别是 0、1、2,更常被称为标准输入、标准输出和标准错误。通常,所有三个描述符都由登录进程(见第 14.6 节)与用户终端相关联,并通过用户运行的进程通过复制和执行继承。因此,程序可以通过读取标准输入来读取用户输入内容,程序可以通过写入标准输出将输出发送到用户屏幕。标准错误描述符也是打开的用于写入,并用于错误输出,而标准输出用于普通输出。
这些(以及其他)描述符可以映射到终端以外的对象;这种映射称为 I/O 重定向,所有标准shells允许用户执行此操作。shell可以通过关闭描述符 1(标准输出)并打开所需的输出文件来将程序的输出定向到文件,以生成新的描述符 1。它可以类似地将标准输入重定向为来自文件,方法是关闭描述符 0 并打开文件。
管道允许一个程序的输出成为另一个程序的输入,而无需重写或甚至重新链接任何一个程序。源程序的描述符 1(标准输出)不是设置为写入终端,而是设置为管道的输入描述符。类似地,接收程序的描述符 0(标准输入)被设置为引用管道的输出,而不是终端键盘。由此产生的两个进程及连接的管道集合被称为管道。管道可以是由管道连接的任意长的一系列进程。
open、pipe 和 socket 系统调用会产生具有可用于描述符的最低未使用编号的新描述符。为了使管道正常工作,必须提供某种机制来将这些描述符映射到 0 和 1。dup 系统调用会创建一个指向相同文件表项的描述符副本。新描述符也是最低未使用的描述符,但如果首先关闭了期望的描述符,则可以使用 dup 来执行所需的映射。但是需要注意:如果期望的是描述符 1,并且描述符 0 也已关闭,那么描述符 0 将是结果。为了避免这个问题,系统提供了 dup2 系统调用;它类似于 dup,但它接受一个额外的参数,指定所需描述符的编号(如果所需描述符已经打开,dup2 在重用之前会将其关闭)。
硬件设备有文件名,并且用户可以通过与常规文件相同的系统调用访问它们。内核可以区分设备特殊文件或特殊文件,并且可以确定它指向哪个设备,但大多数进程不需要做出这种确定。终端、打印机和磁带驱动器都可以像 4.4BSD 磁盘文件一样访问,即被视为字节流。因此,设备的依赖关系和特殊性尽可能地保留在内核中,即使在内核中,大多数特殊性都被隔离在设备驱动程序中。
硬件设备可以被归类为结构化或非结构化;它们分别被称为块设备或字符设备。进程通常通过文件系统中的特殊文件访问设备。对这些文件的 I/O 操作由内核驻留软件模块处理,称为设备驱动程序。大多数网络通信硬件设备只能通过进程间通信设施访问,并且在文件系统名称空间中没有特殊文件,因为原始套接字接口提供比特殊文件更自然的接口。
结构化或块设备的典型代表是磁盘和磁带,包括大多数随机访问设备。内核支持对面向块的结构化设备进行读取-修改-写入类型的缓冲操作,以允许后者以完全随机的字节寻址方式进行读取和写入,就像常规文件一样。文件系统是在块设备上创建的。
非结构化设备是那些不支持块结构的设备。熟悉的非结构化设备包括通信线路、光栅绘图仪和未缓冲的磁带和磁盘。非结构化设备通常支持大块 I/O 传输。
非结构化文件被称为字符设备,因为最早实现的是终端设备驱动程序。内核与这些设备的驱动程序的接口方便了其他非块结构设备的实现。
设备特殊文件是通过 mknod 系统调用创建的。还有一个额外的系统调用 ioctl,用于操作特殊文件的底层设备参数。可以针对每个设备执行不同的操作。这个系统调用允许访问设备的特殊特性,而不是过度使用其他系统调用的语义。例如,磁带驱动器上有一个 ioctl 用于写入磁带结束标记,而不是有一个特殊或修改过的写入版本。
4.2BSD 内核引入了一种 IPC 机制,比管道更灵活,基于套接字。套接字是通信的端点,由描述符引用,就像文件或管道一样。两个进程可以分别创建套接字,然后连接这两个端点以生成可靠的字节流。一旦连接,套接字的描述符可以被进程读取或写入,就像后者使用管道一样。套接字的透明性允许内核将一个进程的输出重定向到另一台机器上的另一个进程的输入。管道和套接字之间的一个主要区别是,管道需要一个共同的父进程来建立通信通道。套接字之间的连接可以由两个不相关的进程建立,可能驻留在不同的机器上。
System V 通过 FIFO(也称为命名管道)提供本地进程间通信。FIFO 在文件系统中显示为一个对象,不相关的进程可以打开并通过它发送数据,就像它们通过管道进行通信一样。因此,FIFO 不需要一个共同的父进程来设置它们;它们可以在一对进程启动并运行后连接。与套接字不同,FIFO 只能在本地机器上使用;它们不能用于在不同机器上的进程之间通信。FIFO 仅在 4.4BSD 中实现,因为它们是 POSIX.1 标准所要求的。它们的功能是套接字接口的一个子集。
套接字机制需要对传统的 UNIX I/O 系统调用进行扩展,以提供相关的命名和连接语义。开发人员没有过度使用现有接口,而是在现有接口能够正常工作的情况下使用它们,并设计了新的接口来处理新增的语义。读取和写入系统调用用于字节流类型的连接,但是还添加了六个新的系统调用,以允许发送和接收带有地址的消息,例如网络数据报。用于编写消息的系统调用包括 send、sendto 和 sendmsg。用于读取消息的系统调用包括 recv、recvfrom 和 recvmsg。回顾起来,每个类中的前两个都是其他系统调用的特例;recvfrom 和 sendto 可能应该作为 recvmsg 和 sendmsg 的库接口添加。
除了传统的读取和写入系统调用外,4.2BSD 还引入了分散/聚集 I/O 的功能。分散输入使用 readv 系统调用,允许将单个读取放置在几个不同的缓冲区中。相反,writev 系统调用允许在单个原子写入中写入几个不同的缓冲区。与使用 read 和 write 时传递单个缓冲区和长度参数不同,进程传递一个指向缓冲区和长度数组的指针,以及描述数组大小的计数。
该功能允许在进程地址空间的不同部分中原子地写入缓冲区,无需将它们复制到单个连续缓冲区。在底层抽象基于记录时,原子写入是必要的,例如磁带驱动器在每个写入请求上输出一个磁带块的情况。能够将单个请求读取到几个不同的缓冲区中也很方便(例如将记录头读取到一个地方,数据读取到另一个地方)。尽管应用程序可以通过将数据读入大缓冲区然后将片段复制到其预定目的地来模拟分散数据的能力,但在这种情况下,内存到内存的复制成本通常会将受影响的应用程序的运行时间加倍以上。
正如 send 和 recv 可以实现为对 sendto 和 recvfrom 的库接口一样,也有可能用 readv 和 writev 来模拟 read 和 write。然而,read 和 write 使用频率要高得多,模拟它们所需的额外成本并不值得。
随着网络计算的扩展,支持本地和远程文件系统变得可取。为了简化对多个文件系统的支持,开发人员向内核添加了一个新的虚拟节点或 vnode 接口。从 vnode 接口导出的操作集看起来很像先前由本地文件系统支持的文件系统操作。但是,它们可以由各种文件系统类型支持:
基于本地磁盘的文件系统
使用各种远程文件系统协议导入的文件
只读 CD-ROM 文件系统
提供特殊接口的文件系统 — 例如, /proc 文件系统
4.4BSD 的一些变体(例如 FreeBSD)允许在文件系统首次被挂载系统调用引用时动态加载文件系统。VNode 接口在第 6.5 节中描述;它的辅助支持例程在第 6.6 节中描述;几个特殊目的的文件系统在第 6.7 节中描述。
常规文件是一组线性字节数组,可以从文件中的任意字节开始进行读取和写入。内核在常规文件中不区分记录边界,尽管许多程序将换行符视为区分行尾的字符,而其他程序可能会施加其他结构。文件本身没有关于文件的与系统相关的信息,但文件系统会随每个文件存储少量所有权、保护和使用信息。
文件名组件是一个长达 255 个字符的字符串。这些文件名存储在称为目录的文件类型中。关于文件的目录中的信息称为目录条目,除了文件名外,还包括指向文件本身的指针。目录条目可能会指向其他目录,也可能指向普通文件。因此形成目录和文件的层次结构,称为文件系统。
一个小的文件系统
一个小的文件系统中显示了一个。目录可以包含子目录,并且目录嵌套的深度没有固有限制。为了保护文件系统的一致性,内核不允许进程直接写入目录。文件系统不仅可以包括普通文件和目录,还可以包括对其他对象的引用,如设备和套接字。
文件系统形成一棵树,其开始是根目录,有时候被称为斜杠,用单个斜杠字符(/)拼写。根目录包含文件;在我们的示例中,在图 2.2 中,它包含 vmunix ,一个内核可执行对象文件的副本。它还包含目录;在这个示例中,它包含 usr 目录。在 usr 目录中是 bin 目录,其中主要包含可执行程序的可执行对象代码,例如文件 ls 和 vi 。
一个进程通过指定文件的路径名来标识文件,该路径名是由零个或多个文件名组成,这些文件名之间用斜杠(/)字符分隔的字符串。内核为每个进程关联两个目录,用于解释路径名。进程的根目录是进程可以访问的文件系统中最顶层的点;通常设置为整个文件系统的根目录。以斜杠开头的路径名称为绝对路径名,并由内核从进程的根目录开始解释。
不以斜杠开头的路径名称为相对路径名,并相对于进程的当前工作目录进行解释。(这个目录也被称为更短的名称当前目录或工作目录。)当前目录本身可以直接用点的名称引用,用单个句点拼写( . )。文件名点点( .. )指的是目录的父目录。根目录是其自身的父目录。
进程可以使用 chroot 系统调用设置其根目录,并使用 chdir 系统调用设置其当前目录。任何进程都可以随时执行 chdir,但只有具有超级用户特权的进程才允许执行 chroot。Chroot 通常用于设置对系统的受限访问。
使用图 2.2 中显示的文件系统,如果进程将文件系统的根目录作为其根目录,并且将 /usr 作为其当前目录,则它可以从根目录使用绝对路径名 /usr/bin/vi 引用文件 vi ,或者从其当前目录使用相对路径名 bin/vi 引用。
系统实用程序和数据库保存在某些众所周知的目录中。定义明确的层次结构的一部分包括一个目录,该目录包含每个用户的主目录——例如,图 2.2 中的 /usr/staff/mckusick 和 /usr/staff/karels 。当用户登录时,其shell的当前工作目录被设置为主目录。在他们的主目录中,用户可以像创建常规文件一样轻松地创建目录。因此,用户可以构建任意复杂的子层次结构。
用户通常只知道一个文件系统,但系统可能知道这个虚拟文件系统实际上由几个物理文件系统组成,每个物理文件系统都在不同的设备上。物理文件系统不能跨多个硬件设备。由于大多数物理磁盘设备被划分为多个逻辑设备,每个物理设备可能有多个文件系统,但每个逻辑设备不会有多个文件系统。有一个文件系统——锚定所有绝对路径名的文件系统——称为根文件系统,并且始终可用。其他文件系统可以挂载;也就是说,它们可以集成到根文件系统的目录层次结构中。对挂载了文件系统的目录的引用会被内核透明地转换为对挂载文件系统的根目录的引用。
链接系统调用将现有文件的名称和另一个要为该文件创建的名称传入。成功链接后,文件可以通过任一文件名访问。可以使用取消链接系统调用来删除文件名。当文件的最终名称被删除(并且拥有该文件打开的最终进程关闭它时),文件将被删除。
文件在目录中按层次方式组织。目录是一种文件类型,但与常规文件不同,系统对目录施加了结构。进程可以像普通文件一样读取目录,但只有内核可以修改目录。目录是通过 mkdir 系统调用创建的,并通过 rmdir 系统调用删除。在 4.2BSD 之前,mkdir 和 rmdir 系统调用是通过一系列链接和取消链接系统调用来实现的。添加系统调用专门用于创建和删除目录有三个原因:
此操作可以变为原子。如果系统崩溃,目录将不会像使用一系列链接操作时可能发生的那样留下一半构建的状态。
当运行网络文件系统时,需要原子地指定文件和目录的创建和删除,以便它们可以被串行化。
当支持非 UNIX 文件系统(例如 MS-DOS 文件系统)在磁盘的另一个分区上时,其他文件系统可能不支持链接操作。尽管其他文件系统可能支持目录的概念,但它们可能不会像 UNIX 文件系统那样使用链接来创建和删除目录。因此,它们只能在提供显式目录创建和删除请求时创建和删除目录。
chown 系统调用设置文件的所有者和组,chmod 更改保护属性。应用于文件名的 stat 可用于读取文件的这些属性。fchown、fchmod 和 fstat 系统调用应用于描述符,而不是文件名,以执行相同的操作。rename 系统调用可用于在文件系统中为文件指定新名称,替换文件的旧名称之一。与目录创建和目录删除操作一样,rename 系统调用被添加到 4.2BSD 中,以在本地文件系统中为名称更改提供原子性。后来,它被证明可以将重命名操作明确地导出到外部文件系统和网络上。
4.2BSD 添加了截断系统调用,允许文件被缩短到任意偏移量。该调用主要是为了支持 Fortran 运行时库而添加的,该库的语义是随机访问文件的末尾被设置为程序最近访问该文件的位置。如果没有截断系统调用,缩短文件的唯一方法是将所需部分复制到新文件中,删除旧文件,然后将副本重命名为原始名称。除了这种算法速度慢之外,库在满文件系统上可能会失败。
一旦文件系统具有缩短文件的能力,内核就利用该能力来缩短大型空目录。缩短空目录的优势在于减少内核在创建或删除名称时搜索它们所花费的时间。
新创建的文件被分配给创建它们的进程的用户标识符和创建它们的目录的组标识符。为了保护文件提供了三级访问控制机制。这三个级别指定了文件对用户、组和其他用户的可访问性。
拥有文件的用户
拥有文件的组
其他所有人
每个访问级别都有单独的指示器,用于读取权限,写入权限和执行权限。
文件创建时长度为零,当写入时可能会增长。当文件打开时,系统会保持指向文件的指针,指示与描述符关联的文件中的当前位置。该指针可以以随机访问方式在文件中移动。通过 fork 或 dup 系统调用共享文件描述符的进程共享当前位置指针。通过独立的打开系统调用创建的描述符具有单独的当前位置指针。文件中可能有空洞。空洞是文件线性范围中的空白区域,从未写入数据。进程可以通过将指针定位到当前文件末尾并写入来创建这些空洞。在读取时,系统将空洞视为零值字节。
早期的 UNIX 系统每个文件名组件的限制为 14 个字符。这种限制通常是一个问题。例如,除了用户希望为文件提供长描述性名称的自然愿望外,形成文件名的常见方式是 basename.extension ,其中扩展名(表示文件类型,例如 C 源文件的 .c 或中间二进制对象的 .o )为一到三个字符,为基本名称留下 10 到 12 个字符。源代码控制系统和编辑器通常占用另外两个字符,作为前缀或后缀,用于它们的目的,剩下八到十个字符。很容易使用一个英文单词的 10 到 12 个字符作为基本名称(例如, multiplexer )。
在这些限制范围内保持是可能的,但是不方便甚至危险,因为其他 UNIX 系统在创建文件时接受超过限制的字符串,然后截断到限制。一个名为 multiplexer.c (已有 13 个字符)的 C 语言源文件可能有一个源代码控制文件,其中包含 s. ,生成一个与 multiplexer.ms 中包含 troff C 程序文档的源代码控制文件不可区分的文件名 s.multiplexer 。这两个原始文件的内容很容易混淆,源代码控制系统不会发出任何警告。仔细编码可以检测到这个问题,但 4.2BSD 中首次引入的长文件名几乎消除了这个问题。
为本地文件系统定义的操作分为两部分。所有本地文件系统共有的特性包括分层命名、锁定、配额、属性管理和保护。这些特性与数据存储方式无关。4.4BSD 有一个单一的实现来提供这些语义。
本地文件系统的另一部分是对存储介质上数据的组织和管理。在存储介质上布置文件内容是文件存储的责任。4.4BSD 支持三种不同的文件存储布局:
传统的伯克利快速文件系统
基于 Sprite 操作系统设计[Rosenblum&Ousterhout,1992]的日志结构文件系统
基于内存的文件系统
尽管这些文件存储的组织完全不同,但对于使用文件存储的进程来说,这些差异是无法区分的。
快速文件系统将数据组织成柱面组。根据文件系统层次结构中它们的位置,可能会一起访问的文件存储在同一个柱面组中。不太可能一起访问的文件被移动到不同的柱面组中。因此,同时写入的文件可能会被放置在磁盘的不同位置。
日志结构化文件系统将数据组织为日志。任何时候所写入的所有数据都会被聚集在一起,并写入相同的磁盘位置。数据永远不会被覆盖;而是写入一个替换旧文件的新副本。当文件系统变满且需要额外的空闲空间时,将通过垃圾回收进程来回收旧文件。
基于内存的文件系统旨在将数据存储在虚拟内存中。它用于需要支持快速但临时数据的文件系统,比如 /tmp 。基于内存的文件系统的目标是尽可能紧凑地保持存储,以最小化虚拟内存资源的使用。
最初,网络用于在一台机器与另一台机器之间传输数据。后来,它发展到允许用户远程登录到另一台机器。下一个合乎逻辑的步骤是将数据带给用户,而不是让用户去找数据 - 网络文件系统就此诞生。本地工作的用户在每次按键时不会遇到网络延迟,因此他们拥有更加响应迅捷的环境。
将文件系统带到本地计算机是最重要的主要客户端 - 服务器应用程序之一。服务器是远程机器,导出其一个或多个文件系统。客户端是导入这些文件系统的本地机器。从本地客户端的角度来看,远程挂载的文件系统会出现在文件树命名空间中,就像任何其他本地挂载的文件系统一样。本地客户端可以切换到远程文件系统中的目录,并可以读取、写入和执行该远程文件系统中的二进制文件,方式与他们在本地文件系统上执行这些操作的方式完全相同。
当本地客户端在远程文件系统上执行操作时,请求被打包并发送到服务器。服务器执行所请求的操作,并返回已请求的信息或指示请求被拒绝的错误。为了获得合理的性能,客户端必须缓存频繁访问的数据。远程文件系统的复杂性在于维护服务器及其众多客户端之间的缓存一致性。
多年来,虽然开发了许多远程文件系统协议,但在 UNIX 系统中最普遍使用的协议是网络文件系统(NFS),其协议和最广泛使用的实现是由 Sun Microsystems 完成的。4.4BSD 内核支持 NFS 协议,尽管该实现是独立于协议规范完成的[Macklem, 1994]。NFS 协议在第 9 章中描述。
终端支持标准系统 I/O 操作,以及一系列特定于终端的操作,用于控制输入字符编辑和输出延迟。最底层是控制硬件终端ports的终端设备驱动程序。终端输入根据底层通信特性处理,比如波特率,和一组软件可控参数,比如奇偶校验。
位于终端设备驱动程序之上的是提供各种程度字符处理的线路纪律。当用于交互式登录时,将选择默认行纪律。行纪律以规范模式运行;输入经过处理以提供标准的面向行的编辑功能,并且将输入逐行呈现给进程。
屏幕编辑器和与其他计算机通信的程序通常以非规范模式运行(也常称为原始模式或逐字符模式)。在此模式下,输入立即传递给读取进程且不进行解释。所有特殊字符输入处理被禁用,不执行擦除或其他行编辑处理,并且所有字符都传递给从终端读取的程序。
可以在这两个极端之间的数千种组合中配置终端。例如,一个希望异步接收用户中断的屏幕编辑器可能会启用生成信号的特殊字符并启用输出流控制,但否则运行在非规范模式;所有其他字符将会未经解释地传递到进程。
输出时,终端处理程序提供简单的格式化服务,包括
将换行符字符转换为两个字符的回车换行序列
在某些标准控制字符后插入延迟
扩展制表符
将回显的非图形 ASCII 字符显示为形式为 ^C 的两个字符序列(即 ASCII 插入符字符后跟 ASCII 字符,该字符是与 ASCII @ 字符的值偏移的字符)。
通过控制请求,这些格式化服务中的每一个都可以被进程单独禁用。
FreeBSD 中的进程间通信是通过通信域进行组织的。目前支持的域包括本地域,用于在同一台机器上执行的进程之间的通信;互联网域,用于使用 TCP/IP 协议套件进行进程之间的通信(可能在 Internet 内);ISO/OSI 协议族,用于需要运行它们的站点之间的通信;以及 XNS 域,用于使用 XEROX Network Systems(XNS)协议进行进程之间的通信。
在一个域内,通信发生在被称为套接字的通信端点之间。如第 2.6 节所述,套接字系统调用创建一个套接字并返回一个描述符;其他 IPC 系统调用在第 11 章中描述。每个套接字都有一个定义其通信语义的类型;这些语义包括可靠性、排序和防止消息重复等属性。
每个套接字都与通信协议相关联。该协议根据套接字的类型提供后者所需的语义。应用程序在创建套接字时可以请求特定协议,也可以允许系统选择适合正在创建的套接字类型的协议。
套接字可能绑定地址。套接字地址的形式和含义取决于创建套接字的通信域。在本地域中将名称绑定到套接字会导致在文件系统中创建文件。
通过套接字传输和接收的普通数据是无类型的。数据表示问题是建立在进程间通信设施之上的库的责任。除了传输普通数据外,通信域还可以支持传输和接收特殊类型的数据,称为访问权限。例如,本地域使用此功能在进程之间传递描述符。
UNIX 4.2BSD 之前的 UNIX 上的网络实现通常是通过对字符设备接口进行重载来实现的。套接字接口的一个目标是使得天真的程序能够在流式连接上无需改变即可工作。如果读取和写入系统调用未更改,则此类程序可以正常工作。因此,原始接口保持不变,并被设计成适用于流类型套接字。为更复杂的套接字增加了一个新接口,例如那些用于发送数据报的套接字,在每个发送调用中必须提供目标地址。
另一个好处是新接口非常易于移植。在伯克利提供了测试版本后不久,UNIX 供应商将套接字接口移植到了 System III(尽管 AT&T 直到 System V Release 4 发布时才支持套接字接口,而决定使用第八版流机制)。套接字接口还被移植到了许多以太网板上,如 Excelan 和 Interlan 这样的供应商,它们在 PC 市场上销售,在那些机器上网络功能过于庞大,无法在主处理器上运行。最近,套接字接口被用作微软 Windows 的 Winsock 网络接口的基础。
套接字 IPC 机制支持的一些通信领域提供对网络协议的访问。这些协议作为内核中逻辑上位于套接字软件之下的单独软件层实现。内核提供许多辅助服务,如缓冲区管理,消息路由,协议的标准化接口,以及用于各种网络协议的网络接口驱动程序的接口。
在实施 4.2BSD 时,有许多正在使用或正在开发的网络协议,每种协议都有其优势和劣势。没有明显优越的协议或协议套件。通过支持多种协议,4.2BSD 可以在伯克利环境中提供互操作性和资源共享,因为那时有各种各样的计算机。多协议支持还为未来的变化提供了可能性。今天设计用于 10 到 100 兆位每秒以太网的协议可能不足以满足明天设计用于 1 到 10 吉位每秒光纤网络的需求。因此,网络通信层被设计为支持多种协议。新协议可以添加到内核中,而不会影响对旧协议的支持。旧应用程序可以继续使用旧协议在与运行新网络协议的应用程序相同的物理网络上运行。
在 4.2BSD 中实施的第一个协议套件是 DARPA 的传输控制协议/互联网协议(TCP/IP)。CSRG 选择 TCP/IP 作为首个集成到套接字 IPC 框架中的网络,因为基于 4.1BSD 的实现可以从 DARPA 赞助的项目(Bolt,Beranek 和 Newman(BBN))中公开获得。那是一个有影响力的选择:4.2BSD 实现是这个协议套件被广泛使用的主要原因。对 TCP/IP 实现的后续性能和功能改进也被广泛采纳。 TCP/IP 实现在第 13 章中有详细描述。
4.3BSD 的发布添加了施乐网络系统(XNS)协议套件,部分建立在马里兰大学和康奈尔大学进行的工作基础上。需要此套件来连接无法使用 TCP/IP 进行通信的孤立机器。
4.4BSD 的发布添加了 ISO 协议套件,因为在美国境内外后者越来越可见。由于 ISO 协议定义了略有不同的语义,因此需要对套接字接口进行一些微小的更改以适应这些语义。这些更改是如此之处,以至于对其他现有协议的客户端是不可见的。ISO 协议还需要对 4.3BSD 内核提供的两级路由表进行广泛添加。4.4BSD 的大大扩展的路由功能包括具有可变长度地址和网络掩码的任意级别的路由。
引导机制用于启动系统运行。首先,必须将 4.4BSD 内核加载到处理器的主内存中。一旦加载完成,它必须经过初始化阶段将硬件设置为已知状态。接下来,内核必须进行自动配置,一个过程,找到并配置连接到处理器的外围设备。系统在单用户模式下开始运行,同时启动脚本进行磁盘检查并启动会计和配额检查。最后,启动脚本启动一般系统服务,将系统带到完全的多用户操作。
在多用户操作期间,进程在配置为用户访问的终端线路和网络ports上等待登录请求。当检测到登录请求时,将生成一个登录进程并进行用户验证。当登录验证成功时,将从中创建一个登录shell,用户可以从中运行其他进程。
Accetta 等人,1986 年 Mach:UNIX 开发的新内核基础" M.Accetta R.Baron W.Bolosky D.Golub R.Rashid A.Tevanian M.Young 93-113 USENIX 协会会议论文集 USENIX 协会 1986 年 6 月
Cheriton,1988 年 V 分布式系统 D. R.Cheriton 314-333 Comm ACM,31,3 1988 年 3 月
Ewens 等,1985 年突尼斯:分布式多处理器操作系统 P.Ewens D.R.Blythe M.Funkenhauser R.C.Holt 247-254 USENIX Association Conference Proceedings USENIX Association 1985 年 6 月
Gingell 等,1987 年 SunOS 中的虚拟内存体系结构 R.Gingell J.Moran W.Shannon 81-94 USENIX Association Conference Proceedings USENIX Association 1987 年 6 月
Kernighan&Pike,1984 年 UNIX 编程环境 B.W.Kernighan R.Pike Prentice-Hall Englewood Cliffs NJ 1984
Macklem,1994 年 4.4BSD NFS 实现 R.Macklem 6:1-14 4.4BSD 系统管理手册 O’Reilly&Associates,Inc. Sebastopol CA 1994
McKusick&Karels,1988 年为 4.3BSD UNIX 内核设计通用内存分配器 M. K.McKusick M. J.Karels 295-304 USENIX 协会会议论文集 USENIX 协会 1998 年 6 月
McKusick 等人,1994 年伯克利软件架构手册,4.4BSD 版 M. K.McKusick M. J.Karels S. J.Leffler W. N.Joy R. S.Faber 5:1-42 4.4BSD 程序员补充文档 O’Reilly&Associates,Inc. Sebastopol CA 1994
里奇,1988 年早期内核设计私人通信 D.M.Ritchie 1988 年 3 月
Rosenblum&Ousterhout,1992 年一种基于日志结构的文件系统的设计和实现 M.Rosenblum K.Ousterhout 26-52 ACM 计算机系统交易,10,1 计算机协会协会 1992 年 2 月
Rozier 等,1988 年 Chorus 分布式操作系统 M.Rozier V.Abrossimov F.Armand I.Boule M.Gien M.Guillemont F.Herrmann C.Kaiser S.Langlois P.Leonard W.Neuhauser 305-370 USENIX 计算系统,1,4 1988 年秋季
Tevanian, 1987 并行和分布式环境中的架构无关虚拟内存管理:Mach 方法 技术报告 CMU-CS-88-106,A.Tevanian 计算机科学系,卡内基梅隆大学,匹兹堡 PA,1987 年 12 月