操作系统笔记
1. 概述
操作系统 ( $Operating\ \ System$, $OS$ ) 是指控制和管理整个计算机系统的硬件和软件资源,以提供给用户和其他软件方便的接口和环境的程序集合,是计算机系统中最基本的系统软件。没有任何软件支持的计算机称为裸机。裸机在最里层,外面是操作系统。
操作系统是计算机系统资源的管理者:
- 处理机管理:处理机的分配和运行以进程(或线程)为基本单位,因为对处理机的管理可以归结为对进程的管理;
- 存储器管理:对内存的管理;
- 文件管理:文件系统;
- 设备管理:
I/O
。
操作系统还提供了用户接口:
- 命令接口:使用命令接口进行作业控制的方式有两种:联机控制方式和脱机控制方式。进一步的,按照控制方式,可以将命令接口分为联机命令接口和脱机命令接口。
- 联机命令接口适用于分时或实时系统的接口,由一组键盘操作命令组成。用户通过控制台或者终端输入命令;
- 脱机命令接口又称批处理命令接口,适用于批处理系统,由一组作业控制命令组成,用户不能直接干预作业运行,实现用相应的作业控制命令做成一份作业操作说明书,连同作业一起提交给系统。
- 程序接口由一组系统调用组成,用户通过在程序中使用这些系统调用命令来请求操作系统为其提供服务。
计算机系统中,通常CPU
执行两种不同性质的程序:一种是操作系统内核程序,另一种是用户自编程序或者系统外层的应用程序。对于操作系统而言,前者是后者的管理者。内核程序可以执行一些特权指令,如I/O
、中断、管理程序状态字寄存器等,出于安全考虑,这些程序不能被用户直接使用。操作系统在实现上划分了核心态(管态)和用户态(目态)以严格区分两类程序。
内核是计算机上配置的底层软件,大多数操作系统的内核包括四个方面的内容:
- 时钟管理:时钟的第一功能是计时,通过时钟可以提供系统时间。此外,通过时钟中断,也可以实现进程切换;
- 中断机制:现代操作系统是靠中断驱动的软件。中断机制中,只有一小部分功能属于内核,负责保护和恢复中断现场信息,转移控制权,这样可以减少中断的处理时间;
- 原语 ( $Atomic\ \ Operation$ ) :原语是一些可以被调用的小程序,处于操作系统的最底层,是最接近硬件的部分,具有原子性,且运行时间短、调用频繁;
- 系统控制的数据结构及处理:操作系统中存在许多记录状态信息的数据结构,如作业控制块、进程控制块、内存分配表等,操作系统需要一些对这些数据结构进行管理的基本操作。
从上述内容可知,核心态指令包括系统调用类指令和一些针对时钟、中断和原语的操作指令。
操作系统不允许用户程序实现核心态的功能,而它们又必须使用这些功能。因此,需要实现核心态和用户态之间的转换。在实际操作系统中,CPU
运行上层程序时的唯一转换途径是通过中断或异常。当中断或异常发生时,运行用户态的CPU
会立即进入核心态,这是通过硬件实现的(例如用一个特殊寄存器表示)。
中断 ( $Interruption$ ) 指来自CPU
执行指令以外的事件的发生,如设备I/O
中断、时钟中断等。访管指令是一条可以在用户态下执行的指令,用于产生一个中断,称为访管中断,系统会根据访管指令的操作数执行对应的访管中断处理程序。异常 ( $Exception$ ),也称为陷入 ( $Trap$ ) ,指来自CPU
执行指令内部的事件的发生,如程序的非法操作码、地址越界、内存缺页以及专门的陷入指令等。对异常的处理一般要依赖于当前程序的运行现场,并且异常不能被屏蔽,一旦出现应立即处理。
系统调用是用户在程序中调用操作系统提供的一些子功能,可以把系统调用看作是特殊的公共子程序。在用户程序中,凡是与资源有关的操作,都必须通过系统调用向操作系统提出服务请求,并由操作系统代为完成。系统调用大致可以分为如下几类:
- 设备管理:设备请求、启动以及释放等;
- 文件管理:对文件的读写、创建和删除等;
- 进程控制:对进程的创建、销毁、阻塞和唤醒等;
- 进程通信:进程之间的消息传递;
- 内存管理:对内存的分配、回收以及获取内存区大小和地址等。
用户通过操作系统运行上层程序,上层程序依赖于操作系统的底层管理程序提供服务支持。当需要管理程序服务时,系统通过硬件中断机制进入核心态;当程序运行出现异常时,系统通过异常处理机制进入核心态。当管理程序结束时,用户程序继续运行,通过之前中断处理程序或者异常处理程序保存的中断现场,返回断点处继续执行。
2. 进程和线程
2.1 进程
在多道程序环境下,多个程序可以并发执行,为此引入了进程 ( $Process$ )。为了使参与并发执行的程序能够独立地执行,操作系统为之配置了一个专门的数据结构称为进程控制块 ( $Process\ \ Control\ \ Block$, $PCB$ )。系统利用PCB
来描述进程的基本情况和运行状态,进而实现对进程的控制和管理。程序段、相关数据段和PCB
构成了进程实体,进程实体是静态的,但是进程是动态的。进程创建实质上是创建PCB
,撤销实质上是撤销PCB
,PCB
是进程存在的唯一标志。PCB
通常包含以下内容:
进程描述信息 | 进程控制和管理信息 | 资源分配清单 | 处理机相关信息 |
---|---|---|---|
进程标识符 ( PID ) |
进程当前状态 | 代码段指针 | 通用寄存器值 |
用户标识符 ( UID ) |
进程优先级 | 数据段指针 | 地址寄存器值 |
代码运行入口地址 | 堆栈段指针 | 控制寄存器值 | |
程序的外存地址 | 文件描述符 | 标志寄存器值 | |
进入内存时间 | 键盘 | 状态字 | |
处理机占用时间 | 鼠标 | ||
信号量使用 |
- 进程描述信息:进程标识符用于唯一标识一个进程,用户标识符用于表示进程归属的用户;
- 进程控制和管理信息:进程当前状态用于描述进程的状态信息,作为处理机分配调度的依据;进程优先级可以保证优先级高的进程优先获得处理机;
- 资源分配清单:说明有关内存地址空间或虚拟地址空间的状况,打开文件列表和所使用的
I/O
设备的信息; - 处理机相关信息:处理机中各个寄存器的值,当进程切换发生时,处理机的状态信息都会被保存在
PCB
中。
进程通常有五种状态:
- 运行:进程在处理机上运行;
- 就绪:进程处于准备运行的状态;
- 阻塞:进程正在等待某一事件而暂停运行;
- 创建:进程正在被创建,创建完成后进入就绪状态;
- 结束:进程正在从系统中消失,系统会先将进程设置为结束状态,再处理资源释放和回收等工作。
进程可以创建另一个进程,此时创建者称为父进程,子进程可以继承父进程所拥有的资源。操作系统创建一个进程的过程如下(创建原语):
- 为新进程分配一个唯一的进程标识号,并申请一个空白的
PCB
(PCB
数量是有限的),申请失败则创建失败; - 为进程分配资源,包括为程序和数据、以及用户栈分配内存(在
PCB
中体现),如果资源不足,会进入阻塞状态; - 初始化
PCB
,包括初始化标志信息、处理机状态信息、处理机控制信息以及进程优先级等; - 如果就绪队列未满,将新进程插入就绪队列中,等待时间片调度。
正在执行的进程如果某些期待的事件未发生,如请求系统资源失败、等待操作完成,系统会执行阻塞原语,将进程由运行状态变为阻塞状态。进程的阻塞是一种主动行为,只有处于运行中的进程才能转为阻塞状态。阻塞原语的执行过程如下:
- 找到进程标识号对应的
PCB
; - 保护进程运行现场,将其转为阻塞状态;
- 将该
PCB
插入到相应事件的等待队列中。
唤醒原语的执行过程如下:
- 在等待队列中找到相应进程的
PCB
; - 将
PCB
从等待队列中移出,将其转为就绪状态; - 将
PCB
插入就绪队列中,等待调度程序调度。
引起进程终止的事件有:正常结束,即完成任务后的准备退出运行,和异常结束,即运行过程中发生异常事件导致程序无法继续运行。终止进程的过程如下:
- 找到进程标识号对应的
PCB
,读取进程状态; - 如果进程处于执行状态,终止进程执行;
- 若进程存在子进程,终止其所有子进程的执行;
- 将进程拥有的资源归还给父进程或者操作系统;
- 将
PCB
从所在队列的链表中删除。
通常进程的创建、撤销以及要求系统设备完成I/O
操作都是利用系统调用进入内核,再由内核中相应的处理程序完成的。进程切换也是在内核的支持下完成的,所以进程是与内核紧密相关的。进程切换的过程如下:
- 保存处理机上下文,包括程序计数器和其他寄存器;
- 更新
PCB
- 把
PCB
移入相应的队列,如就绪队列或者某个事件的阻塞队列; - 找到下一个要执行的进程,更新
PCB
; - 更新内存管理的数据结构;
- 恢复处理机上下文。
进程间通信是指进程之间的信息交换,PV
操作是低级通信方式,而高级通信方式能够以较高速率传输大量数据。高级通信方法主要有以下三种:
- 共享存储:进程间一块可以直接访问的共享空间,通过同步互斥工具控制读写,实现信息交换。低级的共享方式是基于数据结构的共享,而高级的共享方式是基于存储区的共享,操作系统负责提供共享空间和互斥工具,用户负责实现读写指令;
- 消息传递:进程间的数据交换以消息为单位,系统提供发送和接受两个原语。根据是否存在中间件,可以分为直接和间接两种方式。直接通信方式直接把消息发送到接收进程的消息缓冲队列上,间接通信方式则把消息发送到某个中间实体中,典型代表是电子邮箱;
- 管道通信:管道用于连接一个读进程和一个写进程,写进程以字符流形式将大量数据送入管道,读进程则从管道中读取数据。管道必须提供三方面的协调能力:互斥、同步和确定对方存在。
2.2 线程
线程可以减小程序在并发执行时付出的开销,提高操作系统的并发性能。线程的最直接理解就是“轻量级进程”,它是CPU
的基本执行单元,是程序执行的最小单元,由线程ID
、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是系统独立调度和分派的基本单位,只拥有一些运行中必不可少的资源,共享其所在进程的全部资源。线程可以创建和撤销另一个线程,同一个进程中的线程可以并发执行。线程具有就绪、阻塞和运行三个状态。在多线程操作系统中,线程作为独立运行(或调度)的基本单位。此时,进程的执行实际上指的是进程中的某个线程正在执行。
线程的引入提高了系统的并发性和吞吐量。并且由于线程只持有一些必不可少的资源,所以在创建和撤销过程中的系统开销更小。类似地,在进行进程切换时,要涉及到当前进程CPU
环境的保存以及新调度进程CPU
环境的设置,而线程就只需要保存和设置少量寄存器内容即可。并且由于线程共享进程资源,所以线程之间的同步和通信非常容易实现。
线程的实现可以分为两类:用户级线程 ( $User-Level\ \ Thread$, $ULT$ ) 和内核级线程 ( $Kernel-Level\ \ Thread$, $KLT$ )。用户级线程中,有关线程管理的所有工作都交由应用程序完成,内核察觉不到线程的存在。内核级线程中,线程管理的所有工作都交由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息。有些系统同时支持用户级线程和内核级线程,由此产生了不同的线程模型:
- 一对一模型:将每个用户级线程映射到一个内核级线程。优点:当前线程阻塞后允许另一个线程继续运行,并发能力较强;缺点:每个用户级线程都需要创建一个内核级线程与之对应,创建线程的开销较大;
- 多对一模型:将多个用户级线程映射到一个内核级线程。优点:线程管理在用户空间完成,效率较高;缺点:当内核服务被阻塞时,整个进程都会被阻塞,并且多线程不能在多处理机上并行运行;
- 多对多模型:将 $n$ 个用户级线程映射到 $m$ 个内核级线程上,要求 $m \le n$ 。既克服了多对一并发度不高的缺点又克服了一对一开销较大的缺点。
2.3 调度
在多道程序系统中,进程的数量往往多于处理机的个数。处理机调度是对处理机进行分配,就是从就绪队列中按照某个算法选择一个进程并将处理机分配给它运行。一个作业从提交开始直到完成,往往要经历以下三级调度:
- 作业调度:又称高级调度。按照一定的原则从外存上处于后备状态的作业中选择一个(或多个)作业,给它们分配内存、
I/O
设备等资源,并建立相应的进程,以使它们获得竞争处理机的权利。简单来讲就是内存与辅存之间的调度,对于每个作业只调入一次,调出一次; - 内存调度:又称中级调度。将一些暂时不能运行的进程调至外存等待,将进程设为挂起状态。当它们具备运行条件时,再重新调回内存,并设为就绪状态,在就绪队列上等待;
- 进程调度:又称低级调度。按照某个算法从就绪队列中选择一个进程,并分配处理机,是操作系统中最基本的调度。
系统内核负责进程调度和进程切换,当请求调度事件发生后,才会运行进程调度程序,当调度了新的就绪进程后,才会进行进程切换。通常有两种进程调度方式:
- 非抢占:当一个进程正在执行时,如果有另一个优先级更高的进程进入就绪队列,直到当前进程完成或者进入阻塞状态时才能把处理机分配给优先级更高的进程;
- 抢占:当一个进程正在处理机上运行时,如果有另一个优先级更高的进程进入就绪队列,会立即暂停当前进程,将处理机分配给优先级更高的进程。
操作系统中存在多种调度算法,有的调度算法适用于作业调度,有些适用于进程调度,还有的两者都适用。
2.3.1 FCFS
先来先服务 ( $FCFS$ ) 调度算法是最简单的调度算法,既适用于作业调度也适用于进程调度。该算法会每次从后备作业队列或者进程就绪队列中选择最先进入该队列的一个或几个作业/进程,为它们分配资源。该算法属于非抢占算法,虽然实现简单,但是效率低,对长作业有利,适合CPU
繁忙型作业,不利于I/O
繁忙型作业。
2.3.2 SJF
/SPF
最短作业优先 ( $SJF$ )/ 最短进程优先 ( $SPF$ ) 调度算法是对短作业/进程优先调度的算法,即每次从后备队列/就绪队列中选择一个或几个估计运行时间最短的作业,为它们分配资源。该算法对长作业不利,容易出现饥饿现象,但是平均等待时间、平均周转时间最少。
2.3.3 优先级调度算法
优先级调度算法会每次从后备队列/优先队列中选择一个或几个优先级最高的作业/进程,并为它们分配资源。根据新的更高优先级进程能否抢占正在执行的进程,可以将调度算法分为非抢占式优先级调度算法和抢占式优先级调度算法。根据进程创建后优先级是否可以改变,可以将进程优先级分为静态优先级和动态优先级。
2.3.4 高响应比优先调度算法
高响应比优先调度算法适用于作业调度,在每次进行作业调度时,先计算后备作业中每个作业的响应比,从中选出响应比最高的作业运行。计算公式为:
$$ 响应比R_{\rho} = \frac{等待时间+要求服务时间}{要求服务时间} $$
2.3.5 时间片轮转调度算法
时间片轮转调度算法适用于分时系统,系统会将所有就绪进程按照达到时间的先后顺序排成一个队列,按照FCFS
原则,从队列中选择一个进程分配一个时间片。在时间片结束后,进程需要释放处理机给下一个就绪的进程,被剥夺的进程会返回到就绪队列的末尾重新排队。
2.3.6 多级反馈队列调度算法
多级反馈队列调度算法设置多个就绪队列,并为各个队列设置不同的优先级,同时每个队列中进程执行时间片的大小也不同,优先级更高的队列时间片越小。每当一个新进程进入内存后,会先将它放入一级队列的末尾,按照时间片轮转算法调度。如果一个进程未在时间片结束后完成,将会进入二级队列的末尾……如此下去,会一直降到 $n$ 级队列。只有上级队列为空的时候,调度程序才会调度下级队列中的进程。多级反馈队列调度算法既可以兼顾多方面的系统目标,也不需要事先估计进程的执行时间。
2.4 同步
虽然多个进程可以共享系统中的各种资源,但是许多资源一次只能被一个进程使用,我们称之为临界资源,如打印机等。对于临界资源的访问必须互斥进行,访问临界资源的代码称为临界区。同步是指为了完成某种任务而建立的两个或多个进程,因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系就是它们之间的相互合作。互斥亦称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,直到当前进程退出临界区。
临界区互斥的基本实现方法分为软件实现方法和硬件实现方法。软件实现方法通过一个或多个标志位判断是否存在其他进程正在访问临界区。硬件实现方法需要通过计算机提供的特殊硬件指令实现,该指令允许对一个字中的内容进行检测和修正,或者对两个字的内容进行交换等,典型的有中断屏蔽法,即在进入临界区时屏蔽中断(CPU
只在发生中断时进行进程切换),或者通过 $TestAndSet$ 和 $CompareAndSwap$ 这些硬件指令实现。
信号量机制可以用来解决互斥与同步的问题,只能被两个标准原语 $wait$ 和 $signal$ 来访问,也可以记为 $P$ 操作和 $V$ 操作。
管程是由一组数据以及定义在数据之上的对这组数据的操作组成的软件模块,这组操作能初始化并改变管程中的数据和同步进程。管程由三部分组成:
- 局部于管程的共享结构数据说明;
- 对该数据结构进行操作的一组过程;
- 对局部于管程的共享数据设置初始值的语句。
局部于管程的数据只能被局部于管程的过程所访问。一个进程只有通过调用管程内的过程才能进入管程访问共享数据,且每次只允许一个进程在管程内执行某个内部过程。
死锁是指多个进程因竞争资源造成的互相等待。死锁产生的必要条件有:
- 互斥:一段时间内某个资源只被一个进程占有;
- 非抢占:进程在获得资源且未使用完毕时,不能被其他进程抢占;
- 请求并保持:进程已经持有了至少一个资源并提出新请求,同时不释放已经持有的资源;
- 循环等待:存在进程资源的循环等待链。
要防止死锁,需要破坏上述四个条件中的任意一个,或者能够检测死锁的发生并进行恢复。破坏产生死锁的四个必要条件的方式称为死锁预防,在资源分配的过程中用某种方式防止系统进入不安全状态称为死锁避免,而通过检测机制及时检测死锁发生然后采取措施称为死锁检测。死锁预防和死锁避免属于事先预防策略,实现起来较简单,但是效率低。而死锁避免的实现较为复杂。
死锁预防只需要破坏死锁产生的四个必要条件之一即可:
- 破坏互斥:系统的所有资源都能共享使用,但是这种方式不太可行;
- 破坏非抢占:当一个进程在请求新资源失败时,必须释放已经保持的资源。该策略实现起来较复杂,因为释放资源可能会造成前一阶段的工作失效,而且反复地获取和释放会增加系统开销,常用于状态易保存和恢复的资源,如
CPU
寄存器和内存资源; - 破坏请求并保持:采用预先静态分配的方法,进程在运行前一次性申请所有需要的资源。该策略实现起来简单,但是会造成系统资源的严重浪费,还可能带来饥饿现象;
- 破坏循环等待:采用顺序资源分配法,规定进程必须按照一定顺序申请资源。但是这种方法限制了新设备的增加,而且也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,从而造成系统资源的浪费。
死锁避免是在资源动态分配的过程中防止系统进入不安全状态。通常系统会维护一张资源分配表,在新请求进来时计算分配是否会进入不安全状态。典型的算法有银行家算法。
如果系统在资源分配时不采取任何措施,则应该提供死锁检测和解除手段。系统死锁可以通过资源分配图来描述,当且仅当 $S$ 状态的资源分配图是不可完全简化的时候, $S$ 就是死锁。当检测出死锁的时候,就要采取相应的措施,主要方法有资源剥夺法:挂起某些死锁进程并释放它们持有的资源;撤销进程法:强制撤销部分、甚至全部死锁进程并释放它们的资源,可以按照优先级或者撤销代价的顺序进行;进程回退法:让一个或多个进程回退到足以回避死锁的状态,在回退的过程中进程会自愿释放资源,系统需要保持进程的历史信息并设置还原点。
3. 内存
内存管理 ( $Memory\ \ Management$ ) 是操作系统设计中最重要和最复杂的内容之一。创建进程首先要将程序和数据装入内存,将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:
- 编译:由编译程序将用户源代码编译成若干个目标模块;
- 链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块;
- 装入:由装入程序将装入模块装入内存运行。
程序的链接有三种方式:
- 静态链接:在程序运行之前将各自目标模块及它们所需的库函数链接成一个完整的可执行程序,不再拆开;
- 装入时动态链接:将用户源程序编译后得到的一组目标模块,在装入内存时采用边装入边链接的链接方式;
- 运行时动态链接:对某些目标模块的链接,在程序执行中需要该目标模块时,才对它进行链接。
内存的装入模块在装入内存时同样有三种方式:
- 绝对装入:产生绝对地址的目标代码,按照装入模块中的地址,将程序和数据装入内存,此种方式中程序的逻辑地址与实际地址完全相同,只适用于单道程序环境。绝对地址可在编译或汇编时给出,也可以由程序员直接赋值。
- 可重定位装入:在多道程序环境下,多个目标模块的起始地址通常都是从 $0$ 开始,其他地址都是相对于起始地址的。可重定位装入根据当前内存情况,会在在装入时对程序中指令和数据进行修改,称为重定位。地址变换通常是在装入时一次完成的,所以又称为静态重定位。静态重定位的特点是作业装入内存时必须分配其要求的全部内存空间,此外一旦装入,便不能移动和申请新的内存空间;
- 动态运行时装入:也称为动态重定位,程序如果在内存中发生移动,就要采用动态装入方式。装入程序在把装入模块装入内存后,不会立即把装入模块中的相对地址转为绝对地址,而是推迟到程序要真正执行时,这种方式需要重定位寄存器的支持。使用动态重定位,程序在运行的时候可以只装入部分代码,再在运行时根据需要动态申请分配内存。
在编译完成后,每个目标模块都是从 $0$ 号单元开始,称为该模块的相对地址(或逻辑地址)。当链接程序将各个模块链接为一个完整的可执行程序时,链接程序会顺序依次按照各个模块的相对地址构成同一个的从 $0$ 号单元开始编址的逻辑地址空间。物理地址空间是内存中物理单元的集合,它是地址转换的最终地址,进程在运行时执行指令和访问数据最后都要通过物理地址从主存中获取,将逻辑地址转换为物理地址的过程称为地址重定位。
内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。通过采用重定位寄存器和界地址寄存器来实现这种保护。重定位寄存器包含最小的物理地址值,界地址寄存器含逻辑地址值。每个逻辑地址值必须小于界地址寄存器,否则就会发生地址越界。如果未发生地址越界,就会加上重定位寄存器的值,映射成物理地址,再交由内存单元。当CPU
调度程序选择进程执行时,派遣程序会初始化重定位寄存器和界地址寄存器。
内存的分配方式有连续分配方式和非连续分配方式。连续分配方式指为一个用户程序分配一个连续的内存空间,包括:
- 单一连续分配:内存分为系统区和用户区,系统区仅提供给操作系统使用,通常在低地址部分;用户区提供给用户使用。这种方式不需要内存保护,而且实现简单,没有外部碎片,缺点是只适用于单用户、单任务系统,存在内部碎片且内存使用效率较低;
- 固定分区分配:固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干个固定大小的区域,这些区域大小可能相等也可能不相等,每个区域装入一道作业。如果存在空闲分区,就从外存的后备队列中选择适当大小的作业装入分区。这种分配方式产生内存碎片,而且如果没有任何一个分区满足程序的运行,就必须使用覆盖技术;
- 动态分区分配:动态分区分配不会预先划分内存,而是在进程装入内存时,根据进程大小动态地建立分区。这种分配方式会导致外部碎片。外部碎片可以通过紧凑 ( $Compaction$ ) 的技术解决,但是需要动态重定位寄存器的支持,而且相对费时。在将进程装入或换入主存时,如果存在多个足够大的空闲块,操作系统必须决定分配哪个内存块,这就是动态分区的分配策略,主要有以下几种算法:首次适应 ( $First\ \ Fit$ ) 算法、最佳适应 ( $Best\ \ Fit$ ) 算法、最坏适应 ( $Worst\ \ Fit$ ) 算法和邻近适应 ( $Next\ \ Fit$ ) 算法。首次适应算法通常是最好和最快的,但是会导致低地址部分出现很多外部碎片。邻近适应算法会从上次查找结束的位置继续查找,但是会导致分配通常在高地址部分进行,因为低地址部分被释放的空间不会参与分配。最佳适应算法的性能很差,因为每次都会留下很小的外部碎片。最差适应算法会导致很快没有大内存块使用,因此性能也很差。
非连续分配允许一个程序分散地装入到不相邻的内存分区中,根据分区大小是否固定可以分为分段或者分页管理方式。在分页管理方式中,根据是否要将作业的所有页面都装入内存才能运行分为基本分页存储管理方式和请求分页存储管理方式。
分页存储方式将内存空间划分为大小相等且固定的块,每个进程也以块为单位进行划分,进程在执行过程中以块为单位依次申请内存中的块空间。进程中的块称为页 ( $Page$ ),内存中的块称为页帧 ( $Page\ \ Frame$ ),外存也以同样的单位划分,直接称为块 ( $Block$ )。进程在执行过程中需要申请内存空间,就是为每个页面分配内存中可用的页帧。分页的地址结构包含两部分,页号和页偏移量。系统会为每个进程建立一张页表,一般存放在内存中,记录页号对应的内存中的物理块。系统中通常会设置一个页表寄存器 ( $PTR$ ),存放页表在内存的起始地址和长度,在进程执行时设置。在地址变换中通常还会设置一个具有并行查找能力的告诉缓冲存储器,称为快表或者联想寄存器 ( $TLB$ ),存放部分页表项。在设置了快表后,地址的转换过程会优先在快表中查找,如果没有找到,才会在页表中查找,并且会将这次查找结果存在快表中。由于页表存储在内存中,并且页面大小较小,所以会导致页表过大的问题,而且通常一个进程运行时只需要小部分的页面,由此提出了二级页表的概念。二级页表将一级页表的空间进行地址映射,即将一级页表划分为若干个等大的区域,这样在进程执行的过程中,只需要将二级页表映射的那一块页表区域调入内存即可。由二级页表进一步延伸,可以建立多级页表。
分段存储方式按照用户进程中的自然段划分逻辑空间,每段从 $0$ 开始编址,并分配一段连续的地址空间(段内连续,段间不连续)。逻辑地址由段号和段偏移量组成。在分页系统中,逻辑地址的页号和页偏移量对用户透明,但在分段系统中,段号和段偏移量必须由用户显式提供,在高级语言中由编译器完成。每个进程都有一个逻辑空间与内存空间映射的段表,每个段表项对应进程的一个段,包括段号、段长和起始地址。为了实现从逻辑地址到物理地址的映射,系统中设置了一个段表寄存器,存放段表的起始地址和长度。在分段系统中,段共享是通过两个段表中相应表项指向被共享段的同一个物理副本实现的。
段页式存储方式结合了分页和分段,首先将作业的地址空间分为若干个段,每个段都有自己的段号,然后再将段分为固定大小的页。内存的空间管理仍和分页存储方式一样,将其分为若干个固定大小的页帧。作业的逻辑地址分为三部分:段号、页号和页偏移量。系统为每个进程建立一张段表,每个分段有一张页表。系统中还有一个段表寄存器,记录段表起始地址和段表长度。
在程序装入时,可以将程序的一部分装入内存,在执行过程中,当访问的信息不在内存时,再将它们调入内存。另一方面,操作系统也可以将内存中暂时不使用的内存换出到外存上。这样,操作系统好像提供了一个比实际大得多的内存,称为虚拟内存。虚拟内存需要建立在非连续分配的基础上,因此只适用于分段和分页存储管理方式。请求分页系统建立在基本分页系统的基础上,为了支持虚存而增加了请求调页和页面置换功能。为了支持请求分页,操作系统除了需要一定容量的内存和外存之外,还需要有页表机制、缺页中断机制和地址变换机制。
请求分页系统在作业运行之前不要求调入全部内存,因此如何发现和解决缺页是一个基本问题。为此,页表项中新增了四个字段,分别为状态位,用于标识该页是否调入内存;访问字段,记录本页在一段时间内被访问的次数或者最近有多久未被访问;修改位:标识该页在调入内存后是否被修改;外存地址:通常是外存上的物理块号,用于调入页面时提供参考。每当要访问的页面在内存中不存在时,就会产生一个缺页中断,此时进程将会被阻塞。如果内存中存在空闲块,就会将页面装入该块,并修改页表,否则就会执行页面置换算法。常见的页面置换算法有:
- 最佳 ( $OPT$ ) 置换算法:最佳置换算法选择的页面将是以后永不使用的,或者是在最长时间内不会被访问的。但是由于无法预测哪个页面不会被使用,所以该算法无法实现;
- 先进先出 ( $FIFO$ ) 置换算法:淘汰最早进入内存的页面,或者说在内存中驻留时间最久的页面。
FIFO
还可能会出现当所分配的物理块数增加而缺页故障数不减反增的异常,称为Belady
异常; - 最近最久未使用 ( $LRU$ ) 置换算法:选择最近最久未使用的页面淘汰。该算法为每个页面设置一个访问字段,记录自上次访问以来经历的时间,每次淘汰时选择最大的予以淘汰。
LRU
的性能较好,但是需要寄存器和栈的硬件支持; - 时钟 ( $CLOCK$ ) 置换算法:
CLOCK
算法给每一帧关联一个附加位,称为使用位。当某一页首次装入内存时置 $1$ ,如果在之后被访问也会置 $1$ 。在进行页替换的时候,将候选帧看作一个循环缓冲区,并且有一个指针指向其中的某一页帧。当需要替换时,操作系统会扫描缓冲区,查找使用位为 $0$ 的帧并将其替换,在这个过程中每当遇到一个使用位为 $1$ 的帧,就把它的使用位置 $0$ 。也可以在这个算法的基础上添加一个修改位,每当页面被修改时置 $1$ 。优先替换使用位为 $0$ 的帧,其次是修改位为 $0$ 的帧(如果有的话),因为被修改的页面在被替换之前必须先写回。
使用了虚拟内存技术后,进程不需要将所有页都读到内存。但是操作系统需要决定每个进程在内存中驻留的页面的数量,称为驻留集的大小。通常有三种策略:
- 固定分配局部置换:为每个进程分配固定数量的物理块;
- 可变分配全剧置换:这是最容易实现的策略,为每个进程分配一定数量的物理块,操作系统本身也维护一个空闲物理块队列,当进程缺页发生时,从空闲物理块队列中取出一个物理块分配给该进程;
- 可变分配局部置换:为每个进程分配一定数量的物理块,当进程缺页发生时,从该进程的驻留集中选择页面换出。如果进程频繁缺页,系统会为该进程分配若干物理块,直到缺页率趋于适当程度。如果进程缺页率过低,系统也会回收部分物理块。
在调入页面的时候,除了只调入需要的页面外,系统也可以一次调入若干个相邻的页面,但是需要预测哪些页面会被使用。这种策略主要用于进程的首次调入,由用户指定调入页面。
请求分页系统的外存可以分为文件区和对换区两个部分。对换区通常采用连续分配方式,文件区采用离散分配方式,因此对换区的I/O
速度会更快。如果对换区空间足够,进程运行前可以把文件先从文件区复制到对换区;如果对换区空间不足,则优先将可能被修改的部分放入对换区;还可以使用UNIX
方式,即将文件放在文件区,把运行过被换出的页面放入对换区。
4. 文件
在系统运行时,计算机以进程为基本单位进行资源的调度和分配;而在用户进行I/O
时,基本单位是文件 ( $File$ )。操作系统提供了文件系统 ( $File\ \ System$ ) 实现对文件的管理,如访问文件、修改文件、保存文件和删除文件等。文件的结构包括:
- 数据项:分为基本数据项和组合数据项,前者用于描述一个对象的某种属性的一个值,是原子数据;后者由多个基本数据项组成;
- 记录:记录是一组相关的数据项的集合,用于描述一个对象在某方面的属性;
- 文件:文件是创建者定义的一组相关信息的集合,逻辑上可分为有结构文件和无结构文件。有结构文件由一组相似记录组成,而无结构文件由字符流或者字节流组成。
文件具有一定的属性,通常有:
- 名称:唯一名称;
- 标识符:文件系统内的唯一标签,通常为数字;
- 类型:支持不同类型文件的文件系统中使用;
- 位置:设备和设备上文件指针;
- 大小:文件大小,单位可以是字节、字或者块,也可以包含文件允许的最大值;
- 保护:访问控制信息;
- 时间、日期和用户标识:文件创建、修改和访问的相关信息。
文件信息保存在目录结构中,目录结构保存在外存上,当需要使用时再调入内存。
文件系统支持对文件进行一些基本操作:
- 创建:创建包含为文件在文件系统中找到存储空间和为文件在目录中创建条目,条目用于记录文件名称、位置和其他信息;
- 读/写:系统调用,指明文件名称和文件内容。系统根据文件名称搜索目录查找文件位置,为文件维护一个读/写位置指针。一个进程通常只对一个文件进行读/写,所以当前操作位置可作为文件位置指针,同时读写共用一个指针;
- 文件重定位:按照条件搜索目录,将当前文件位置设为给定值;
- 删除:从目录中找到待删除的文件,将其设为空,然后回收空间;
- 截断:文件属性不变但是长度设为 $0$ 并回收空间。
因为许多文件操作都涉及为给定文件搜索相关目录条目,所以许多系统要求在首次使用文件时通过系统调用 $open$ 将文件属性从外存拷贝到内存打开文件表 ( $open-file\ \ table$ ) 中,并将条目索引返回给用户。当用户需要文件操作时,通过索引可以直接访问指定文件,避免了搜索操作。通常,打开文件表中的条目还有一个文件打开计数器 ( $Open\ \ Count$ ),用于记录多少进程打开了该文件。当计数器为 $0$ 时,代表文件不再被使用,此时系统就会回收文件的内存空间,包括打开文件表中的对应条目,如果文件被修改过,还会将文件写回外存,最后释放文件的文件控制块 ( $File\ \ Control\ \ Block$, $FCB$ )。每个打开文件表的条目都有如下关联信息:
- 文件指针:系统跟踪上次文件读写位置作为文件位置指针,对于每个进程来讲是唯一的;
- 文件打开计数:跟踪文件打开和关闭的数量,值为 $0$ 时关闭文件,删除对应条目;
- 磁盘位置:绝大多数文件操作都要修改文件数据,保存该信息是为了避免每次都进行搜索;
- 访问权限:每个进程打开文件都有一个访问模式(只读、只写、读写等),保存在打开文件表中以便操作系统能允许或者拒绝之后的
I/O
请求。
文件的逻辑结构是从用户的角度出发看到的文件的组织形式,物理结构是从实现的角度出发看到的文件的组织形式,又称为文件的存储结构。按照逻辑结构,文件有无结构文件和有结构文件两种类型。无结构文件是最简单的文件组织形式,按照顺序组织成记录并累积保存,以字节或字符为单位,适用于对基本信息单位操作不多的文件,如源程序文件、目标代码文件等;有结构文件按记录的组织形式可以分为:
- 顺序文件:文件中的记录顺序排列,可以是定长的或者变长的,可以顺序存储也可以以链表方式存储。可以分为串结构和顺序结构,前者中记录顺序与关键字无关,通常按时间排序,后者中所有记录按照关键字顺序排序;
- 索引文件:索引文件维护一张索引表,表项记录了索引、记录开头位置指针和记录长度;
- 索引顺序文件:顺序文件和索引文件两种组织形式的结合,将顺序文件中的记录分为组,为每个组的第一条记录建立一个索引项。索引表中只包含关键字和指针两个记录,按照关键字顺序排序;
- 直接文件/散列文件:直接通过记录的键值或者哈希值确定记录的物理位置。
文件目录包含文件有关的信息,包括属性、位置和所有权等,目录管理通过树形结构实现。为了实现目录管理,操作系统中引入了文件控制块。文件控制块是用来存放控制文件需要的各种信息的数据结构,FCB
的有序集合就是文件目录,一个FCB
就是一个文件目录项。每当创建一个新文件的时候,系统就会分配一个FCB
并存放在文件目录中,成为文件目录项。FCB
主要包括:
- 基本信息:文件名、物理位置、逻辑结构和物理结构等;
- 存储控制信息:如文件存取权限等;
- 使用信息:文件创建时间、修改时间等。
在检索文件目录时,只需要使用文件名,因此有的系统,如UNIX
,使用了文件名和描述信息分开的方法。文件描述信息单独形成一个称为索引结点的数据结构,简称为 $i$ 结点。文件目录的每个目录项由文件名和指向该文件对应的 $i$ 结点的指针形成。
在文件目录上进行的基本操作包括:
- 搜索:用户使用文件前需要先在目录中进行搜索,找到对应的目录项;
- 创建:文件被创建时需要在目录中增加一个目录项;
- 删除:文件被删除时需要在目录中删除对应的目录项;
- 显示目录:显示目录中所有的文件及属性;
- 修改目录:目录中保存了一些文件属性,当这些属性变化时需要修改目录项。
在操作时,需要考虑以下集中目录结构:
- 单级目录结构:整个文件系统中建立一张目录表,每个文件占一个目录项;
- 两级目录结构:将文件目录分为主文件目录 ( $Master\ \ File\ \ Directory$, $MFD$ ) 和用户文件目录 ( $User\ \ File\ \ Directory$, $UFD$ )。主文件目录项记录用户名及相应用户文件目录所在的存储位置,用户文件目录项记录该用户文件的
FCB
信息; - 多级目录结构:即树形目录结构。用户访问文件时使用文件的路径名标识文件,路径名是从根目录出发到所找文件的路径上所有的目录名。从根目录出发的路径称为绝对路径,从当前目录出发的路径称为相对路径;
- 无环图目录结构:树形目录结构便于实现文件分类,不便于实现文件共享,为此在树形目录结构上增加了一些指向同一节点的有向边,使得整个目录变成一个有向无环图。每个共享节点都有一个共享计数器,只有计数器为 $0$ 时才会删除节点。
在读取文件之前,必须先通过路径名找到对应的目录项。目录实现的基本方式有线性表和哈希表两种:
- 线性表:使用存储文件名和数据块指针的线性表是最简单的目录实现方法。在创建文件时需要先搜索目录表确定没有重名文件,再在目录表中添加表项;
- 哈希表:哈希表根据文件名得到一个哈希值,通过哈希值可以得到一个指向线性表中元素的指针。
文件共享使得多个用户可以共享同一文件,系统只需要保存文件的一份副本。如果系统不支持文件共享,那么就需要该文件的每个用户都拥有各自的副本。常用的两种文件共享方式有:
- 基于索引结点的共享方式(硬链接):在这种共享方式中,文件名和索引结点分开存储,文件目录中只设置文件名和指向索引结点的指针。当其他用户访问文件时,会在其文件目录中增加一个目录项,设置指针指向该文件的索引结点,并将索引计数加 $1$ ;
- 基于符号链的共享方式(软链接):系统为共享文件创建一个 $LINK$ 类型的新文件,将其写入对应用户的目录中,新文件中包含被链接文件的路径名。只有文件的拥有者才拥有指向索引结点的指针,其他用户只有该文件的路径名。当拥有者把共享文件删除时,其他用户通过符号链的访问会访问失败。在符号链的共享方式中,其他用户读取共享文件时需要根据路径查找目录,因此每次访问都要多次读盘,增加了访问开销。
文件保护通过口令保护、加密保护和访问控制等方式实现。其中,口令保护和加密保护是为了防止用户文件被他人存取或窃取,而访问控制则用于控制用户对文件的访问方式。对文件的保护可以从限制对文件的访问类型中出发,可以控制的访问类型主要有:读、写、执行、添加、删除和列表清单(列出文件名和文件属性)。此外还有重命名、复制、编辑等,这些高层功能可以通过低层系统调用实现,保护可以在低层提供。
解决访问控制最常用的方法是根据用户身份进行控制,最普通的方法是为每个文件和目录增加一个访问控制列表 ( $Access-Control\ \ List$, $ACL$ ),规定每个用户名及其所允许的访问类型。优点是可以使用复杂的访问方法,缺点是长度无法估计,可能导致复杂的空间管理,精简的访问列表可以解决这个问题。精简的访问列表采用拥有者、组和其他三种用户类型,这样只需要使用三个域列出访问表中这三类用户的访问权限即可。拥有者在创建文件时,系统会将拥有者、所属组名列在FCB
中。
口令是指用户在建立文件时提供一个口令,系统在FCB
上附上相应口令,用户请求访问时必须提供相应口令,优点是时间和空间开销低,缺点是口令存在系统内部,不够安全。密码则是用户对文件进行加密,用户访问时需要使用密钥,优点是保密性强,缺点是需要进行编码和解码,提高了访问开销。
文件分配对应于文件的物理结构,指如何为文件分配磁盘块,常见的分配方式有:
- 连续分配:文件在磁盘上占有一组连续的块,支持顺序访问和直接访问,但是会产生外部碎片。文件的目录条目会包括文件开始块的地址和文件长度;
- 链接分配:采取离散分配方式,可以动态分配磁盘块。可以分为隐式链接和显式链接两种形式。隐式链接中每个文件对应一个磁盘块链表,除最后一个块之外,其他块都有指向下一个块的指针。显式链接会把链接文件各个块指针显式存放在一张链接表中,称为文件分配表 ( $File\ \ Allocation\ \ Table$, $FAT$ );
- 索引分配:索引分配将每个文件的所有块号集中在一起构成索引块,每个文件都有对应的索引块。对于大文件,可以将多个索引块链接起来。也可以使用多层索引方式,或者混合索引方式,即系统既采用直接地址,又采用单级索引或两级索引分配方式。
磁盘 ( $Disk$ ) 是由表面涂有磁性物质的金属或塑料构成的圆形盘片,通过一个称为磁头的导体线圈从磁盘中存取数据。磁盘的盘面上的数据存储在一组同心圆中,称为磁道,每个磁道与磁头一样宽。磁道又可以划分为几百个扇区,每个扇区固定存储大小 ( 通常为 $512B$ ),一个扇区称为一个块。相邻磁道及相邻扇区之间通过一定的间隙分开,避免精度错误。磁盘安装在磁盘驱动器中,由磁头臂、主轴和用于I/O
的电子设备组成,多个盘片垂直堆叠,形成磁盘组,每个盘面对应一个磁头,所有磁头固定在一起,与磁盘中心距离相同且一起移动。扇区是磁盘可寻址的最小存储单位,磁盘地址使用柱面号、盘面号和扇区号表示。
一次磁盘读写操作由寻道时间、延迟时间和传输时间决定。寻道时间指将磁头移到指定磁道所需的时间,延迟时间指磁头定位到某一磁道的扇区所需的时间,传输时间指从磁盘读出或向磁盘写入数据所经历的时间。常用的磁盘调度算法有:
- 先来先服务 ( $First\ \ Come\ \ First\ \ Serverd$, $FCFS$ ) 算法:根据进程请求访问磁盘的先后顺序进行调度;
- 最短寻道时间优先 ( $Shortest\ \ Seek\ \ Time\ \ First$, $SSTF$ ) 算法:选择调度处理的磁道是与当前磁头所在磁道距离最近的磁道。这种算法可能会产生饥饿;
- 扫描 ( $SCAN$ ) 算法:在磁头当前移动方向上选择与当前磁头所在磁道距离最近的请求作为下一次服务的对象;
- 循环扫描 ( $Circulair\ \ SCAN$, $C-SCAN$ ) 算法:在扫描算法的基础上规定磁头单向移动。
一个新的磁盘只是一个含有磁性记录材料的空白盘,在进行存储之前必须先分成扇区以便磁盘控制器能进行读写,这个过程称为低级格式化 ( 物理分区 )。每个扇区的数据结构通常由头、数据区域和尾组成,头尾包含了一些磁盘控制器所使用的信息。为了使用磁盘存储文件,操作系统还需要将自己的数据结构记录在磁盘上:第一步将磁盘分为由一个或多个柱面组成的分区,第二步对物理分区进行逻辑格式化,即创建文件系统。计算机启动时需要运行一个初始化程序,用于初始化CPU
、寄存器、设备控制器和内存等,接着启动系统。初始化程序通常保存在ROM
中,为了避免修改代码需要改变ROM
的问题,通常在ROM
中只保留很小部分,并将完整功能保存在磁盘的启动块上。启动块位于磁盘的固定位置上,拥有启动块的磁盘称为启动磁盘或者系统磁盘。
5. I/O
设备管理的主要任务之一是控制设备和内存或处理机之间的数据传送,外围设备和内存之间的输入/输出控制方式有四种:
- 程序直接控制方式。计算机从外部设备读取数据到存储器,每次读一个字的数据。对读入的每个字,
CPU
需要对外设状态进行循环检查,直到确定该字已经在I/O
控制器的数据寄存器中。程序直接控制方式虽然简单易于实现,但是其缺点也是显而易见的,由于CPU
和I/O
设备只能串行工作,导致CPU
利用率相当低。 - 中断驱动方式。允许
I/O
设备主动打断CPU
的运行并请求服务,从而解放CPU
,使得其向I/O
控制器发送读命令后可以继续做其他有用的工作。- 从
I/O
控制器的角度来看,I/O
控制器从CPU
接收一个读命令,然后从外围设备读数据。一旦数据读入到该I/O
控制器的数据寄存器,便通过控制线给CPU
发出一个中断信号,表示数据已准备好,然后等待CPU
请求该数据。I/O
控制器收到CPU
发出的取数据请求后,将数据放到数据总线上,传到CPU
寄存器中。 - 从
CPU
的角度来看,CPU
发出读命令,然后保存当前运行程序的上下文,转而去执行其他程序。在每个指令周期的末尾,CPU
检查中断。当有来自I/O
控制器的中断时,CPU
保存当前正在运行程序的上下文,转而去执行中断处理程序。
- 从
DMA
方式。DMA
方式的基本单位是数据块,传送数据是从设备直接送入内存或者相反,仅在传送一个或多个数据块的开始和结束时,才需要CPU
干预,整块数据的传送是在DMA
控制器的控制下完成的。为了实现在主机与控制器之间成块数据的直接交换,必须在DMA
控制器中设置如下四类寄存器:命令/状态寄存器 ( $CR$ ),接收从CPU
发来的I/O
命令、有关控制信息或设备状态;内存地址寄存器 ( $MAR$ ),输入时存放把数据从设备传送到内存的起始目标地址,输出时存放由内存到设备的内存源地址;数据寄存器 ( $DR$ ),用于暂存从设备到内存,或从内存到设备的数据;数据计数器 ( $DC$ ),存放本次CPU
要读或写的数据量。CPU
读写数据时,会给I/O
控制器发出一条命令,启动DMA
控制器,然后继续其他工作,之后CPU
就把控制操作委托给DMA
控制器,由该控制器负责处理。当传送完成后,DMA
控制器发送一个中断信号给处理器。- 通道控制方式。
I/O
通道是指专门负责输入/输出的处理机,是DMA
方式的发展,即把对一个数据块的读写为单位的CPU
干预,减少为对一组数据块的读写及有关的控制和管理为单位的干预。当CPU
要完成一组相关的读写操作及有关控制时,只需向I/O
通道发送一条I/O
指令,以给出其所要执行的通道程序的首地址和要访问的I/O
设备,通道接收到该指令后,通过执行通道程序即可完成CPU
指定的I/O
任务,数据传送结束时向CPU
发中断请求。与DMA
方式的区别是,DMA
需要CPU
控制传输的数据块大小和内存位置,而通道中这些信息是由通道控制的,另外,通道可以控制多台设备与内存的数据交换。
Linux
系统提供了 $5$ 种I/O
处理模型:
- 阻塞
I/O
:应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。在这个过程中,当前进程被阻塞,但是其他进程还可以执行,CPU
利用率较高; - 非阻塞式
I/O
:应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知I/O
是否完成,即轮询。由于CPU
要处理更多的系统调用,这种模型的CPU
利用率较低; I/O
复用:使用 $select$ 或者 $poll$ 等待多个套接字中的任何一个变为可读,这个过程会阻塞。当某一个套接字返回可读时,再通过 $recvfrom$ 把数据从内核复制到进程中,又称为事件驱动I/O
;- 信号驱动
I/O
:使用 $sigaction$ 系统调用,内核立即返回,应用进程可以继续执行,在数据到达时向应用进程发送 $SIGIO$ 信号,应用进程再在信号处理程序中调用 $recvfrom$ 将数据从内核复制到进程中; - 异步
I/O
:应用进程执行 $aio_-read$ 系统调用后立即返回,应用进程可以继续执行,不会被阻塞,内核在完成所有操作后向应用进程发送信号。与信号驱动I/O
的区别在于,异步I/O
的信号是通知应用I/O
完成,信号驱动I/O
是通知应用进程可以开始I/O
。