0%

总结-操作系统

操作系统

计算机系统概论

计算机操作系统总考纲找不到图片(Image not found)

【考纲内容】

(一)操作系统的概念、特征、功能和提供的服务

(二)操作系统的发展与分类

(三)操作系统的运行环境

内核态与用户态;中断、异常;系统调用

(四)操作系统体系结构

【知识框架】

概论

  • 特征
    • 并发(最基本)
    • 共享(最基本)
    • 虚拟
    • 同步
  • 目标和功能
    • 计算机系统资源的管理者
    • 用户与计算机系统之间的接口
      • 命令接口
      • 程序接口
      • GUI
    • 扩充机器
  • 发展——批处理操作系统——分时操作系统——实时操作系统——网络和分布式操作系统
  • 运行机制
    • 中断和异常
    • 系统调用
  • 体系结构
    • 大内核
    • 微内核

【复习提示】

本章内容通常以选择题的形式考查,重点考查操作系统的功能、运行环境和提供的服务。要求读者能在宏观上把握操作系统各个部分的功能,微观上掌握细微的知识点。因此,在复习操作系统时,首先要在形成大体框架后,通过反复做题巩固、完善知识体系,然后把操作系统的所有内容串成一个整体。本章的内容有助于读者整体上初步认识操作系统,为后面展开各章节的知识点奠定基础,进而整体把握课程。不要因为本章内容在历年考题中出现的比例不高而忽视。

操作系统的基本概念

操作系统的概念

在信息化时代,软件是计算机系统的灵魂,而作为软件核心的操作系统,已与现代计算机系统密不可分、融为一体。计算机系统自下而上可大致分为4部分:$\color{green}{\text{硬件}}$、$\color{green}{\text{操作系统}}$、$\color{green}{\text{应用程序}}$和$\color{green}{\text{用户}}$(这里的划分与计算机组成原理中的分层不同)。操作系统管理各种计算机硬件,为应用程序提供基础,并充当计算机硬件与用户之间的中介。

硬件如中央处理器、内存、输入/输出设备等,提供基本的计算资源。应用程序如字处理程序、电子制表软件、编译器、网络浏览器等,规定按何种方式使用这些资源来解决用户的计算问题。操作系统控制和协调各用户的应用程序对硬件的分配与使用。

在计算机系统的运行过程中,操作系统提供了正确使用这些资源的方法。

综上所述,操作系统(Operating System,OS)是指控制和管理整个计算机系统的硬件与软件资源,合理地组织、调度计算机的工作与资源的分配,进而为用户和其他软件提供方便接口与环境的程序集合。操作系统是计算机系统中最基本的系统软件。

操作系统的特征

操作系统是一种系统软件,但与其他系统软件和应用软件有很大的不同,它有自己的特殊性即基本特征。操作系统的基本特征包括并发、共享、虚拟和异步。这些概念对理解和掌握操作系统的核心至关重要,将一直贯穿于各个章节中。

并发(Concurrence)

并发是指两个或多个事件在同一时间间隔内发生。操作系统的并发性是指计算机系统中同时存在多个运行的程序,因此它具有处理和调度多个程序同时执行的能力。在操作系统中,引入进程的目的是使程序能并发执行。

注意同一时间间隔($\color{red}{\text{并发}}$)和同一时刻($\color{red}{\text{并行}}$)的区别。在多道程序环境下,一段时间内,宏观上有多道程序在同时执行,而在每个时刻,单处理机环境下实际仅能有一道程序执行,因此微观上这些程序仍是分时交替执行的。操作系统的并发性是通过分时得以实现的。

注意,并行性是指系统具有同时进行运算或操作的特性,在同一时刻能完成两种或两种以上的工作。并行性需要有相关硬件的支持,如多流水线或多处理机硬件环境。

我们以现实生活中的直观例子来认识并发和并行的区别。例如,如果你在9:00~9:10仅吃面包,在9:10~9:20仅写字,在9:20~9:30仅吃面包,在9:30~10:00仅写字,那么在9:00~10:00吃面包和写字这两种行为就是并发执行的;再如,如果你在9:00~10:00右手写字,左手同时拿着面包吃,那么这两个动作就是并行执行的。

共享(Sharing)

资源共享即共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。共享可分为以下两种资源共享方式。

(1)互斥共享方式

系统中的某些资源,如打印机、磁带机,虽然可供多个进程使用,但为使得所打印或记录的结果不致造成混淆,应规定在一段时间内只允许一个进程访问该资源。

为此,当进程A访问某个资源时,必须先提出请求,若此时该资源空闲,则系统便将之分配给进程A使用,此后有其他进程也要访问该资源时(只要A未用完)就必须等待。仅当进程A访问完并释放该资源后,才允许另一个进程对该资源进行访问。我们把这种资源共享方式称为互斥式共享,而把在一段时间内只允许一个进程访问的资源称为临界资源或独占资源。计算机系统中的大多数物理设备及某些软件中所用的栈、变量和表格,都属于临界资源,它们都要求被互斥地共享。

(2)同时访问方式

系统中还有另一类资源,这类资源允许在一段时间内由多个进程“同时”访问。这里所说的“同时”通常是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问即“分时共享”的。可供多个进程“同时”访问的典型资源是磁盘设备,一些用重入码编写的文件也可被“同时”共享,即允许若干个用户同时访问该文件。

注意,互斥共享要求一种资源在一段时间内(哪怕是一段很小的时间)只能满足一个请求,否则就会出现严重的问题,(你能想象打印机第一行打印A文档的内容、第二行打印B文档的内容的效果吗?)而同时访问共享通常要求一个请求分几个时间片段间隔地完成,其效果与连续完成的效果相同。

$\color{green}{\text{并发}}$和$\color{green}{\text{共享}}$是操作系统两个最基本的特征,两者之间互为存在的条件:①资源共享是以程序的并发为条件的,若系统不允许程序并发执行,则自然不存在资源共享问题;②若系统不能对资源共享实施有效的管理,则必将影响到程序的并发执行,甚至根本无法并发执行。

虚拟(Virtual)

虚拟是指把一个物理上的$\color{green}{\text{实体}}$变为若干$\color{green}{\text{逻辑}}$上的对应物。物理实体(前者)是实的,即实际存在的;而后者是虚的,是用户感觉上的事物。用于实现虚拟的技术,称为虚拟技术。操作系统中利用了多种虚拟技术来实现虚拟处理器、虚拟内存和虚拟外部设备等。

虚拟处理器技术是通过多道程序设计技术,采用让多道程序并发执行的方法,来分时使用一个处理器的。此时,虽然只有一个处理器,但它能同时为多个用户服务,使每个终端用户都感觉有一个中央处理器(CPU)在专门为它服务。利用多道程序设计技术把一个物理上的CPU虚拟为多个逻辑上的CPU,称为虚拟处理器。

类似地,可以采用虚拟存储器技术将一台机器的物理存储器变为虚拟存储器,以便从逻辑上扩充存储器的容量。当然,这时用户所感觉到的内存容量是虚的。我们把用户感觉到(但实际不存在)的存储器称为虚拟存储器。

还可采用虚拟设备技术将一台物理IO设备虚拟为多台逻辑上的I/O 设备,并允许每个用户占用一台逻辑上的IO设备,使原来仅允许在一段时间内由一个用户访问的设备(即临界资源)变为在一段时间内允许多个用户同时访问的共享设备。

因此,操作系统的虚拟技术可归纳为:时分复用技术,如处理器的分时共享;空分复用技术,如虚拟存储器。

异步(Asynchronism)

多道程序环境允许多个程序并发执行,但由于资源有限,进程的执行并不是一贯到底的,而是走走停停的,它以不可预知的速度向前推进,这就是进程的异步性。

异步性使得操作系统运行在一种随机的环境下,可能导致进程产生与时间有关的错误(就像对全局变量的访问顺序不当会导致程序出错一样)。然而,只要运行环境相同,操作系统就须保证多次运行进程后都能获得相同的结果。

操作系统的目标和功能

为了给多道程序提供良好的运行环境,操作系统应具有以下几方面的功能:处理机管理、存储器管理、设备管理和文件管理。为了方便用户使用操作系统,还必须向用户提供接口。同时,操作系统可用来扩充机器,以提供更方便的服务、更高的资源利用率。

我们用一个直观的例子来理解这种情况。例如,用户是雇主,操作系统是工人(用来操作机器),计算机是机器(由处理机、存储器、设备、文件几个部件构成),工人有熟练的技能,能够控制和协调各个部件的工作,这就是操作系统对资源的管理;同时,工人必须接收雇主的命令,这就是“接口”;有了工人,机器就能发挥更大的作用,因此工人就成了“扩充机器”。

操作系统作为计算机系统资源的管理者
处理机管理

在多道程序环境下,处理机的分配和运行都以进程(或线程)为基本单位,因而对处理机的管理可归结为对进程的管理。并发是指在计算机内同时运行多个进程,因此进程何时创建、何时撤销、如何管理、如何避免冲突、合理共享就是进程管理的最主要的任务。进程管理的主要功能包括进程控制、进程同步、进程通信、死锁处理、处理机调度等。

存储器管理

存储器管理是为了给多道程序的运行提供良好的环境,方便用户使用及提高内存的利用率,主要包括内存分配与回收、地址映射、内存保护与共享和内存扩充等功能。

文件管理

计算机中的信息都是以文件的形式存在的,操作系统中负责文件管理的部分称为文件系统。文件管理包括文件存储空间的管理、目录管理及文件读写管理和保护等。

设备管理

设备管理的主要任务是完成用户的IO请求,方便用户使用各种设备,并提高设备的利用率,主要包括缓冲管理、设备分配、设备处理和虚拟设备等功能。

这些工作都由“工人”负责,“雇主”无须关注。

操作系统作为用户与计算机硬件系统之间的接口

为了让用户方便、快捷、可靠地操纵计算机硬件并运行自己的程序,操作系统还提供了用户接口。操作系统提供的接口主要分为两类:一类是命令接口,用户利用这些操作命令来组织和控制作业的执行;另一类是程序接口,编程人员可以使用它们来请求操作系统服务。

命令接口

使用命令接口进行作业控制的主要方式有两种,即联机控制方式和脱机控制方式。按作业控制方式的不同,可将命令接口分为联机命令接口和脱机命令接口。

$\color{green}{\text{联机命令接口}}$又称$\color{green}{\text{交互式命令接口}}$,适用于分时或实时系统的接口。它由一组键盘操作命令组成。用户通过控制台或终端输入操作命令,向系统提出各种服务要求。用户每输入一条命令,控制权就转给操作系统的命令解释程序,然后由命令解释程序解释并执行输入的命令,完成指定的功能。之后,控制权转回控制台或终端,此时用户又可输入下一条命令。联机命令接口可以这样理解:“雇主”说一句话,“工人”做一件事,并做出反馈,这就强调了交互性。

$\color{green}{\text{脱机命令接口}}$又称$\color{green}{\text{批处理命令接口}}$,适用于批处理系统,它由一组作业控制命令组成。脱机用户不能直接干预作业的运行,而应事先用相应的作业控制命令写成一份作业操作说明书,连同作业一起提交给系统。系统调度到该作业时,由系统中的命令解释程序逐条解释执行作业说明书上的命令,从而间接地控制作业的运行。脱机命令接口可以这样理解:“雇主”把要“工人”做的事写在清单上,“工人”按照清单命令逐条完成这些事,这就是批处理。

$\color{green}{\text{联}}$机和脱机可以理解为「$\color{green}{\text{联}}$接机器」的「$\color{green}{\text{联}}$」不是「$\color{red}{\text{联}}$网」的「$\color{red}{\text{联}}$」;交互的状态不就是连接着机器的吗

程序接口

程序接口由一组$\color{green}{\text{系统调用}}$(也称$\color{green}{\text{广义指令}}$)组成。用户通过在程序中使用这些系统调用来请求操作系统为其提供服务,如使用各种外部设备、申请分配和回收内存及其他各种要求。

当前最为流行的是图形用户界面(GUI),即图形接口。GUI最终是通过调用程序接口实现的,用户通过鼠标和键盘在图形界面上单击或使用快捷键,就能很方便地使用操作系统。严格来说,图形接口不是操作系统的一部分,但图形接口所调用的系统调用命令是操作系统的一部分。

操作系统用作扩充机器

没有任何软件支持的计算机称为裸机,它仅构成计算机系统的物质基础,而实际呈现在用户面前的计算机系统是经过若干层软件改造的计算机。裸机在最里层,其外面是操作系统。操作系统所提供的资源管理功能和方便用户的各种服务功能,将裸机改造成功能更强、使用更方便的机器;因此,我们通常把覆盖了软件的机器称为扩充机器或虚拟机。

“工人”操作机器,机器就有更大的作用,于是“工人”便成了“扩充机器”。

注意,本课程所关注的内容是操作系统如何控制和协调处理机、存储器、设备和文件,而不关注接口和扩充机器,后两者读者只需要有个印象,能理解即可。

操作系统的发展与分类

手工操作阶段(此阶段无操作系统)

用户在计算机上算题的所有工作都要人工干预,如程序的装入、运行、结果的输出等。随着计算机硬件的发展,人机矛盾(速度和资源利用)越来越大,必须寻求新的解决办法。

手工操作阶段有两个突出的缺点:①用户独占全机,虽然不会出现因资源已被其他用户占用而等待的现象,但资源利用率低。②CPU等待手工操作,CPU的利用不充分。

唯一的解决办法就是用高速的机器代替相对较慢的手工操作来对作业进行控制。

批处理阶段(操作系统开始出现)

为了解决人机矛盾及CPU和IO设备之间速度不匹配的矛盾,出现了批处理系统。按发展历程又分为单道批处理系统、多道批处理系统(多道程序设计技术出现以后)。·

单道批处理系统

系统对作业的处理是成批进行的,但内存中始终保持一道作业。单道批处理系统是在解决人机矛盾及CPU和IO设备速率不匹配的矛盾中形成的。单道批处理系统的主要特征如下:

1)自动性。在顺利的情况下,磁带上的一批作业能自动地逐个运行,而无须人工干预

2)顺序性。磁带上的各道作业顺序地进入内存,各道作业的完成顺序与它们进入内存的顺序在正常情况下应完全相同,亦即先调入内存的作业先完成。

3)单道性。内存中仅有一道程序运行,即监督程序每次从磁带上只调入一道程序进入内存运行,当该程序完成或发生异常情况时,才换入其后继程序进入内存运行。

此时面临的问题是:每次主机内存中仅存放一道作业,每当它在运行期间(注意这里是“运行时”而不是“完成后”)发出输入/输出请求后,高速的CPU便处于等待低速的IO完成的状态。为了进一步提高资源的利用率和系统的吞吐量,引入了多道程序技术。

多道批处理系统

多道程序设计技术允许多个程序同时进入内存并允许它们在CPU中交替地运行,这些程序共享系统中的各种硬/软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。它不采用某些机制来提高某一技术方面的瓶颈问题,而让系统的各个组成部分都尽量去“忙”,因此切换任务所花费的时间很少,可实现系统各部件之间的并行工作,使其整体在单位时间内的效率翻倍。

当然,多道批处理系统的设计和实现要比单道系统复杂很多,因为要充分利用各种资源,就要涉及各种资源的调度问题。

多道程序设计的特点是多道、宏观上并行、微观上串行。

1)多道。计算机内存中同时存放多道相互独立的程序。

2)宏观上并行。同时进入系统的多道程序都处于运行过程中,即它们先后开始各自的运行,但都未运行完毕。

3)微观上串行。内存中的多道程序轮流占有CPU,交替执行。

多道程序设计技术的实现需要解决下列问题:

1)如何分配处理器。

2)多道程序的内存分配问题。

3)IO设备如何分配。

4)如何组织和存放大量的程序和数据,以方便用户使用并保证其安全性与一致性。

在批处理系统中采用多道程序设计技术就形成了多道批处理操作系统。该系统把用户提交的作业成批地送入计算机内存,然后由作业调度程序自动地选择作业运行。

优点:资源利用率高,多道程序共享计算机资源,从而使各种资源得到充分利用;系统吞吐量大,CPU和其他资源保持“忙碌”状态。缺点:用户响应的时间较长;不提供人机交互能力,用户既不能了解自己的程序的运行情况,又不能控制计算机。

分时操作系统

所谓分时技术,是指把处理器的运行时间分成很短的时间片,按时间片轮流把处理器分配给各联机作业使用。若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时停止运行,把处理器让给其他作业使用,等待下一轮再继续运行。由于计算机速度很快,作业运行轮转得也很快,因此给每个用户的感觉就像是自己独占一台计算机。

分时操作系统是指多个用户通过终端同时共享一台主机,这些终端连接在主机上,用户可以同时与主机进行交互操作而互不干扰。因此,实现分时系统最关键的问题是如何使用户能与自己的作业进行交互,即当用户在自己的终端上键入命令时,系统应能及时接收并及时处理该命令,再将结果返回用户。分时系统也是支持多道程序设计的系统,但它不同于多道批处理系统多道批处理是实现作业自动控制而无须人工干预的系统,而分时系统是实现人机交互的系统,这使得分时系统具有与批处理系统不同的特征。分时系统的主要特征如下:

1)同时性。同时性也称多路性,指允许多个终端用户同时使用一台计算机,即一台计算机与若干台终端相连接,终端上的这些用户可以同时或基本同时使用计算机。

2)交互性。用户能够方便地与系统进行人机对话,即用户通过终端采用人机对话的方式直接控制程序运行,与同程序进行交互。

3)独立性。系统中多个用户可以彼此独立地进行操作,互不干扰,单个用户感觉不到别人也在使用这台计算机,好像只有自己单独使用这台计算机一样。

4)及时性。用户请求能在很短时间内获得响应。分时系统采用时间片轮转方式使一台计算机同时为多个终端服务,使用户能够对系统的及时响应感到满意。

虽然分时操作系统较好地解决了人机交互问题,但在一些应用场合,需要系统能对外部的信息在规定的时间(比时间片的时间还短)内做出处理(比如飞机订票系统或导弹制导系统),因此,实时操作系统应运而生。

实时操作系统

为了能在某个时间限制内完成某些紧急任务而不需要时间片排队,诞生了实时操作系统。这里的时间限制可以分为两种情况:若某个动作必须绝对地在规定的时刻(或规定的时间范围)发生,则称为硬实时系统,如飞行器的飞行自动控制系统,这类系统必须提供绝对保证,让某个特定的动作在规定的时间内完成。若能够接受偶尔违反时间规定且不会引起任何永久性的损害,则称为软实时系统,如飞机订票系统、银行管理系统。

在实时操作系统的控制下,计算机系统接收到外部信号后及时进行处理,并在严格的时限内处理完接收的事件。实时操作系统的主要特点是 $\color{green}{\text{及时性}}$ 和 $\color{green}{\text{可靠性}}$ 。

  • 系统架构师的考试中涉及到,什么是实时系统,实时系统的主要特征有哪些

网络操作系统和分布式计算机系统

网络操作系统把计算机网络中的各台计算机有机地结合起来,提供一种统一、经济而有效的使用各台计算机的方法,实现各台计算机之间数据的互相传送。网络操作系统最主要的特点是网络中各种资源的共享及各台计算机之间的通信。

分布式计算机系统是由多台计算机组成并满足下列条件的系统:系统中任意两台计算机通过通信方式交换信息;系统中的每台计算机都具有同等的地位,即没有主机也没有从机;每台计算机上的资源为所有用户共享;系统中的任意台计算机都可以构成一个子系统,并且还能重构;任何工作都可以分布在几台计算机上,由它们并行工作、协同完成。用于管理分布式计算机系统的操作系统称为分布式计算机系统。该系统的主要特点是:分布性和并行性。分布式操作系统与网络操作系统的本质不同是,分布式操作系统中的若干计算机相互协同完成同一任务。

个人计算机操作系统

个人计算机操作系统是目前使用最广泛的操作系统,它广泛应用于文字处理、电子表格、游戏中,常见的有 Windows、Linux和 Macintosh等。操作系统的发展历程如图1.1所示。

图片详情找不到图片(Image not found)

此外,还有嵌入式操作系统、服务器操作系统、智能手机操作系统等。

操作系统的运行环境

操作系统的运行机制

初学者需要弄清楚一个问题,即计算机“指令”和高级语言的“代码”是不同的。我们一般所说的“编写代码”指的是用高级语言〈如C、Java等)来编写程序。但CPU看不懂这些高级语言程序的含义,为了让这些程序能够顺利执行,就需要把它们“翻译”成CPU能懂的机器语言,即一条条“指令”(这个“翻译”的过程称为“编译”)。所谓执行程序,其实就是CPU根据一条条指令的指示来执行一个个具体的操作。

计算机系统中,通常CPU执行两种不同性质的程序:一种是操作系统内核程序;另一种是用户自编程序(即系统外层的应用程序,或简称“应用程序”)。对操作系统而言,这两种程序的作用不同,前者是后者的管理者,因此“管理程序”(即内核程序)要执行一些特权指令,而“被管理程序”(即用户自编程序)出于安全考虑不能执行这些指令。所谓特权指令,是指计算机中不允许用户直接使用的指令,如IO指令、置中断指令,存取用于内存保护的寄存器、送程序状态字到程序状态字寄存器等的指令。在具体实现上,将CPU的状态划分为用户态(目态)和核心态(又称管态、内核态)。可以理解为CPU内部有一个小开关,当小开关为1时,CPU处于核心态,此时CPU可以执行特权指令;当小开关为0时,CPU处于用户态,此时CPU只能执行非特权指令。用户自编程序运行在用户态,操作系统内核程序运行在核心态。

在软件工程思想和结构化程序设计方法影响下诞生的现代操作系统,几乎都是层次式的结构。操作系统的各项功能分别被设置在不同的层次上。一些与硬件关联较紧密的模块,如时钟管理、中断处理、设备驱动等处于最低层。其次是运行频率较高的程序,如进程管理、存储器管理和设备管理等。这两部分内容构成了操作系统的内核。这部分内容的指令操作工作在核心态。

内核是计算机上配置的底层软件,是计算机功能的延伸。不同系统对内核的定义稍有区别,大多数操作系统的内核包括4方面的内容。

时钟管理

在计算机的各种部件中,时钟是最关键的设备。时钟的第一功能是计时,操作系统需要通过时钟管理,向用户提供标准的系统时间。另外,通过时钟中断的管理,可以实现进程的切换。例如,在分时操作系统中采用时间片轮转调度,在实时系统中按截止时间控制运行,在批处理系统中通过时钟管理来衡量一个作业的运行程度等。因此,系统管理的方方面面无不依赖于时钟。

中断机制

引入中断技术的初衷是提高多道程序运行环境中CPU 的利用率,而且主要是针对外部设备的。后来逐步得到发展,形成了多种类型,成为操作系统各项操作的基础。例如,键盘或鼠标信息的输入、进程的管理和调度、系统功能的调用、设备驱动、文件访问等,无不依赖于中断机制。可以说,现代操作系统是靠中断驱动的软件。

中断机制中,只有一小部分功能属于内核,它们负责保护和恢复中断现场的信息,转移控制权到相关的处理程序。这样可以减少中断的处理时间,提高系统的并行处理能力。

原语

按层次结构设计的操作系统,底层必然是一些可被调用的公用小程序,它们各自完成一个规定的操作。它们的特点如下:

1)处于操作系统的最低层,是最接近硬件的部分。

2)这些程序的运行具有原子性,其操作只能一气呵成(主要从系统安全性和便于管理考虑)。

3)这些程序的运行时间都较短,而且调用频繁。

通常把具有这些特点的程序称为原语(Atomic Operation)。定义原语的直接方法是关闭中断,让其所有动作不可分割地完成后再打开中断。

系统中的设备驱动、CPU切换、进程通信等功能中的部分操作都可定义为原语,使它们成为内核的组成部分。

系统控制的数据结构及处理

系统中用来登记状态信息的数据结构很多,如作业控制块、进程控制块(PCB)、设备控制块、各类链表、消息队列、缓冲区、空闲区登记表、内存分配表等。为了实现有效的管理,系统需要一些基本的操作,常见的操作有以下3种:

1)进程管理。进程状态管理、进程调度和分派、创建与撤销进程控制块等。

2)存储器管理。存储器的空间分配和回收、内存信息保护程序、代码对换程序等。

3)设备管理。缓冲区管理、设备分配和回收等。

从上述内容可以了解,核心态指令实际上包括系统调用类指令和一些针对时钟、中断和原语的操作指令。

中断和异常的概念

建议结合《计算机组成原理考研复习指导》第7章学习,那里的讲解更详细。

在操作系统中引入核心态和用户态这两种工作状态后,就需要考虑这两种状态之间如何切换。操作系统内核工作在核心态,而用户程序工作在用户态。系统不允许用户程序实现核心态的功能,而它们又必须使用这些功能。因此,需要在核心态建立一些“门”,以便实现从用户态进入核心态。在实际操作系统中,CPU运行上层程序时唯一能进入这些“门”的途径就是通过中断或异常。发生中断或异常时,运行用户态的CPU会立即进入核心态,这是通过硬件实现的(例如,用一个特殊寄存器的一位来表示CPU所处的工作状态,0表示核心态,1表示用户态。若要进入核心态,则只需将该位置0即可)。中断是操作系统中非常重要的一个概念,对一个运行在计算机上的实用操作系统而言,缺少了中断机制,将是不可想象的。原因是,操作系统的发展过程大体上就是一个想方设法不断提高资源利用率的过程,而提高资源利用率就需要在程序并未使用某种资源时,把它对那种资源的占有权释放,而这一行为就需要通过中断实现。

中断和异常的定义

中断(Interruption)也称外中断,指来自CPU执行指令以外的事件的发生,如设备发出的IO结束中断,表示设备输入/输出处理已经完成,希望处理机能够向设备发下一个输入/输出请求,同时让完成输入/输出后的程序继续运行。时钟中断,表示一个固定的时间片已到,让处理机处理计时、启动定时运行的任务等。这一类中断通常是与当前指令执行无关的事件,即它们与当前处理机运行的程序无关。

图片详情找不到图片(Image not found)

异常(Exception)也称内中断、例外或陷入(trap),指源自CPU执行指令内部的事件,如程序的非法操作码、地址越界、算术溢出、虚存系统的缺页及专门的陷入指令等引起的事件。对异常的处理一般要依赖于当前程序的运行现场,而且异常不能被屏蔽,一旦出现应立即处理。关于内中断和外中断的联系与区别如图1.2所示。

异常不能被屏蔽,一旦出现应立即处理???

中断处理的过程

不同计算机的中断(指外中断)处理过程各具特色,就其多数而论,中断处理流程如图1.3所示。各阶段处理流程的描述如下:

图1.3 中断处理流程找不到图片(Image not found)

1)关中断。CPU响应中断后,首先要保护程序的现场状态,在保护现场的过程中,CPU不应响应更高级中断源的中断请求。否则,若现场保存不完整,在中断服务程序结束后,也就不能正确地恢复并继续执行现行程序。

2)保存断点。为保证中断服务程序执行完毕后能正确地返回到原来的程序,必须将原来的程序的断点(即程序计数器PC)保存起来。

3)中断服务程序寻址。其实质是取出中断服务程序的入口地址送入程序计数器PC。

4)保存现场和屏蔽字。进入中断服务程序后,首先要保存现场,现场信息一般是指程序状态字寄存器PSWR和某些通用寄存器的内容。

5)开中断。允许更高级中断请求得到响应。

6)执行中断服务程序。这是中断请求的目的。

7)关中断。保证在恢复现场和屏蔽字时不被中断。

8)恢复现场和屏蔽字。将现场和屏蔽字恢复到原来的状态。

9)开中断、中断返回。中断服务程序的最后一条指令通常是一条中断返回指令,使其返回到原程序的断点处,以便继续执行原程序。

其中,1 ~ 3步是在CPU进入中断周期后,由硬件自动(中断隐指令)完成的;4 ~ 9步由中断服务程序完成。恢复现场是指在中断返回前,必须将寄存器的内容恢复到中断处理前的状态,这部分工作由中断服务程序完成。中断返回由中断服务程序的最后一条中断返回指令完成。

系统调用

所谓系统调用,是指用户在程序中调用操作系统所提供的一些子功能,系统调用可视为特殊的公共子程序。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序中,凡是与资源有关的操作(如存储分配、进行IO传输及管理文件等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。通常,一个操作系统提供的系统调用命令有几十条乃至上百条之多。这些系统调用按功能大致可分为如下几类。

  • 设备管理。完成设备的请求或释放,以及设备启动等功能。文件管理。完成文件的读、写、创建及删除等功能。
  • 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
  • 进程通信。完成进程之间的消息传递或信号传递等功能。
  • 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及始址等功能。

显然,系统调用相关功能涉及系统资源管理、进程管理之类的操作,对整个系统的影响非常大,因此必定需要使用某些特权指令才能完成,所以系统调用的处理需要由操作系统内核程序负责完成,要运行在核心态。用户程序可以执行陷入指令(又称$\color{green}{\text{访管指令}}$或$\color{green}{\text{trap指令}}$)来发起系统调用,请求操作系统提供服务。可以这么理解,用户程序执行“陷入指令”,相当于把CPU成低用权主动交给操作系统内核程序(CPU 状态会从用户态进入核心态),之后操作系统内核程序再对系统调用请求做出相应处理。处理完成后,操作系统内核程序又会把CPU的使用权还给用户程序(即CPU状态会从核心态回到用户态)。这么设计的目的是:用户程序不能直接执行对系统影响非常大的操作,必须通过系统调用的方式请求操作系统代为执行,以便保证系统的稳定性和安全性,防止用户程序随意更改或访问重要的系统资源,影响其他进程的运行。

这样,操作系统的运行环境就可以理解为:用户通过操作系统运行上层程序(如系统提供的命令解释程序或用户自编程序),而这个上层程序的运行依赖于操作系统的底层管理程序提供服务支持,当需要管理程序服务时,系统则通过硬件中断机制进入核心态,运行管理程序;也可能是程序运行出现异常情况,被动地需要管理程序的服务,这时就通过异常处理来进入核心态。管理程序运行结束时,用户程序需要继续运行,此时通过相应的保存的程序现场退出中断处理程序或异常处理程序,返回断点处继续执行,如图1.4所示。

图片详情找不到图片(Image not found)

在操作系统这一层面上,我们关心的是系统核心态和用户态的软件实现与切换,对于硬件层面的具体理解,可以结合“计算机组成原理”课程中有关中断的内容进行学习。

下面列举一些由用户态转向核心态的例子:

1)用户程序要求操作系统的服务,即系统调用。

2)发生一次中断。

3)用户程序中产生了一个错误状态。

4)用户程序中企图执行一条特权指令。

5)从核心态转向用户态由一条指令实现,这条指令也是特权命令,一般是中断返回指令。

注意:由用户态进入核心态,不仅状态需要切换,而且所用的堆栈也可能需要由用户堆栈切换为系统堆栈,但这个系统堆栈也是属于该进程的。

若程序的运行由用户态转到核心态,则会用到访管指令,访管指令是在用户态使用的,所以它不可能是特权指令。

操作系统的体系结构

大内核和微内核

操作系统的体系结构是一个开放的问题。如上文所述,操作系统在核心态为应用程序提供公共的服务,那么操作系统在核心态应该提供什么服务、怎样提供服务﹖有关这一问题的回答形成了两种主要的体系结构:大内核和微内核。

大内核系统将操作系统的主要功能模块都作为一个紧密联系的整体运行在核心态,从而为应用提供高性能的系统服务。因为各管理模块之间共享信息,能有效利用相互之间的有效特性.所以具有无可比拟的性能优势。

但随着体系结构和应用需求的不断发展,需要操作系统提供的服务越来越多,而且接口形式越来越复杂,操作系统的设计规模急剧增长,操作系统也面临着“软件危机”困境。为此,操作系统设计人员试图按照复杂性、时间常数、抽象级别等因素,将操作系统内核分成基本进程管理、虚存、IO与设备管理、IPC、文件系统等几个层次,继而定义层次之间的服务结构,提高操作系统内核设计上的模块化。但是,由于层次之间的交互关系错综复杂,定义清晰的层次间接口非常困难,复杂的交互关系也使得层次之间的界限极其模糊。

为解决操作系统的内核代码难以维护的问题,提出了微内核的体系结构。它将内核中最基本的功能(如进程管理等)保留在内核,而将那些不需要在核心态执行的功能移到用户态执行,从而降低了内核的设计复杂性。那些移出内核的操作系统代码根据分层的原则被划分成若干服务程序,它们的执行相互独立,交互则都借助于微内核进行通信。

微内核结构有效地分离了内核与服务、服务与服务,使得它们之间的接口更加清晰,维护的代价大大降低,各部分可以独立地优化和演进,从而保证了操作系统的可靠性。

微内核结构的最大问题是性能问题,因为需要频繁地在核心态和用户态之间进行切换,操作系统的执行开销偏大。因此有的操作系统将那些频繁使用的系统服务又移回内核,从而保证系统性能。但相当多的实验数据表明,体系结构不是引起性能下降的主要因素,体系结构带来的性能提升足以弥补切换开销带来的缺陷。为减少切换开销,也有人提出将系统服务作为运行库链接到用户程序的一种解决方案,这样的体系结构称为$\color{green}{\text{库操作系统}}$。

体系结构带来的性能提升足以弥补切换开销带来的缺陷在说啥?

本章疑难点

并行性与并发性的区别和联系

并行性和并发性是既相似又有区别的两个概念。并行性是指两个或多个事件在同一时刻发生,并发性是指两个或多个事件在同一时间间隔内发生。

在多道程序环境下,并发性是指在一段时间内,宏观上有多个程序同时运行,但在单处理器系统中每个时刻却仅能有一道程序执行,因此微观上这些程序只能分时地交替执行。若在计算机系统中有多个处理器,则这些可以并发执行的程序便被分配到多个处理器上,实现并行执行,即利用每个处理器来处理一个可并发执行的程序。

咬文嚼字一下,并行依靠多处理器支持,如果两个任务挂在到两个不同的处理器那么就能并行执行
考虑java的线程机制,一个继承了thread的线程,在调用start的时候$\color{green}{\text{启动}}$一个线程,那么就实现了并$\color{green}{\text{发}}$(发车,启动)
java run和start的区别

特权指令与非特权指令

所谓特权指令,是指有特殊权限的指令,由于这类指令的权限最大,使用不当将导致整个系统崩溃,如清内存、置时钟、分配系统资源、修改虚存的段表或页表、修改用户的访问权限等。若所有程序都能使用这些指令,则系统一天死机n次就不足为奇。为保证系统安全,这类指令只能用于操作系统或其他系统软件,不直接提供给用户使用。因此,特权指令必须在核心态执行。实际上,CPU在核心态下可以执行指令系统的全集。形象地说,特权指令是那些儿童不宜的东西,而非特权指令是老少皆宜的东西。

为了防止用户程序中使用特权指令,用户态下只能使用非特权指令,核心态下可以使用全部指令。在用户态下使用特权指令时,将产生中断以阻止用户使用特权指令。所以把用户程序放在用户态下运行,而操作系统中必须使用特权指令的那部分程序在核心态下运行,保证了计算机系
统的安全可靠。从用户态转换为核心态的唯一途径是中断或异常。

访管指令与访管中断

访管指令是一条可以在用户态下执行的指令。在用户程序中,因要求操作系统提供服务而有意识地使用访管指令,从而产生一个中断事件(自愿中断),将操作系统转换为核心态,称为访管中断。访管中断由访管指令产生,程序员使用访管指令向操作系统请求服务。

为什么要在程序中引入访管指令呢?这是因为用户程序只能在用户态下运行。若用户程序想要完成在用户态下无法完成的工作,该怎么办﹖解决这个问题要靠访管指令。访管指令本身不是特权指令,其基本功能是让程序拥有“自愿进管”的手段,从而引起访管中断。

处于用户态的用户程序使用访管指令时,系统根据访管指令的操作数执行访管中断处理程序,访管中断处理程序将按系统调用的操作数和参数转到相应的例行子程序。完成服务功能后,退出中断,返回到用户程序断点继续执行。

进程管理

【考纲内容】

(一)进程与线程

  1. 进程与 $\color{green}{\text{线程}}$ 的基本概念
  2. 进程/ $\color{red}{\text{线程的状态与转换}}$
  3. $\color{green}{\text{线程的实现}}$ (内核支持的线程,线程库支持的线程)
  4. 进程与 $\color{red}{\text{线程的组织与控制}}$
  5. 进程间的通信(共享内存,消息传递,管道)

(二)CPU调度与上下文切换

  1. 调度的基本概念
  2. $\color{green}{\text{调度的目标}}$
  3. $\color{green}{\text{调度的实现}}$ ( $\color{red}{\text{调度器/调度程序}}$ (scheduler),调度的时机与调度方式(抢占式/非抢占式), $\color{red}{\text{闲逛进程}}$ , $\color{red}{\text{内核级线程与用户级线程调度}}$ )
  4. 典型调度算法(先来先服务调度算法;短作业(短进程、短线程)优先调度算法;时间片轮转调度算法;优先级调度算法;高响应比优先调度算法; $\color{red}{\text{多级队列调度算法}}$ ;多级反馈队列调度算法。)
  5. $\color{green}{\text{上下文及其切换机制}}$

(三)进程同步

  1. 进程同步的基本概念
  2. 基本实现方法(软件方法,硬件方法)
  3. $\color{red}{\text{锁}}$
  4. 信号量
  5. $\color{red}{\text{条件变量}}$
  6. 经典同步问题(生产者-消费者问题;读者-写者问题;哲学家进餐问题)

(四)死锁

  1. 死锁的概念
  2. 死锁处理策略
  3. 死锁预防
  4. 死锁避免
  5. 死锁的检测和解除

【知识框架】

  • 进程
    • 概念:与程序的区别
    • 特征:动态性、并发性、独立性、异步性、结构性
    • 状态:运行、就绪、阻塞、创建、结束
    • 控制:创建、终止、阻塞和唤醒、切换
    • 组织:进程控制块PCB、程序段、数据段
    • 通信:共享存储、消息传递、管道通信
  • 线程
    • 概念、与进程的比较、属性
    • 线程的实现方式
  • 处理机调度
    • 概念、三级调度:作业调度、中级调度、进程调度调度方式:剥夺式、非剥夺式
    • 调度准则:CPU利用率、吞吐量、周转时间、等待时间、响应时间
    • 算法:先来先服务、短作业(SJF)优先、优先级、高响应比优先、时间片轮转、多级反馈队列
  • 进程同步
    • 概念:临界资源、同步、互斥
    • 实现方法:软件实现的几种算法、硬件实现
    • 信号量:整型、记录型
    • 经典问题:生产者-消费者问题、读者-写者问题、哲学家进餐问题、吸烟者问题
  • 死锁
    • 定义
    • 原因:系统资源竞争、进程推进顺序非法
    • 条件:互斥、不剥夺、请求和保持、循环等待
    • 策略:预防死锁、避免死锁、死锁的检测与解除

【复习提示】

进程管理是操作系统的核心,也是每年必考的重点。其中,进程的概念、进程调度、信号量机制实现同步和互斥、进程死锁等更是重中之重,必须深入掌握。需要注意的是,除选择题外,本章还容易出综合题,其中信号量机制实现同步和互斥、进程调度算法和银行家算法都是可能出现的综合题考点,如利用信号量进行进程同步就在往年的统考中频繁出现。

进程:process

进程与线程

在学习本节时,请读者思考以下问题:

1)为什么要引入进程?

2)什么是进程?进程由什么组成?

3)进程是如何解决问题的?

希望读者带着上述问题去学习本节内容,并在学习的过程中多思考,从而更深入地理解本节内容。进程本身是一个比较抽象的概念,它不是实物,看不见、摸不着,初学者在理解进程概念时存在一定困难,在介绍完进程的相关知识后,我们会用比较直观的例子帮助大家理解。

进程的概念和特征

进程的概念

在多道程序环境下,允许多个程序并发执行,此时它们将失去封闭性,并具有间断性及不可再现性的特征。为此引入了进程(Process)的概念,以便更好地描述和控制程序的并发执行,实现操作系统的并发性和共享性(最基本的两个特性)。

为了使参与并发执行的程序(含数据)能独立地运行,必须为之配置一个专门的数据结构,称为进程控制块(Process Control Block,PCB)。系统利用PCB来描述进程的基本情况和运行状态,进而控制和管理进程。相应地,由程序段、相关数据段和PCB三部分构成了进程映像(进程实体)。所谓创建进程,实质上是创建进程映像中的PCB;而撤销进程,实质上是撤销进程的PCB。值得注意的是,进程映像是静态的,进程则是动态的。

注意:PCB是进程存在的唯一标志!

从不同的角度,进程可以有不同的定义,比较典型的定义有:

1)进程是程序的一次执行过程。

2)进程是一个程序及其数据在处理机上顺序执行时所发生的活动。

3)进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

引入进程实体的概念后,我们可以把传统操作系统中的进程定义为:“进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。”

读者要准确理解这里说的系统资源。它指处理机、存储器和其他设备服务于某个进程的“时间”,例如把处理机资源理解为处理机的时间片才是准确的。因为进程是这些资源分配和调度的独立单位,即“时间片”分配的独立单位,这就决定了进程一定是一个动态的、过程性的概念。

进程的特征

进程是由多道程序的并发执行而引出的,它和程序是两个截然不同的概念。进程的基本特征是对比单个程序的顺序执行提出的,也是对进程管理提出的基本要求。

1)动态性。进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。

2)并发性。指多个进程实体同时存于内存中,能在一段时间内同时运行。并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是使程序能与其他进程的程序并发执行,以提高资源利用率。

3)独立性。指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序,都不能作为一个独立的单位参与运行。

4)异步性。由于进程的相互制约,使得进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制。

5)结构性。每个进程都配置一个PCB对其进行描述。从结构上看,进程实体是由程序段、数据段和进程控制块三部分组成的。

通常不会直接考查进程有什么特性,所以读者对上面的5个特性不求记忆,只求理解。

并发和异步的区别

进程的状态与转换

进程在其生命周期内,由于系统中各进程之间的相互制约关系及系统的运行环境的变化,使得进程的状态也在不断地发生变化(一个进程会经历若干不同状态)。通常进程有以下5种状态,前3种是进程的基本状态。

1)运行态。进程正在处理机上运行。在单处理机环境下,每个时刻最多只有一个进程处于运行态。

2)就绪态。进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即运行。系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。

3)阻塞态,又称等待态。进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

4)创建态。进程正在被创建,尚未转到就绪态。创建进程通常需要多个步骤:首先申请一个空白的 PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分配运行时所必需的资源;最后把该进程转入就绪态。

5)结束态。进程正从系统中消失,可能是进程正常结束或其他原因中断退出运行。进程需要结束运行时,系统首先必须将该进程置为结束态,然后进一步处理资源释放和回收等工作。

注意区别就绪态和等待态:就绪态是指进程仅缺少处理机,只要获得处理机资源就立即运行;而等待态是指进程需要其他资源(除了处理机)或等待某一事件。之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪态的;而其他资源(如外设)的使用和分配或某一事件的发生(如IO操作的完成)对应的时间相对来说很长,进程转换到等待态的次数也相对较少。这样来看,就绪态和等待态是进程生命周期中两个完全不同的状态,显然需要加以区分。

图2.1说明了5种进程状态的转换,而3种基本状态之间的转换如下:

  • 就绪态→运行态:处于就绪态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪态转换为运行态。
  • 运行态→就绪态:处于运行态的进程在时间片用完后,不得不让出处理机,从而进程由运行态转换为就绪态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行。
  • 运行态→阻塞态:进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行态转换为阻塞态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。
  • 阻塞态→就绪态:进程等待的事件到来时,如IO操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞态转换为就绪态。
图2.1 5种进程状态的转换找不到图片(Image not found)

需要注意的是,一个进程从运行态变成阻塞态是 $\color{green}{\text{主动}}$ 的行为,而从阻塞态变成就绪态是 $\color{green}{\text{被动}}$ 的行为,需要其他相关进程的协助。

进程控制

进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。在操作系统中,一般把进程控制用的程序段称为原语,原语的特点是执行期间不允许中断,它是一个不可分割的基本单位。

进程的创建

允许一个进程创建另一个进程。此时创建者称为父进程,被创建的进程称为子进程。子进程可以继承父进程所拥有的资源。当子进程被撤销时,应将其从父进程那里获得的资源归还给程。此外,在撤销父进程时,必须同时撤销其所有的子进程。

在操作系统中,终端用户登录系统、作业调度、系统提供服务、用户程序的应用请求等都会引起进程的创建。操作系统创建一个新进程的过程如下(创建原语):
1)为新进程分配一个唯一的进程标识号,并申请一个空白的 PCB (PCB是有限的)。若申请失败,则创建失败。

2)为进程分配资源,为新进程的程序和数据及用户栈分配必要的内存空间(在PCB中体现)。注意,若资源不足(如内存空间),则并不是创建失败,而是处于阻塞态,等待内存资源。

3)初始化 PCB,主要包括初始化标志信息、初始化处理机状态信息和初始化处理机控制信息,以及设置进程的优先级等。

4)若进程就绪队列能够接纳新进程,则将新进程插入就绪队列,等待被调度运行。

进程的终止

引起进程终止的事件主要有:①正常结束,表示进程的任务已完成并准备退出运行。②异常结束,表示进程在运行时,发生了某种异常事件,使程序无法继续运行,如存储区越界、保护错、非法指令、特权指令错、运行超时、算术运算错、I/O故障等。③外界干预,指进程应外界的请求而终止运行,如操作员或操作系统干预、父进程请求和父进程终止。

操作系统终止进程的过程如下(撤销原语):

1)根据被终止进程的标识符,检索PCB,从中读出该进程的状态。

2)若被终止进程处于执行状态,立即终止该进程的执行,将处理机资源分配给其他进程。

3)若该进程还有子孙进程,则应将其所有子孙进程终止。

4)将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统。

5)将该PCB从所在队列(链表)中删除。

进程的阻塞和唤醒

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作可做等,由系统自动执行阻塞原语(Block),使自己由运行态变为阻塞态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU才可能将其转为阻塞态。阻塞原语的执行过程如下:

1)找到将要被阻塞进程的标识号对应的PCB。

2)若该进程为运行态,则保护其现场,将其状态转为阻塞态,停止运行。3)把该PCB插入相应事件的等待队列,将处理机资源调度给其他就绪进程。

当被阻塞进程所期待的事件出现时,如它所启动的IO操作已完成或其所期待的数据已到达,由有关进程(比如,释放该IO 设备的进程,或提供数据的进程)调用唤醒原语(Wakeup),将等待该事件的进程唤醒。唤醒原语的执行过程如下:

1)在该事件的等待队列中找到相应进程的PCB。

2)将其从等待队列中移出,并置其状态为就绪态。

3)把该PCB插入就绪队列,等待调度程序调度。

需要注意的是,Block原语和 Wakeup原语是一对作用刚好相反的原语,必须成对使用。Block原语是由被阻塞进程自我调用实现的,而 Wakeup原语则是由一个与被唤醒进程合作或被其他相关的进程调用实现的。

进程切换

对于通常的进程而言,其创建、撤销及要求由系统设备完成的I/O操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成的。进程切换同样是在内核的支持下实现的,因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

进程切换是指处理机从一个进程的运行转到另一个进程上运行,在这个过程中,进程的运行环境产生了实质性的变化。进程切换的过程如下:

1))保存处理机上下文,包括程序计数器和其他寄存器。

2)更新PCB信息。

3)把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。

4)选择另一个进程执行,并更新其PCB。

5)更新内存管理的数据结构。

6)恢复处理机上下文。

注意,进程切换与处理机模式切换是不同的,模式切换时,处理机逻辑上可能还在同一进程中运行。若进程因中断或异常进入核心态运行,执行完后又回到用户态刚被中断的程序运行,则操作系统只需恢复进程进入内核时所保存的CPU现场,而无须改变当前进程的环境信息。但若要切换进程,当前运行进程改变了,则当前进程的环境信息也需要改变。

进程的组织

进程是一个独立的运行单位,也是操作系统进行资源分配和调度的基本单位。它由以下三部分组成,其中最核心的是进程控制块(PCB)。

进程控制块

进程创建时,操作系统为它新建一个PCB,该结构之后常驻内存,任意时刻都可以存取,并在进程结束时删除。PCB是进程实体的一部分,是进程存在的唯一标志。

进程执行时,系统通过其 PCB 了解进程的现行状态信息,以便对其进行控制和管理;进程结束时,系统收回其PCB,该进程随之消亡。操作系统通过PCB表来管理和控制进程。

当操作系统欲调度某进程运行时,要从该进程的PCB中查出其现行状态及优先级;在调度到某进程后,要根据其PCB中所保存的处理机状态信息,设置该进程恢复运行的现场,并根据其PCB中的程序和数据的内存始址,找到其程序和数据;进程在运行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也需要访问PCB;当进程由于某种原因而暂停运行时,又需将其断点的处理机环境保存在PCB中。可见,在进程的整个生命期中,系统总是通过PCB对进程进行控制的,亦即系统唯有通过进程的PCB才能感知到该进程的存在。

表2.1是一个PCB 的实例。PCB主要包括进程描述信息、进程控制和管理信息、资源分配清单和处理机相关信息等。各部分的主要说明如下:

表2.1 PCB通常包含的内容找不到图片(Image not found)

1)进程描述信息。进程标识符:标志各个进程,每个进程都有一个唯一的标识号。用户标识符:进程归属的用户,用户标识符主要为共享和保护服务。

2)进程控制和管理信息。进程当前状态:描述进程的状态信息,作为处理机分配调度的依据。进程优先级:描述进程抢占处理机的优先级,优先级高的进程可优先获得处理机。

3)资源分配清单,用于说明有关内存地址空间或虚拟地址空间的状况,所打开文件的列表和所使用的输入/输出设备信息。

4)处理机相关信息,主要指处理机中各寄存器的值,当进程被切换时,处理机状态信息都必须保存在相应的PCB中,以便在该进程重新执行时,能从断点继续执行。

在一个系统中,通常存在着许多进程的PCB,有的处于就绪态,有的处于阻塞态,而且阻塞的原因各不相同。为了方便进程的调度和管理,需要将各进程的PCB用适当的方法组织起来。目前,常用的组织方式有链接方式和索引方式两种。链接方式将同一状态的PCB链接成一个队列,不同状态对应不同的队列,也可把处于阻塞态的进程的PCB,根据其阻塞原因的不同,排成多个阻塞队列。索引方式将同一状态的进程组织在一个索引表中,索引表的表项指向相应的PCB,不同状态对应不同的索引表,如就绪索引表和阻塞索引表等。

程序段

程序段就是能被进程调度程序调度到CPU执行的程序代码段。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。

数据段

一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。

进程的通信

进程通信是指进程之间的信息交换。PV操作是低级通信方式,高级通信方式是指以较高的效率传输大量数据的通信方式。高级通信方法主要有以下三类。

共享存储

在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换,如图2.2所示。在对共享空间进行写/读操作时,需要使用同步互斥工具(如Р操作、V操作),对共享空间的写/读进行控制。共享存储又分为两种:低级方式的共享是基于数据结构的共享;高级方式的共享则是基于存储区的共享。操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具,而数据交换则由用户自己安排读/写指令完成。

注意,用户进程空间一般都是独立的,进程运行期间一般不能访问其他进程的空间,要想让两个用户进程共享空间,必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。

简单理解就是,甲和乙中间有一个大布袋,甲和乙交换物品是通过大布袋进行的,甲把物品放在大布袋里,乙拿走。但乙不能直接到甲的手中拿东西,甲也不能直接到乙的手中拿东西。

消息传递

在消息传递系统中,进程间的数据交换是以格式化的消息(Message)为单位的。若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息和接收消息两个原语进行数据交换。

1)直接通信方式。发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息,如图2.3所示。

图片详情找不到图片(Image not found)

2)间接通信方式。发送进程把消息发送到某个中间实体,接收进程从中间实体取得消息。

这种中间实体一般称为信箱,这种通信方式又称信箱通信方式。该通信方式广泛应用于计算机网络中,相应的通信系统称为电子邮件系统。
简单理解就是,甲要告诉乙某些事情,就要写信,然后通过邮差送给乙。直接通信就是邮差把信直接送到乙的手上;间接通信就是乙家门口有一个邮箱,邮差把信放到邮箱里。

管道通信

管道通信是消息传递的一种特殊方式(见图2.4)。所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间的通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入(写)管道;而接收管道输出的接收进程(即读进程)则从管道中接收(读)数据。为了协调双方的通信,管道机制必须提供以下三方面的协调能力:互斥、同步和确定对方的存在。

图片详情找不到图片(Image not found)

下面以Linux中的管道为例进行说明。在Linux 中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下:

1)限制管道的大小。实际上,管道是一个固定大小的缓冲区。在 Linux中,该缓冲区的大小为4KB,这使得它的大小不像文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,这种情况发生时,随后对管道的 write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供 write()调用写。

2)读进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。

注意:从管道读数据是一次性操作,数据一旦被读取,它就从管道中被抛弃,释放空间以便写更多的数据。管道只能采用半双工通信,即某一时刻只能单向传输。要实现父子进程双方互动通信,需要定义两个管道。

管道可以理解为共享存储的优化和发展,因为在共享存储中,若某进程要访问共享存储空间则必须没有其他进程在该共享存储空间中进行写操作,否则访问行为就会被阻塞。而管道通信中存储空间进化成了缓冲区,缓冲区只允许一边写入、另一边读出,因此只要缓冲区中有数据,进程就能从缓冲区中读出,而不必担心会因为其他进程在其中进行写操作而遭到阻塞,因为写进程会先把缓冲区写满,然后才让读进程读,当缓冲区中还有数据时,写进程不会往缓冲区写数据。当然,这也决定了管道通信必然是半双工通信。

线程概念和多线程模型

线程的基本概念

引入进程的目的是更好地使多道程序并发执行,提高资源利用率和系统吞吐量;而引入线程的目的则是减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。

引入线程后,进程的内涵发生了改变,进程只作为除CPU外的系统资源的分配单元,而线程则作为处理机的分配单元。由于一个进程内部有多个线程,若线程的切换发生在同一个进程内部,则只需要很少的时空开销。

线程与进程的比较

1)调度。在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。在引入线程的操作系统中,线程是独立调度的基本单位,进程是拥有资源的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。

2)拥有资源。不论是传统操作系统还是设有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源(也有一点儿必不可少的资源),但线程可以访问其隶属进程的系统资源。要知道,若线程也是拥有资源的单位,则切换线程就需要较大的时空开销,线程这个概念的提出就没有意义。

3)并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。

4)系统开销。由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、IO设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU 环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信非常容易实现,甚至无须操作系统的干预。

5)地址空间和其他资源(如打开的文件)。进程的地址空间之间互相独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。

6)通信方面。进程间通信(IPC)需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段(如全局变量)来进行通信。

线程的属性

多线程操作系统把线程作为独立运行(或调度)的基本单位,此时的进程已不再是一个基本的可执行实体,但它仍具有与执行相关的状态。所谓进程处于“执行”状态,实际上是指该进程中的某线程正在执行。线程的主要属性如下:

1)线程是一个轻型实体,它不拥有系统资源,但每个线程都应有一个唯一的标识符和一个线程控制块,线程控制块记录了线程执行的寄存器和栈等现场状态。

2)不同的线程可以执行相同的程序,即同一个服务程序被不同的用户调用时,操作系统把它们创建成不同的线程。

3)同一进程中的各个线程共享该进程所拥有的资源。

4)线程是处理机的独立调度单位,多个线程是可以并发执行的。在单CPU的计算机系统中,各线程可交替地占用CPU;在多CPU的计算机系统中,各线程可同时占用不同的CPU,若各个CPU同时为一个进程内的各线程服务,则可缩短进程的处理时间。

5)一个线程被创建后,便开始了它的生命周期,直至终止。线程在生命周期内会经历阻塞态、就绪态和运行态等各种状态变化。

为什么线程的提出有利于提高系统并发性?可以这样来理解:由于有了线程,线程切换时,有可能会发生进程切换,也有可能不发生进程切换,平均而言每次切换所需的开销就变小了,因此能够让更多的线程参与并发,而不会影响到响应时间等问题。

线程的实现方式

线程的实现可以分为两类:用户级线程(User-Level Thread,ULT)和内核级线程(Kernel-LevelThread,KLT)。内核级线程又称内核支持的线程。

在用户级线程中,有关线程管理(线程的创建、撤销和切换等)的所有工作都由应用程序完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程开始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。图2.5(a)说明了用户级线程的实现方式。

在内核级线程中,线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息,调度也在内核基于线程架构的基础上完成。图2.5(b)说明了内核级线程的实现方式。

有些系统中使用组合方式的多线程实现。线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于等于用户级线程的数目)内核级线程上。图2.5(c)说明了用户级与内核级的组合实现方式。

图片详情找不到图片(Image not found)
多线程模型

有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程和内核级线程的连接方式。

1)多对一模型。将多个用户级线程映射到一个内核级线程,线程管理在用户空间完成。此模式中,用户级线程对操作系统不可见(即透明)。

优点:线程管理是在用户空间进行的,因而效率比较高。

缺点:一个线程在使用内核服务时被阻塞,整个进程都会被阻塞;多个线程不能并行地运行在多处理机上。

2)一对一模型。将每个用户级线程映射到一个内核级线程。

优点:当一个线程被阻塞后,允许另一个线程继续执行,所以并发能力较强。

缺点:每创建一个用户级线程都需要创建一个内核级线程与其对应,这样创建线程的开销比较大,会影响到应用程序的性能。

3)多对多模型。将n个用户级线程映射到m个内核级线程上,要求m≤n。

特点:多对多模型是多对一模型和一对一模型的折中,既克服了多对一模型并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。

此外,还拥有多对一模型和一对一模型各自的优点,可谓集两者之所长。

本节小结

为什么要引入进程?

在多道程序同时运行的背景下,进程之间需要共享系统资源,因此会导致各程序在执行过程中出现相互制约的关系,程序的执行会表现出间断性的特征。这些特征都是在程序的执行过程中发生的,是动态的过程,而传统的程序本身是一组指令的集合,是一个静态的概念,无法描述程序在内存中的执行情况,即我们无法从程序的字面上看出它何时执行、何时停顿,也无法看出它与其他执行程序的关系,因此,程序这个静态概念已不能如实反映程序并发执行过程的特征。为了深刻描述程序动态执行过程的性质乃至更好地支持和管理多道程序的并发执行,人们引入了进程的概念。

什么是进程?进程由什么组成?

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码本身,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

一个进程实体由程序段、相关数据段和 PCB三部分构成,其中 PCB是标志一个进程存在的唯一标识,程序段是进程运行的程序的代码,数据段则存储程序运行过程中相关的一些数据。

进程是如何解决问题的?

进程把能够识别程序运行态的一些变量存放在 PCB中,通过这些变量系统能够更好地了解进程的状况,并在适当时进行进程的切换,以避免一些资源的浪费,甚至划分为更小的调度单位一线程来提高系统的并发度。

本节主要介绍什么是进程,并围绕这个问题进行一些阐述和讨论,为下一节讨论的内容做铺垫,但之前未学过相关课程的读者可能会比较费解,到现在为止对进程这个概念还未形成比较清晰的认识。接下来,我们再用一个比较熟悉的概念来类比进程,以便大家能彻底理解本节的内容到底在讲什么,到底解决了什么问题。

我们用“人的生命历程”来类比进程。首先,人的生命历程一定是一个动态的、过程性的概念,要研究人的生命历程,先要介绍经历这个历程的主体是什么。主体当然是人,相当于经历进程的主体是进程映像,人有自己的身份,相当于进程映像里有PCB;人生历程会经历好几种状态:出生的时候、弥留的时候、充满斗志的时候、发奋图强的时候及失落的时候,相当于进程有创建、撤销、就绪、运行、阻塞等状态,这几种状态会发生改变,人会充满斗志而转向发奋图强,发奋图强获得进步之后又会充满斗志预备下一次发奋图强,或者发奋图强后遇到阻碍会进入失落状态,然后在别人的开导之下又重新充满斗志。类比进程,会由就绪态转向运行态,运行态转向就绪态,或者运行态转向阻塞态,然后在别的进程帮助下返回就绪态。

若我们用“人生历程”这个过程的概念去类比进程,则对进程的理解就会更深一层。前面生活化的例子可以帮我们理解进程的实质,但它毕竟有不严谨的地方。一种较好的方式是,在类比进程和“人生历程”后,再看一遍前面较为严谨的书面阐述和讨论,这样对知识的掌握会更加准确而全面。

这里再给出一些学习计算机科学知识的建议。学习科学知识时,很多同学会陷入一个误区,即只注重对定理、公式的应用,而忽视对基础概念的理解。这是我们从小到大为了应付考试而培养出的一个毛病,因为熟练应用公式和定理对考试有立竿见影的效果。公式、定理的应用固然重要,但基础概念的理解能让我们透彻地理解一门学科,更利于我们产生兴趣,培养创造性思维。

处理机调度

在学习本节时,请读者思考以下问题:

1)为什么要进行处理机调度?

2)调度算法有哪几种?结合第1章学习的分时操作系统和实时操作系统,思考哪种调度算法比较适合这两种操作系统。

希望读者能够在学习调度算法前,先自己思考一些调度算法,在学习的过程中注意把自己的想法与这些经典的算法进行比对,并学会计算一些调度算法的周转时间。

调度的概念

调度的基本概念

在多道程序系统中,进程的数量往往多于处理机的个数,因此进程争用处理机的情况在所难免。处理机调度是对处理机进行分配,即从就绪队列中按照一定的算法(公平、高效)选择一个进程并将处理机分配给它运行,以实现进程并发地执行。

处理机调度是多道程序操作系统的基础,是操作系统设计的核心问题。

调度的层次

一个作业从提交开始直到完成,往往要经历以下三级调度,如图2.6所示。

1) $\color{green}{\text{作业调度}}$ 。又称高级调度,其主要任务是按一定的原则从外存上处于后备状态的作业中挑选一个(或多个)作业,给它(们)分配内存、输入/输出设备等必要的资源,并建立相应的进程,以使它(们)获得竞争处理机的权利。简言之,作业调度就是内存与辅存之间的调度。对于每个作业只调入一次、调出一次。
多道批处理系统中大多配有作业调度,而其他系统中通常不需要配置作业调度。作业调度的执行频率较低,通常为几分钟一次。

2) $\color{green}{\text{中级调度}}$ 。又称内存调度,其作用是提高内存利用率和系统吞吐量。为此,应将那些暂时不能运行的进程调至外存等待,把此时的进程状态称为挂起态。当它们已具备运行条件且内存又稍有空闲时,由中级调度来决定把外存上的那些已具备运行条件的就绪进程再重新调入内存,并修改其状态为就绪态,挂在就绪队列上等待。

3) $\color{green}{\text{进程调度}}$ 。又称低级调度,其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。进程调度的频率很高,一般几十毫秒一次。

图2.6处理机的三级调度找不到图片(Image not found)
三级调度的联系

作业调度从外存的后备队列中选择一批作业进入内存,为它们建立进程,这些进程被送入就绪队列,进程调度从就绪队列中选出一个进程,并把其状态改为运行态,把CPU分配给它。中级调度是为了提高内存的利用率,系统将那些暂时不能运行的进程挂起来。当内存空间宽松时,通过中级调度选择具备运行条件的进程,将其唤醒。

1)作业调度为进程活动做准备,进程调度使进程正常活动起来,中级调度将暂时不能运行的进程挂起,中级调度处于作业调度和进程调度之间。

2)作业调度次数少,中级调度次数略多,进程调度频率最高。

3)进程调度是最基本的,不可或缺。

调度的时机、切换与过程

进程调度和切换程序是操作系统内核程序。请求调度的事件发生后,才可能运行进程调度程序,调度了新的就绪进程后,才会进行进程间的切换。理论上这三件事情应该顺序执行,但在实际设计中,操作系统内核程序运行时,若某时发生了引起进程调度的因素,则不一定能够马上进行调度与切换。

现代操作系统中,不能进行进程的调度与切换的情况有以下几种:

1)在 $\color{green}{\text{处理中断}}$ 的过程中。中断处理过程复杂,在实现上很难做到进程切换,而且中断处理是系统工作的一部分,逻辑上不属于某一进程,不应被剥夺处理机资源。

2)进程在操作系统 $\color{red}{\text{内核程序}}$ $\color{green}{\text{临界区}}$ 中。进入临界区后,需要独占式地访问共享数据,理论上必须加锁,以防止其他并行程序进入,在解锁前不应切换到其他进程运行,以加快该共享数据的释放。

3)其他需要完全屏蔽中断的 $\color{green}{\text{原子操作}}$ 过程中。如加锁、解锁、中断现场保护、恢复等原子操作。在原子过程中,连中断都要屏蔽,更不应该进行进程调度与切换。

若在上述过程中发生了引起调度的条件,则不能马上进行调度和切换,应置系统的请求调度标志,直到上述过程结束后才进行相应的调度与切换。

应该进行进程调度与切换的情况如下:

1)发生引起调度条件且当前进程无法继续运行下去时,可以马上进行调度与切换。若操作系统只在这种情况下进行进程调度,则是 $\color{green}{\text{非剥夺调度}}$ 。

2)中断处理结束或自陷处理结束后,返回被中断进程的用户态程序执行现场前,若置上请求调度标志,即可马上进行进程调度与切换。若操作系统支持这种情况下的运行调度程序,则实现了 $\color{green}{\text{剥夺方式}}$ 的调度。

进程切换往往在调度完成后立刻发生,它要求保存原进程当前切换点的现场信息,恢复被调度进程的现场信息。现场切换时,操作系统内核将原进程的现场信息推入当前进程的内核堆栈来保存它们,并更新堆栈指针。内核完成从新进程的内核栈中装入新进程的现场信息、更新当前运行进程空间指针、重设PC寄存器等相关工作之后,开始运行新的进程。

进程调度方式

所谓进程调度方式,是指当某个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要处理,即有优先权更高的进程进入就绪队列,此时应如何分配处理机。

通常有以下两种进程调度方式:

1)非剥夺调度方式,又称非抢占方式。非剥夺调度方式是指当一个进程正在处理机上执行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行直到该进程完成或发生某种事件而进入阻塞态时,才把处理机分配给更为重要或紧迫的进程。

在非剥夺调度方式下,一旦把CPU分配给一个进程,该进程就会保持CPU直到终止或转换到等待态。这种方式的优点是实现简单、系统开销小,适用于大多数的批处理系统但它不能用于分时系统和大多数的实时系统。

2)剥夺调度方式,又称抢占方式。剥夺调度方式是指当一个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给这个更为重要或紧迫的进程。

采用剥夺式的调度,对提高系统吞吐率和响应效率都有明显的好处。但“剥夺”不是一种任意性行为,必须遵循一定的原则,主要有优先权、短进程优先和时间片原则等。

调度的基本准则

不同的调度算法具有不同的特性,在选择调度算法时,必须考虑算法的特性。为了比较处理机调度算法的性能,人们提出了很多评价准则,下面介绍其中主要的几种:

1) $\color{green}{\text{CPU利用率}}$ 。CPU是计算机系统中最重要和昂贵的资源之一,所以应尽可能使CPU保持“忙”状态,使这一资源利用率最高。

2) $\color{green}{\text{系统吞吐量}}$ 。表示单位时间内CPU完成作业的数量。长作业需要消耗较长的处理机时间,因此会降低系统的吞吐量。而对于短作业,它们所需要消耗的处理机时间较短,因此能提高系统的吞吐量。调度算法和方式的不同,也会对系统的吞吐量产生较大的影响。

3) $\color{green}{\text{周转时间}}$ 。周转时间是指从作业提交到作业完成所经历的时间,是作业等待、在就绪队列中排队、在处理机上运行及进行输入/输出操作所花费时间的总和。

作业的周转时间可用公式表示如下:

周转时间=作业完成时间-作业提交时间

平均周转时间是指多个作业周转时间的平均值:

平均周转时间=(作业1的周转时间+…+作业 $n$ 的周转时间)/ $n$

$\color{red}{\text{带权周转时间}}$ 是指 $\color{green}{\text{作业周转时间}}$ 与 $\color{green}{\text{作业实际运行时间}}$ 的比值:

$\text{带权周转时间}=\dfrac{\text{作业周转时间}}{作业实际运行时间}$

$\color{red}{\text{平均带权周转时间}}$ 是指多个作业带权周转时间的平均值:

平均带权周转时间=(作业1的带权周转时间+..+作业 $n$ 的带权周转时间)/ $n$

4)等待时间。等待时间指进程处于等处理机状态的时间之和,等待时间越长,用户满意度越低。处理机调度算法实际上并不影响作业执行或输入/输出操作的时间,只影响作业在就绪队列中等待所花的时间。因此,衡量一个调度算法的优劣,常常只需简单地考察等待时间。

5)响应时间。响应时间指从用户提交请求到系统首次产生响应所用的时间。在交互式系统中,周转时间不可能是最好的评价准则,一般采用响应时间作为衡量调度算法的重要准则之一。从用户角度来看,调度策略应尽量降低响应时间,使响应时间处在用户能接受的范围之内。

要想得到一个满足所有用户和系统要求的算法几乎是不可能的。设计调度程序,一方面要满足特定系统用户的要求(如某些实时和交互进程的快速响应要求),另一方面要考虑系统整体效率(如减少整个系统的进程平均周转时间),同时还要考虑调度算法的开销。

典型的调度算法

先来先服务(FCFS)调度算法

FCFS 调度算法是一种最简单的调度算法,它既可用于作业调度,又可用于进程调度。在作业调度中,算法每次从后备作业队列中选择最先进入该队列的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。

在进程调度中,FCFS 调度算法每次从就绪队列中选择最先进入该队列的进程,将处理机分配给它,使之投入运行,直到完成或因某种原因而阻塞时才释放处理机。

下面通过一个实例来说明FCFS调度算法的性能。假设系统中有4个作业,它们的提交时间分别是8,8.4,8.8,9,运行时间依次是2,1,0.5,0.2,系统采用FCFS调度算法,这组作业的平均等待时间、平均周转时间和平均带权周转时间见表2.2。

表2.2 FCFS调度算法的性能找不到图片(Image not found)

FCFS 调度算法属于不可剥夺算法。从表面上看,它对所有作业都是公平的,但若一个长作业先到达系统,就会使后面的许多短作业等待很长时间,因此它不能作为分时系统和实时系统的主要调度策略。但它常被结合在其他调度策略中使用。例如,在使用优先级作为调度策略的系统中,往往对多个具有相同优先级的进程按FCFS原则处理。

FCFS 调度算法的特点是算法简单,但效率低;对长作业比较有利,但对短作业不利(相对SJF和高响应比);有利于CPU繁忙型作业,而不利于I/O繁忙型作业。

短作业优先(SJF)调度算法

短作业(进程)优先调度算法是指对短作业(进程)优先调度的算法。短作业优先(SJF)调度算法从后备队列中选择一个或若干估计运行时间最短的作业,将它们调入内存运行;短进程优先(SPF)调度算法从就绪队列中选择一个估计运行时间最短的进程,将处理机分配给它,使之立即执行,直到完成或发生某事件而阻塞时,才释放处理机。

例如,考虑表2.2中给出的一组作业,若系统采用短作业优先调度算法,其平均等待时间、平均周转时间和平均带权周转时间见表2.3。

表2.3SJF调度算法的性能找不到图片(Image not found)

SJF调度算法也存在不容忽视的缺点:

1)该算法对长作业不利,由表2.2和表2.3可知,SJF调度算法中长作业的周转时间会增加更严重的是,若有一长作业进入系统的后备队列,由于调度程序总是优先调度那些(E使是后进来的)短作业,将导致长作业长期不被调度(“饥饿”现象,注意区分“死锁”后者是系统环形等待,前者是调度策略问题)。

2)该算法完全未考虑作业的紧迫程度,因而不能保证紧迫性作业会被及时处理。

3)由于作业的长短只是根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度。

注意,SJF调度算法的平均等待时间、平均周转时间最少。

优先级调度算法

优先级调度算法又称优先权调度算法,它既可用于作业调度,又可用于进程调度。该算法中的优先级用于描述作业运行的紧迫程度。

在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最高的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。

根据新的更高优先级进程能否抢占正在执行的进程,可将该调度算法分为如下两种:

1) $\color{green}{\text{非剥夺式优先级调度算法}}$ 。当一个进程正在处理机上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于其自身的原因而主动让出处理机时(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。

2) $\color{green}{\text{剥夺式优先级调度算法}}$ 。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。

而根据进程创建后其优先级是否可以改变,可以将进程优先级分为以下两种:

1) $\color{green}{\text{静态优先级}}$ 。优先级是在创建进程时确定的,且在进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求。

2) $\color{green}{\text{动态优先级}}$ 。在进程运行过程中,根据进程情况的变化动态调整优先级。动态调整优先级的主要依据有进程占有CPU时间的长短、就绪进程等待CPU时间的长短。

一般来说,进程优先级的设置可以参照以下原则:

1) $\color{green}{\text{系统进程}}$ > $\color{green}{\text{用户进程}}$ 。系统进程作为系统的管理者,理应拥有更高的优先级。

2) $\color{green}{\text{交互型进程}}$ > $\color{green}{\text{非交互型进程}}$ (或前台进程>后台进程)。大家平时在使用手机时,在前台运行的正在和你交互的进程应该更快速地响应你,因此自然需要被优先处理,即要有更高的优先级。

3) $\color{green}{\text{I/O型进程}}$ > $\color{green}{\text{计算型进程}}$ 。所谓IO型进程,是指那些会频繁使用IO设备的进程,而计算型进程是那些频繁使用CPU的进程(很少使用I/O设备)。我们知道,IO设备(如打印机)的处理速度要比CPU慢得多,因此若将IO型进程的优先级设置得更高,就更有可能让IO设备尽早开始工作,进而提升系统的整体效率。

高响应比优先调度算法

高响应比优先调度算法主要用于作业调度,是对FCFS调度算法和SJF调度算法的一种综合平衡,同时考虑了每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。

$\color{red}{\text{响应比}}$ 的变化规律可描述为

$\text{响应比}R_p=\dfrac{\text{等待时间}+\text{要求服务时间}}{\text{要求服务时间}}$

根据公式可知:
1)作业的等待时间相同时,要求服务时间越短,响应比越高,有利于 $\color{green}{\text{短作业}}$ 。

2)要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是 $\color{green}{\text{先来先服务}}$ 。

3)对于长作业,作业的响应比可以随等待时间的增加而提高,等待时间足够长时,其响应比便可升到很高,从而也可获得处理机。因此,克服了 $\color{green}{\text{饥饿状态}}$ ,兼顾了长作业。

时间片轮转调度算法

时间片轮转调度算法主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列,进程调度程序总是选择就绪队列中的第一个进程执行,即先来先服务的原则,但仅能运行一个时间片,如100ms。在使用完一个时间片后,即使进程并未完成其运行,它也必须释放出(被剥夺)处理机给下一个就绪的进程,而被剥夺的进程返回到就绪队列的末尾重新排队,等候再次运行。

在时间片轮转调度算法中,时间片的大小对系统性能的影响很大。若时间片足够大,以至于所有进程都能在一个时间片内执行完毕,则时间片轮转调度算法就退化为先来先服务调度算法。若时间片很小,则处理机将在进程间过于频繁地切换,使处理机的开销增大,而真正用于运行用户进程的时间将减少。因此,时间片的大小应选择适当。

时间片的长短通常由以下因素确定:系统的响应时间、就绪队列中的进程数目和系统的处理能力。

多级反馈队列调度算法(融合了前几种算法的优点)

多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合与发展,如图2.7所示。通过动态调整进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。例如,为提高系统吞吐量和缩短平均周转时间而照顾短进程;为获得较好的IO设备利用率和缩短响应时间而照顾IO型进程;同时,也不必事先估计进程的执行时间。

图片详情找不到图片(Image not found)

多级反馈队列调度算法的实现思想如下:

1)设置多个就绪队列,并为各个队列赋予不同的优先级,第1级队列的优先级最高,第2级队列次之,其余队列的优先级逐次降低。

2)赋予各个队列中进程执行时间片的大小各不相同。在优先级越高的队列中,每个进程的运行时间片越小。例如,第2级队列的时间片要比第1级队列的时间片长1倍……第 $i$ +1级队列的时间片要比第 $i$ 级队列的时间片长1倍。

3)一个新进程进入内存后,首先将它放入第1级队列的末尾,按FCFS原则排队等待调度当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;若它在一个时间片结束时尚未完成,调度程序便将该进程转入第2级队列的末尾,再同样按FCFS原则等待调度执行;若它在第2级队列中运行一个时间片后仍未完成,再以同样的方法放入第3级队列……如此下去,当一个长进程从第1级队列依次降到第 $n$ 级队列后,在第 $n$ 级队列中便采用时间片轮转的方式运行。

4)仅当第1级队列为空时,调度程序才调度2级队列中的进程运行;仅当第1~( $i$ -1)级队列均为空时,才会调度第 $i$ 级队列中的进程运行。若处理机正在执行第 $i$ 级队列中的某进程,这时又有新进程进入优先级较高的队列〔第1~( $i$ -1)中的任何一个队列],则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回第i级队列的末尾,把处理机分配给新到的更高优先级的进程。

多级反馈队列的优势有以下几点:

1)终端型作业用户:短作业优先。

2)短批处理作业用户:周转时间较短。

3)长批处理作业用户:经过前面几个队列得到部分执行,不会长期得不到处理。

本章小结

本节开头提出的问题的参考答案如下。

为什么要进行处理机调度?

若没有处理机调度,同意味着要等到当前运行的进程执行完毕后,下一个进程才能执行,而实际情况中,进程时常需要等待一些外部设备的输入,而外部设备的速度与处理机相比是非常缓慢的,若让处理机总是等待外部设备,则对处理机的资源是极大的浪费。而引进处理机调度后,可在运行进程等待外部设备时,把处理机调度给其他进程,从而提高处理机的利用率。用一句简单的话说,就是为了合理地处理计算机的软/硬件资源。

调度算法有哪几种?结合第1章学习的分时操作系统和实时操作系统,思考有没有哪种调度算法比较适合这两种操作系统。

本节介绍的调度算法有先来先服务调度算法、短作业优先调度算法、优先级调度算法、高响应比优先调度算法、时间片轮转调度算法、多级反馈队列调度算法6种。

先来先服务算法和短作业优先算法无法保证及时地接收和处理问题,因此尤法休证仕规疋时时间间隔内响应每个用户的需求,也同样无法达到实时操作系统的及的性而水。优兀级A度开仫按照任务的优先级进行调度,对于更紧急的任务给予更高的优先级,适合实时操作系统。

高响应比优先调度算法、时间片轮转调度算法、多级反馈队列调度算法都能保证每个任务在一定时间内分配到时间片,并轮流占用CPU,适合分时操作系统。

本节主要介绍了处理机调度的概念。操作系统主要管理处理机、内存、文件、设备几种资源,只要对资源的请求大于资源本身的数量,就会涉及调度。例如,在单处理机系统中,处理机只有一个,而请求服务的进程却有多个,所以就有处理机调度的概念出现。而出现调度的概念后,又有了一个问题,即如何调度、应该满足谁、应该让谁等待,这是调度算法所回答的问题;而应该满足谁、应该让谁等待,要遵循一定的准则,即调度的准则。调度这一概念贯穿于操作系统的始终,读者在接下来的学习中,将接触到几种资源的调度问题和相应的调度算法。将它们与处理机调度的内容相对比,将会发现它们有异曲同工之妙。

进程同步

在学习本节时,请读者思考以下问题:

1)为什么要引入进程同步的概念?

2)不同的进程之间会存在什么关系?

3)当单纯用本节介绍的方法解决这些问题时会遇到什么新的问题吗?

用PV操作解决进程之间的同步互斥问题是这一节的重点,考试已经多次考查过这一内容,读者务必多加练习,掌握好求解问题的方法。

  • 大题中遇到相关的题目,信号量的类型直接定义为semaphore

进程同步的基本概念

在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。下面举一个简单的例于米帮大豕理解赵个概念。例如,让系统计算1+2x3,假设系统产生两个进程:一个是加法进程,一个是乘法进程。要让计算结果是正确的,一定要让加法进程发生在乘法进程之后,但实际上操作系统具有异步性,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生,而这种机制就是本节要讨论的内容。

临界资源

虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,我们将一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。

对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。为了保证临界资源的正确使用,可把临界资源的访问过程分成4个部分:

1) $\color{green}{\text{进入区}}$ 。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。

2) $\color{green}{\text{临界区}}$ 。进程中访问临界资源的那段代码,又称临界段。

3) $\color{green}{\text{退出区}}$ 。将正在访问临界区的标志清除。

4) $\color{green}{\text{剩余区}}$ 。代码中的其余部分。

代码详情
1
2
3
4
5
6
do{
entry section; //进入区
critical section; //临界区
exit section;//退出区
remainder section;//剩余区
} while (true)
同步

同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系源于它们之间的相互合作。

例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B就被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。

互斥

互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。

例如,在仅有一台打印机的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程唤醒,并将其由阻塞态变为就绪态。

为禁止两个进程同时进入临界区,同步机制应遵循以下准则:

1) $\color{green}{\text{空闲让进}}$ 。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。

2) $\color{green}{\text{忙则等待}}$ 。当已有进程进入临界区时,其他试图进入临界区的进程必须等待。

3) $\color{green}{\text{有限等待}}$ 。对请求访问的进程,应保证能在有限时间内进入临界区。

4) $\color{green}{\text{让权等待}}$ 。当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。

实现临界区互斥的基本方法

软件实现方法

在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。

1)算法一:单标志法。该算法设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,即若turn=0,则允许 $P_0$ 进程进入临界区。该算法可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背“空闲让进”)。这样很容易造成资源利用不充分。若 $P_0$ 顺利进入临界区并从临界区离开,则此时临界区是空闲的,但 $P_1$ 并没有进入临界区的打算,turn=1一直成立, $P_0$ 就无法再次进入临界区(一直被while死循环困住)。

图片详情找不到图片(Image not found)

2)算法二:双标志法先检查。该算法的基本思想是在每个进程访问临界区资源之前,先查看临界资源是否正被访问,若正被访问,该进程需等待;否则,进程才进入自己的临界区。为此,设置一个数据flag[ $i$ ],如第i个元素值为FALSE,表示 $P_i$ 进程未进入临界区,值为TRUE,表示P进程进入临界区。

图片详情找不到图片(Image not found)

优点:不用交替进入,可连续使用;缺点: $P_i$ 和 $P_j$ ,可能同时进入临界区。按序列①② ${\textstyle\unicode{x2462}}$ ${\textstyle\unicode{x2463}}$ 执行时,会同时进入临界区(违背“忙则等待”)。即在检查对方的 flag 后和切换自己的flag前有一段时间,结果都检查通过。这里的问题出在检查和修改操作不能一次进行。

3)算法三:双标志法后检查。算法二先检测对方的进程状态标志,再置自己的标志,由在检测和放置中可插入另一个进程到达时的检测操作,会造成两个进程在分别检测后同时进入临界区。为此,算法三先将自己的标志设置为 TRUE,再检测对方的状态标志,若对方标志为TRUE,则进程等待;否则进入临界区。

图片详情找不到图片(Image not found)

两个进程几乎同时都想进入临界区时,它们分别将自己的标志值 flag 设置为 TRUE,并且同时检测对方的状态(执行while语句),发现对方也要进入临界区时,双方互相谦让,结果谁也进不了临界区,从而导致“饥饿”现象。

4)算法四:Peterson’s Algorithm。为了防止两个进程为进入临界区而无限期等待,又设置变量 turn,每个进程在先设置自己的标志后再设置turn标志。这时,再同时检测另一个进程状态标志和不允许进入标志,以便保证两个进程同时要求进入临界区时,只允许个进程进入临界区。

图片详情找不到图片(Image not found)

具体如下:考虑进程 $P_i$ ,一旦设置flag[ $i$ ] = true,就表示它想要进入临界区,同时turn =j,此时若进程 $P_j$ 已在临界区中,符合进程 $P_i$ 中的while循环条件,则 $P_i$ 不能进入临界区。若 $P_j$ 不想要进入临界区,即 flag[ $j$ ] = false,循环条件不符合,则 $P_i$ 可以顺利进入,反之亦然。本算法的基本思想是算法一和算法三的结合。利用flag解决临界资源的互斥访问,而利用turn解决“饥饿”现象。

理解Peterson’s Algorithm的最好方法就是手动模拟。

硬件实现方法

理解本节介绍的硬件实现,对学习后面的信号量很有帮助。计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或对两个字的内容进行交换等。通过硬件支持实现临界段问题的方法称为低级方法,或称元方法。

(1)中断屏蔽方法

当一个进程正在使用处理机执行它的临界区代码时,防止其他进程进入其临界区进行访问的最简方法是,禁止一切中断发生,或称之为屏蔽中断、关中断。因为CPU只在发生中断时引起进程切换,因此屏蔽中断能够保证当前运行的进程让临界区代码顺利地执行完,进而保证互斥的正确实现,然后执行开中断。其典型模式为

图片详情找不到图片(Image not found)

这种方法限制了处理机交替执行程序的能力,因此执行的效率会明显降低。对内核来说,在它执行更新变量或列表的几条指令期间,关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断后不再开中断,则系统可能会因此终止。

(2)硬件指令方法

TestAndSet 指令:这条指令是原子操作,即执行该代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。指令的功能描述如下:

图片详情找不到图片(Image not found)

可以为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态: true表示正被占用,初值为false。在进程访问临界资源之前,利用TestAndSet检查和修改标志lock;若有进程在临界区,则重复检查,直到进程退出。利用该指令实现进程互斥的算法描述如下:

图片详情找不到图片(Image not found)

Swap指令:该指令的功能是交换两个字(字节)的内容。其功能描述如下:

图片详情找不到图片(Image not found)

注意:以上对TestAndSet和Swap指令的描述仅是功能实现,而并非软件实现的定义。事实上,它们是由硬件逻辑直接实现的,不会被中断。

应为每个临界资源设置一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量 key,用于与lock交换信息。在进入临界区前,先利用Swap 指令交换lock 与 key的内容,然后检查key 的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap指令实现进程互斥的算法描述如下:

图片详情找不到图片(Image not found)

硬件方法的优点:适用于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。

硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”现象。

无论是软件实现方法还是硬件实现方法,读者只需理解它的执行过程即可,关键是软件实现方法。实际练习和考试中很少让读者写出某种软件和硬件实现方法,因此读者并不需要默写或记忆。以上的代码实现与我们平时在编译器上写的代码意义不同,以上的代码实现是为了表述进程实现同步和互斥的过程,并不是说计算机内部实现同步互斥的就是这些代码。

信号量

信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,它只能被两个标准的 $\color{green}{\text{原语}}$ wait(S)和 signal(S)访问,也可记为“P操作”和“V操作”。

原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常可由硬件来实现。例如,前述的Test-and-Set和Swap指令就是由硬件实现的原子操作。原语功能的不被中断执行特性在单处理机上可由软件通过屏蔽中断方法实现。

原语之所以不能被中断执行,是因为原语对变量的操作过程若被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题。若能够找到一种解决临界段问题的元方法,就可以实现对共享变量操作的原子性。

整型信号量

整型信号量被定义为一个用于表示资源数目的整型量S,wait和 signal操作可描述为

图片详情找不到图片(Image not found)

wait 操作中,只要信号量S≤0,就会不断地测试。因此,该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。

记录型信号量

记录型信号量是不存在“忙等”现象的进程同步机制。除需要一个用于代表资源数目的整型变量value 外,再增加一个进程链表L,用于链接所有等待该资源的进程。记录型信号量得名于采用了记录型的数据结构。记录型信号量可描述为

图片详情找不到图片(Image not found)

相应的 wait(S)和 signal(S)的操作如下:

图片详情找不到图片(Image not found)

wait操作,S.value—表示进程请求一个该类资源,当S.value <0时,表示该类资源已分配完毕,因此进程应调用block 原语,进行自我阻塞,放弃处理机,并插入该类资源的等待队列S.L,可见该机制遵循了“让权等待”的准则。

图片详情找不到图片(Image not found)

signal操作,表示进程释放一个资源,使系统中可供分配的该类资源数增1,因此有S.value ++。若加1后仍是S.value≤0,则表示在S.L中仍有等待该资源的进程被阻塞,因此还应调用wakeup原语,将S.L中的第一个等待进程唤醒。

利用信号量实现同步

信号量机制能用于解决进程间的各种同步问题。设S为实现进程 $P_1$ , $P_2$ 同步的公共信号量,初值为0。进程 $P_2$ 中的语句y要使用进程 $P_1$ 中语句x的运行结果,所以只有当语句x执行完成之后语句y 才可以执行。其实现进程同步的算法如下:

图片详情找不到图片(Image not found)

若 $P_2$ 先执行到P(S)时,S为0,执行Р操作会把进程 $P_2$ 阻塞,并放入阻塞队列;当进程 $P_1$ 中的x执行完后,执行V操作,把 $P_2$ 从阻塞队列中放回就绪队列,当 $P_2$ 得到处理机时,就得以继续执行。

利用信号量实现进程互斥

信号量机制也能很方便地解决进程互斥问题。设S为实现进程 $P_1$ , $P_2$ 互斥的信号量,由于每次只允许一个进程进入临界区﹐所以S的初值应为1(即可用资源数为1)。只需把临界区置于P(S)和V(S)之间,即可实现两个进程对临界资源的互斥访问。其算法如下:

图片详情找不到图片(Image not found)

当没有进程在临界区时,任意一个进程要进入临界区,就要执行Р操作,把S的值减为0,然后进入临界区;当有进程存在于临界区时,S的值为0,再有进程要进入临界区,执行Р操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥。

互斥是不同进程对同一信号量进行P,V操作实现的,一个进程成功对信号量执行了Р操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入。

下面简单总结一下PV操作在同步互斥中的应用:在同步问题中,若某个行为要用到某种资源,则在这个行为前面Р这种资源一下;若某个行为会提供某种资源,则在这个行为后面V这种资源一下。在互斥问题中,P, V操作要紧夹使用互斥资源的那个行为,中间不能有其他冗余代码。

利用信号量实现前驱关系
图片详情找不到图片(Image not found)

信号量也可用来描述程序之间或语句之间的前驱关系。图2.8给出了一个前驱图,其中 $S_1$ , $S_2$ , $S_3$ ,… , $S_6$ 是最简单的程序段(只有一条语句)。为使各程序段能正确执行,应设置若干初始值为“0”的信号量。例如,为保证 $S_1$ → $S_2$ , $S_1$ → $S_3$ 的前驱关系,应分别设置信号量al, a2。同样,为保证 $S_2$ → $S_4$ , $S_2$ → $S_5$ , $S_5$ → $S_6$ , $S_4$ → $S_6$ , $S_5$ → $S_6$ ,应设置信号量b1, b2,c, d,e。

图片详情找不到图片(Image not found)
分析进程同步和互斥问题的方法步骤

1)关系分析。找出问题中的进程数,并分析它们之间的同步和互斥关系。同步、互斥、前驱关系直接按照上面例子中的经典范式改写。

2)整理思路。找出解决问题的关键点,并根据做过的题目找出求解的思路。根据进程的操作流程确定Р操作、V操作的大致顺序。

3)设置信号量。根据上面的两步,设置需要的信号量,确定初值,完善整理。

这是一个比较直观的同步问题,以 $S_2$ 为例,它是 $S_1$ 的后继,所以要用到 $S_1$ 的资源,在前面的简单总结中我们说过,在同步问题中,要用到某种资源,就要在行为(题中统一抽象成L)前面Р这种资源一下。 $S_2$ 是 $S_4$ , $S_5$ 的前驱,给 $S_4$ , $S_5$ ,提供资源,所以要在L行为后面V由 $S_4$ 和 $S_5$ 代表的资源一下。

管程

在信号量机制中,每个要访问临界资源的进程都必须自备同步的PV操作,大量分散的同步操作给系统管理带来了麻烦,且容易因同步操作不当而导致系统死锁。于是,便产生了一种新的进程同步工具-管程。管程的特性保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量,可以让程序员灵活地实现进程同步。

管程的定义

系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。

利用共享数据结构抽象地表示系统中的共享资源,而把对该数据结构实施的操作定义为一组过程。进程对共享资源的申请、释放等操作,都通过这组过程来实现,这组过程还可以根据资源情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。这个代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,称为管程( monitor )。管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。

由上述定义可知,管程由4部分组成:

①管程的名称;
${\textstyle\unicode{x2461}}$ 局部于管程内部的共享结构数据说明;

${\textstyle\unicode{x2462}}$对该数据结构进行操作的一组过程(或函数);

${\textstyle\unicode{x2463}}$ 对局部于管程内部的共享数据设置初始值的语句。

管程的定义描述举例如下:

图片详情找不到图片(Image not found)

熟悉面向对象程序设计的读者看到管程的组成后,会立即联想到管程很像一个类(class)。

1)管程把对共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能进入管程访问共享资源。对于上例,外部进程只能通过调用take_away()过程来申请一个资源;归还资源也一样。

2)每次仅允许一个进程进入管程,从而实现进程互斥。若多个进程同时调用take_away(),give_back(),则只有某个进程运行完它调用的过程后,下个进程才能开始运行它调用的过程。也就是说,各个进程只能串行执行管程内的过程,这一特性保证了进程“互斥”访问共享数据结构S。

条件变量

当一个进程进入管程后被阻塞,直到阻塞的原因解除时,在此期间,如果该进程不释放管程,那么其他进程无法进入管程。为此,将阻塞原因定义为条件变量condition。通常,一个进程被阻塞的原因可以有多个,因此在管程中设置了多个条件变量。每个条件变量保存了一个等待队列,用于记录因该条件变量而阻塞的所有进程,对条件变量只能进行两种操作,即 wait和 signal。

x.wait:当x对应的条件不满足时,正在调用管程的进程调用x.wait将自己插入x条件的等待队列,并释放管程。此时其他进程可以使用该管程。

x.signal: x对应的条件发生了变化,则调用x.signal,唤醒一个因x条件而阻塞的进程。下面给出条件变量的定义和使用:

图片详情找不到图片(Image not found)

条件变量和信号量的比较:

相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞/唤醒。

不同点:条件变量是“没有值”的,仅实现了“排队等待”功能;而信号量是“有值”的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。

经典同步问题

生产者-消费者问题

问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。

问题分析:

1)关系分析。生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。

2)整理思路。这里比较简单,只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步PV操作的位置。

3)信号量设置。信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1;信号量full用于记录当前缓冲池中的“满”缓冲区数,初值为0。信号量empty用于记录当前缓冲池中的“空”缓冲区数,初值为 $n$ 。

我们对同步互斥问题的介绍是一个循序渐进的过程。上面介绍了一个同步问题的例子和一个互斥问题的例子,下面来看生产者-消费者问题的例子是什么样的。

生产者-消费者进程的描述如下:

图片详情找不到图片(Image not found)

该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对empty变量执行Р操作,一旦取走一个产品便要执行V操作以释放空闲区。对empty和 full变量的Р操作必须放在对mutex 的P操作之前。若生产者进程先执行P(mutex),然后执行P(empty),消费者执行P(mutex),然后执行 P(full),这样可不可以﹖答案是否定的。设想生产者进程已将缓冲区放满,消费者进程并没有取产品,即 empty =0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,因此陷入了无休止的等待。同理,若消费者进程已将缓冲区取空,即 full = 0,下次若还是消费者先运行,也会出现类似的死锁。不过生产者释放信号量时,mutex, full 先释放哪一个无所谓,消费者先释放mutex 或empty都可以。

根据对同步互斥问题的简单总结,我们发现,其实生产者-消费者问题只是一个同步互斥问题的综合而已。

下面再看一个较为复杂的生产者-消费者问题。

问题描述:桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。

问题分析:

1)关系分析。这里的关系要稍复杂一些。由每次只能向其中放入一只水果可知,爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发,如图2.9所示。

2)整理思路。这里有4个进程,实际上可抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。

3)信号量设置。首先将信号量plate设置互斥信号量,表示是否允许向盘子放入水果,初值为1表示允许放入,且只允许放入一个。信号量apple表示盘子中是否有苹果,初值为0表示盘子为空,不许取,apple = 1表示可以取。信号量orange表示盘子中是否有橘子,初值为0表示盘子为空,不许取,orange =1表示可以取。

图2.9进程之间的关系找不到图片(Image not found)

解决该问题的代码如下:

图片详情找不到图片(Image not found)

进程间的关系如图2.9所示。dad()和 daughter()、mom()和 son()必须连续执行,正因为如此,也只能在女儿拿走苹果后或儿子拿走橘子后才能释放盘子,即V(plate)操作。

读者-写者问题

问题描述:有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

问题分析:

1)关系分析。由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

2)整理思路。两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的Р操作、V操作即可解决。读者的问题比较复杂,它必须在实现与写者互斥的同时,实现与其他读者的同步,因此简单的一对Р操作、V操作是无法解决问题的。这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,这里不同读者对计数器的访问也应该是互斥的。

3)信号量设置。首先设置信号量count为计数器,用于记录当前读者的数量,初值为0;设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rw,用于保证读者和写者的互斥访问。

代码如下:

图片详情找不到图片(Image not found)

在上面的算法中,读进程是优先的,即当存在读进程时,写操作将被延迟,且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式会导致写进程可能长时间等待,且存在写进程“饿死”的情况。

若希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

图片详情找不到图片(Image not found)

这里的写进程优先是相对而言的,有些书上把这个算法称为读写公平法,即读写进程具有-一样的优先级。当一个写进程访问文件时,若先有一些读进程要求访问文件,后有另一个写进程要求访问文件,则当前访问文件的进程结束对文件的写操作时,会是一个读进程而不是一个写进程占用文件(在信号量w的阻塞队列上,因为读进程先来,因此排在阻塞队列队首,而V操作唤醒进程时唤醒的是队首进程),所以说这里的写优先是相对的,想要了解如何做到真正写者优先,

可参考其他相关资料。

读者-写者问题有一个关键的特征,即有一个互斥访问的计数器count,因此遇到一个不太好解决的同步互斥问题时,要想一想用互斥访问的计数器count能否解决问题。

哲学家进餐问题
图片详情找不到图片(Image not found)

问题描述:一张圆桌边上坐着5名哲学家,每两名哲学家之间的桌上摆一根筷子,两根筷子中间是一碗米饭,如图2.10所示。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,进餐完毕后,放下筷子继续思考。

问题分析:

1)关系分析。5名哲学家与左右邻居对其中间筷子的访问是互斥关系。

2)整理思路。显然,这里有5个进程。本题的关键是如何让一名哲学家拿到左右两根筷子而不造成死锁或饥饿现象。解决方法有两个:一是让他们同时拿两根筷子;二是对每名哲学家的动作制定规则,避免饥饿或死锁现象的发生。

3)信号量设置。定义互斥信号量数组chopstick[ 5 ]={1,1,1,1,1},用于对5个筷子的互斥访问。哲学家按顺序编号为0~4,哲学家i左边筷子的编号为i,哲学家右边筷子的编号为(i +1)%5。

图片详情找不到图片(Image not found)

该算法存在以下问题:当5名哲学家都想要进餐并分别拿起左边的筷子时(都恰好执行完wait(chopstick[ i]);)筷子已被拿光,等到他们再想拿右边的筷子时(执行wait(chopstick[(i + 1)%5]);)就全被阻塞,因此出现了死锁。
为防止死锁发生,可对哲学家进程施加一些限制条件,比如至多允许4名哲学家同时进餐;仅当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子;对哲学家顺序编号,要求奇数号哲学家先拿左边的筷子,然后拿右边的筷子,而偶数号哲学家刚好相反。

制定的正确规则如下:假设采用第二种方法,当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子。

图片详情找不到图片(Image not found)

此外,还可采用AND型信号量机制来解决哲学家进餐问题,有兴趣的读者可以查阅相关资料,自行思考。

熟悉ACM或有过相关训练的读者都应知道贪心算法,哲学家进餐问题的思想其实与贪心算法的思想截然相反,贪心算法强调争取眼前认为最好的,而不考虑后续会有什么后果。若哲学家进餐问题用贪心算法来解决,即只要眼前有筷子能拿起就拿起的话,就会出现死锁。然而,若不仅考虑眼前的一步,而且考虑下一步,即不因为有筷子能拿起就拿起,而考虑能不能一次拿起两根筷子才做决定的话,就会避免死锁问题,这就是哲学家进餐问题的思维精髓。

大部分练习题和真题用消费者-生产者模型或读者-写者问题就能解决,但对于哲学家进餐问题和吸烟者问题仍然要熟悉。考研复习的关键在于反复多次和全面,“偷工减料”是要吃亏的。

吸烟者问题

问题描述:假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌上,如此重复(让三个抽烟者轮流地抽烟)。

问题分析:
1)关系分析。供应者与三个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。

2)整理思路。显然这里有4个进程。供应者作为生产者向三个抽烟者提供材料。

3)信号量设置。信号量offer1, offer2, offer3分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源。信号量finish用于互斥进行抽烟动作。

代码如下:

图片详情找不到图片(Image not found)

本节小结

本节开头提出的问题的参考答案如下。

为什么要引入进程同步的概念?

在多道程序共同执行的条件下,进程与进程是并发执行的,不同进程之间存在不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。

不同的进程之间会存在什么关系?

进程之间存在同步与互斥的制约关系。

同步是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。

互斥是指当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。

当单纯用本节介绍的方法解决这些问题时会遇到什么新的问题吗?

当两个或两个以上的进程在执行过程中,因占有一些资源而又需要对方的资源时,会因为争夺资源而造成一种互相等待的现象,若无外力作用,它们都将无法推进下去。这种现象称为死锁,具体介绍和解决方案请参考下一节。

死锁

在学习本节时,请读者思考以下问题:

1)为什么会产生死锁?产生死锁有什么条件?

2)有什么办法可以解决死锁问题?

学完本节,读者应了解死锁的由来、产生条件及基本解决方法,区分死锁的避免和死锁的预防。

死锁的概念

死锁的定义

在多道程序系统中,由于多个进程的并发执行,改善了系统资源的利用率并提高了系统的处理能力。然而,多个进程的并发执行也带来了新的问题—死锁。所谓死锁,是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

下面通过一些实例来说明死锁现象。

先看生活中的一个实例。在一条河上有一座桥,桥面很窄,只能容纳一辆汽车通行。若有两辆汽车分别从桥的左右两端驶上该桥,则会出现下述冲突情况:此时,左边的汽车占有桥面左边的一段,要想过桥还需等待右边的汽车让出桥面右边的一段;右边的汽车占有桥面右边的一段,要想过桥还需等待左边的汽车让出桥面左边的一段。此时,若左右两边的汽车都只能向前行驶,则两辆汽车都无法过桥。

在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入设备,进程 $P_1$ 正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程 $P_2$ 所占用,而 $P_2$ 在未释放打印机之前,又提出请求使用正被 $P_1$ 占用的输入设备。这样,两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

死锁产生的原因
系统资源的竞争

通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。

进程推进顺序非法

进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 $P_1$ , $P_2$ 分别保持了资源 $R_1$ , $R_2$ ,而进程 $P_1$ 申请资源 $R_2$ 、进程 $P_2$ 申请资源 $R_1$ 时,两者都会因为所需资源被占用而阻塞。

信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

死锁产生的必要条件

产生死锁必须同时满足以下4个条件,只要其中任意一个条件不成立,死锁就不会发生。

互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

不剥夺条件:进程所获得的资源在未使用完之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

请求并保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

循环等待条件:存在一种进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待态的进程集合{ $P1$ , $P_2$ ,…, $P_n$ },其中 $P_i$ 等待的资源被 $P{i+1}$ ( $i$ =0,1,… , $n$ -1)占有, $P_n$ 等待的资源被 $P_0$ 占有,如图2.11所示。

直观上看,循环等待条件似乎和死锁的定义一样,其实不然。按死锁定义构成等待环所要求的条件更严,它要求 $Pi$ 等待的资源必须由 $P{i+1}$ 来满足,而循环等待条件则无此限制。例如,系统中有两台输出设备, $P_0$ 占有一台, $P_K$ 占有另一台,且K不属于集合{0,1,…, n}。 $P_n$ 等待一台输出设备,它可从 $P_0$ 获得,也可能从 $P_K$ 获得。因此,虽然 $P_n$ , $P_0$ 和其他一些进程形成了循环等待圈,但 $P_K$ 不在圈内,若 $P_K$ 释放了输出设备,则可打破循环等待,如图2.12所示。因此循环等待只是死锁的必要条件。

图2.11和图2.12找不到图片(Image not found)

资源分配图含圈而系统又不一定有死锁的原因是,同类资源数大于1。但若系统中每类资源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件。

要注意区分不剥夺条件与请求并保持条件。下面用一个简单的例子进行说明:若你手上拿着一个苹果(即便你不打算吃),别人不能把你手上的苹果拿走,则这就是不剥夺条件;若你左手拿着一个苹果,允许你右手再去拿一个苹果,则这就是请求并保持条件。

死锁的处理策略

为使系统不发生死锁,必须设法破坏产生死锁的4个必要条件之一,或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复。

死锁预防

设置某些限制条件,破坏产生死锁的4个必要条件中的一个或几个,以防止发生死锁。

避免死锁

在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁。

死锁的检测及解除

无须采取任何限制性措施,允许进程在运行过程中发生死锁。通过系统的检测机构及时地检测出死锁的发生,然后采取某种措施解除死锁。

预防死锁和避免死锁都属于事先预防策略,预防死锁的限制条件比较严格,实现起来较为简单,但往往导致系统的效率低,资源利用率低;避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂。

死锁的几种处理策略的比较见表2.4。

表2.4 死锁处理策略的比较找不到图片(Image not found)

死锁预防

防止死锁的发生只需破坏死锁产生的4个必要条件之一即可。

破坏互斥条件

若允许系统资源都能共享使用,则系统不会进入死锁状态。但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。

破坏不剥夺条件

当一个已保持了某些不可剥夺资源的进程请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺,或从而破坏了不剥夺条件。

该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如 CPU的寄存器及内存资源,一般不能用于打印机之类的资源。

破坏请求并保持条件

采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行,这些资源就一直归它所有,不再提出其他资源请求,这样就可以保证系统不会发生死锁。

这种方式实现简单,但缺点也显而易见,系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

破坏循环等待条件

为了破坏循环等待条件,可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源 $R_i$ ,则该进程在以后的资源申请中就只能申请编号大于 $R_i$ 的资源。

这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。

死锁避免

避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。这种方法所施加的限制条件较弱,可以获得较好的系统性能。

系统安全状态

避免死锁的方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配;否则让进程等待。

所谓安全状态,是指系统能按某种进程推进顺序( $P_1$ , $P_2$ ,…, $P_n$ )为每个进程 $P_i$ 分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。此时称 $P_i$ , $P_2$ .…, $P_n$ 为安全序列。若系统无法找到一个安全序列,则称系统处于不安全状态。

假设系统中有三个进程 $P_1$ , $P_2$ ,和 $P_3$ ,共有12台磁带机。进程 $P_1$ 共需要10台磁带机, $P_2$ 和 $P_3$ 分别需要4台和9台。假设在 $T_0$ 时刻,进程 $P_1$ , $P_2$ 和 $P_3$ 已分别获得5台、2台和2台,尚有3台未分配,见表2.5。

表2.5 资源分配找不到图片(Image not found)

在 $T_0$ 时刻是安全的,因为存在一个安全序列 $P_2$ , $P_1$ , $P_3$ ,只要系统按此进程序列分配资源,那么每个进程都能顺利完成。也就是说,当前可用磁带机为3台,先把3台磁带机分配给 $Р_2$ 以满足其最大需求, $P_2$ 结束并归还资源后,系统有5台磁带机可用;接下来给 $P_1$ 分配5台磁带机以满足其最大需求, $P_1$ 结束并归还资源后,剩余10台磁带机可用;最后分配7台磁带机给 $P_3$ ,这样$P_3$也能顺利完成。

若在 $T_0$ 时刻后,系统分配1台磁带机给 $P_3$ ,系统剩余可用资源数为2,此时系统进入不安全状态,因为此时已无法再找到一个安全序列。当系统进入不安全状态后,便可能导致死锁。例如,把剩下的2台磁带机分配给 $P_2$ 这样, $P_2$ 完成后只能释放4台磁带机,既不能满足 $P_1$ 又不能满足 $P_3$ ,致使它们都无法推进到完成,彼此都在等待对方释放资源,陷入僵局,即导致死锁。

并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态。

银行家算法

银行家算法是最著名的死锁避免算法,其思想是:把操作系统视为银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。操作系统按照银行家制定的规则为进程分配资源。进程运行之前先声明对各种资源的最大需求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源数与本次申请的资源数之和是否超过该进程声明的最大需求量。若超过则拒绝分配资源,若未超过则再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。

数据结构描述

可利用资源向量Available:含有m个元素的数组,其中每个元素代表一类可用的资源数目。Available[ j ]=K表示系统中现有 $R_j$ 类资源K个。

最大需求矩阵 Max: n×m 矩阵,定义系统中 $n$ 个进程中的每个进程对 $m$ 类资源的最大需求。简单来说,一行代表一个进程,一列代表一类资源。Max[ i,j ]=K表示进程i需要 $R_j$ 类资源的最大数目为K。

分配矩阵Allocation: $n \times m$ 矩阵,定义系统中每类资源当前已分配给每个进程的资源数。Allocation[ i,j ]=K表示进程 $i$ 当前已分得 $R_j$ 类资源的数目为K。初学者容易混淆Available向量和Allocation矩阵,在此特别提醒。

需求矩阵Need: $n \times m$ 矩阵,表示每个进程接下来最多还需要多少资源。Need[ i,j ]=K表示进程i还需要 $R_j$类资源的数目为K。

上述三个矩阵间存在下述关系:

Need= Max- Allocation

一般情况下,在银行家算法的题目中,Max矩阵和Allocation矩阵是已知条件,而求出 Need矩阵是解题的第一步。

银行家算法描述
银行家算法描述找不到图片(Image not found)
安全性算法举例
图片详情找不到图片(Image not found)
银行家算法举例
图片详情找不到图片(Image not found)

死锁检测和解除

前面介绍的死锁预防和避免算法,都是在为进程分配资源时施加限制条件或进行检测,若系统为进程分配资源时不采取任何措施,则应该提供死锁检测和解除的手段。

资源分配图

系统死锁可利用资源分配图来描述。如图2.13所示,用圆圈代表一个进程,用框代表一类资源。由于一种类型的资源可能有多个,因此用框中的一个圆代表一类资源中的一个资源。从进程到资源的有向边称为 $\color{green}{\text{请求边}}$ ,表示该进程申请一个单位的该类资源;从资源到进程的边称为 $\color{green}{\text{分配边}}$ ,表示该类资源已有一个资源分配给了该进程。

在图2.13所示的资源分配图中,进程 $P_1$ 已经分得了两个 $R_1$ 资源,并又请求一个 $R_2$ 资源;进程 $P_2$ ,分得了一个 $R_1$ 资源和一个 $R_2$ 资源,并又请求一个 $R_1$ 资源。

图2.13 资源分配示例找不到图片(Image not found)
死锁定理

简化资源分配图可检测系统状态S是否为死锁状态。简化方法如下:

1)在资源分配图中,找出既不阻塞又不孤点的进程 $P_i$ (即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中己有的空闲资源数量,如在图2.13中, $R_1$ 没有空闲资源, $R_2$ 有一个空闲资源。若所有连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之成为孤立的结点。在图2.14(a)中, $P_1$ 是满足这一条件的进程结点,将P的所有边消去,便得到图2.14(b)所示的情况。

这里要注意一个问题,判断某种资源是否有空间,应用它的资源数量减去它在资源分配图中的出度,例如在图2.13中, $R_1$ 的资源数为3,而出度也为3,所以 $R_1$ 没有空闲资源, $R_2$ 的资源数为2,出度为1,所以 $R_2$ 有一个空闲资源。

2)进程 $P_i$ 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。在图2.13中,进程 $P_2$ 就满足这样的条件。根据1)中的方法进行一系列简化后,若能消去图中所有的边,则称该图是可完全简化的,如图2.14(c)所示。

图2.14 资源分配图的化简找不到图片(Image not found)

S为死锁的条件是当且仅当S状态的资源分配图是不可完全简化的,该条件为 $\color{green}{\text{死锁定理}}$ 。

死锁解除

一旦检测出死锁,就应立即采取相应的措施来解除死锁。死锁解除的主要方法有:

1) $\color{green}{\text{资源剥夺法}}$ 。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源而处于资源匮乏的状态。

2) $\color{green}{\text{撤销进程法}}$ 。强制撤销部分甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。

3) $\color{green}{\text{进程回退法}}$ 。让一(或多〉个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而非被剥夺。要求系统保持进程的历史信息,设置还原点。

本节小结

本节开头提出的问题的参考答案如下。

1)为什么会产生死锁?产生死锁有什么条件?

由于系统中存在一些不可剥夺资源,当两个或两个以上的进程占有自身的资源并请求对方的资源时,会导致每个进程都无法向前推进,这就是死锁。死锁产生的必要条件有4个,分别是互斥条件、不剥夺条件、请求并保持条件和循环等待条件。

互斥条件是指进程要求分配的资源是排他性的,即最多只能同时供一个进程使用。

不剥夺条件是指进程在使用完资源之前,资源不能被强制夺走。

请求并保持条件是指进程占有自身本来拥有的资源并要求其他资源。

循环等待条件是指存在一种进程资源的循环等待链。

2)有什么办法可以解决死锁问题?

死锁的处理策略可以分为预防死锁、避免死锁及死锁的检测与解除。

死锁的预防是指通过设立一些限制条件,破坏死锁的一些必要条件,让死锁无法发生。

死锁的避免是指在动态分配资源的过程中,用一些算法防止系统进入不安全状态,从而避免死锁。

死锁的检测和解除是指在死锁产生前不采取任何措施,只检测当前系统有没有发生死锁,若有,则采取一些措施解除死锁。

本章疑难点

进程与程序的区别与联系

1)进程是程序及其数据在计算机上的一次运行活动,是一个动态的概念。进程的运行实体是程序,离开程序的进程没有存在的意义。从静态角度看,进程是由程序、数据和进程控制块(PCB)三部分组成的。而程序是一组有序的指令集合,是一种静态的概念。

2)进程是程序的一次执行过程,它是动态地创建和消亡的,具有一定的生命周期,是暂时存在的;而程序则是一组代码的集合,是永久存在的,可长期保存。

3)一个进程可以执行一个或几个程序,一个程序也可构成多个进程。进程可创建进程,而程序不可能形成新的程序。

4)进程与程序的组成不同。进程的组成包括程序、数据和PCB。

死锁与饥饿

具有等待队列的信号量的实现可能导致这样的情况:两个或多个进程无限地等待一个事件,而该事件只能由这些等待进程之一来产生。这里的事件是V操作的执行(即释放资源)。出现这样的状态时,这些进程称为死锁(Deadlocked)。

为加以说明,考虑一个由两个进程 $P_0$ 和 $P_1$ 组成的系统,每个进程都访问两个信号量S和Q,这两个信号量的初值均为1。

图片详情找不到图片(Image not found)

假设进程 $P_0$ 执行P(S),接着进程 $P_1$ 执行P(Q)。当进程 $P_0$ 执行P(Q)时,它必须等待,直到进程 $P_1$ 执行V(Q)。类似地,当进程 $P_1$ 执行P(S)时,它必须等待,直到进程 $P_0$ 执行V(S)。由于这两个V操作都不能执行,因此进程 $P_0$ 和进程 $P_1$ 就死锁了。

一组进程处于死锁状态是指组内的每个进程都在等待一个事件,而该事件只可能由组内的另一个进程产生。这里所关心的主要是事件是资源的获取和释放。

与死锁相关的另一个问题是无限期阻塞(Indefinite Blocking)或饥饿(Starvation),即进程在信号量内无穷等待的情况。

产生饥饿的主要原因是:在一个动态系统中,对于每类系统资源,操作系统需要确定一个分配策略,当多个进程同时申请某类资源时,由分配策略确定资源分配给进程的次序。有时资源分配策略可能是不公平的,即不能保证等待时间上界的存在。在这种情况下,即使系统没有发生死锁,某些进程也可能会长时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程“饥饿”,当“饥饿”到一定程度的进程所赋予的任务即使完成也不再具有实际意义时,称该进程被“饿死”。

例如,当有多个进程需要打印文件时,若系统分配打印机的策略是最短文件优先,则长文件的打印任务将由于短文件的源源不断到来而被无限期推迟,导致最终“饥饿”甚至“饿死”。

“饥饿”并不表示系统一定会死锁,但至少有一个进程的执行被无限期推迟。“饥饿”与死锁的主要差别如下:

1)进入“饥饿”状态的进程可以只有一个,而因循环等待条件而进入死锁状态的进程却必须大于等于两个。

2)处于“饥饿”状态的进程可以是一个就绪进程,如静态优先权调度算法时的低优先权进程,而处于死锁状态的进程则必定是阻塞进程。

银行家算法的工作原理

银行家算法的主要思想是避免系统进入不安全状态。在每次进行资源分配时,它首先检查系统是否有足够的资源满足要求,若有则先进行分配,并对分配后的新状态进行安全性检查。若新状态安全,则正式分配上述资源,否则拒绝分配上述资源。这样,它保证系统始终处于安全状态,从而避免了死锁现象的发生。

进程同步、互斥的区别和联系

并发进程的执行会产生相互制约的关系:一种是进程之间竞争使用临界资源,只能让它们逐个使用,这种现象称为互斥,是一种竞争关系;另一种是进程之间协同完成任务,在关键点上等待另一个进程发来的消息,以便协同一致,是一种协作关系。

作业和进程的关系

进程是系统资源的使用者,系统的资源大部分都是以进程为单位分配的。而用户使用计算机是为了实现一串相关的任务,通常把用户要求计算机完成的这一串任务称为作业。

批处理系统中作业与进程的关系(进程组织)

批处理系统可以通过磁记录设备或卡片机向系统提交批作业,由系统的SPOOLing输入进程将作业放入磁盘的输入井,作为后备作业。作业调度程序(一般也作为独立的进程运行)每当选择一道后备作业运行时,首先为该作业创建一个进程(称为该作业的根进程)。该进程将执行作业控制语言解释程序,解释该作业的作业说明书。父进程在运行过程中可以动态地创建一个或多个子进程,执行说明书中的语句。例如,对一条编译的语句,该进程可以创建一个子进程执行编译程序对用户源程序进行编译。类似地,子进程也可继续创建子进程去完成指定的功能。因此,一个作业就动态地转换成了一组运行实体—进程族。当父进程遇到作业说明书中的“撤出作业”语句时,将该作业从运行态改变为完成态,将作业及相关结果送入磁盘上的输出井。作业终止进程负责将输出井中的作业利用打印机输出,回收作业所占用的资源,删除作业有关的数据结构,删除作业在磁盘输出井中的信息等。作业终止进程撤除一道作业后,可向作业调度进程请求进行新的作业调度。至此,一道进入系统运行的作业全部结束。

分时系统中作业与进程的关系

在分时系统中,作业的提交方法、组织形式均与批处理作业有很大差异。分时系统的用户通过命令语言逐条与系统应合八把武大系体自动时,系统为每个终端设备建立一个进程(称为终端统内部对应一个(以右T经程序,命令解释程序从终端设备读入俞令,解藉疯令是一茶后台命进程),该进程执仃类令n可以创建一个子进程去具体执行。若当HPN根据需要创建子孙进程。条命令。对于每条终端命令,可以创建一个子进程去具体执行。若当前的终端命令是一条后台命令,则可以和下一条终端命令并行处理。各子进程在运行过程中完全可以根据需要创建子孙进程。终端命令所对应的进程结束后,命令的功能也相应处理完毕。用户本次上机完毕,用户通过一条登出命令即结束上机过程。

分时系统的作业就是用户的一次上机交互过程,可以认为终端进程的创建是一个交互作业的开始,登出命令运行结束代表用户交互作业的终止。

命令解释程序流程扮演着批处理系统中作业控制语言解释程序的角色,只不过命令解释程序是从用户终端接收命令。

交互地提交批作业

在同时支持交互和批处理的操作系统中,人们可以用交互的方式准备好批作业的有关程序、数据及作业控制说明书。比如,可用交互式系统提供的全屏幕编辑命令编辑好自编的一个天气预报程序,用编译及装配命令将程序变成可执行文件,用调试命令进行程序调试。调试成功后,用户每天都要做如下工作:准备原始天气数据,运行天气预报执行文件处理原始数据,把结果打印出来等。这时,用交互系统提供的全屏幕编辑命令编辑好将要提交的作业控制说明书文件,如Windows系统的 bat 文件和 Linux系统的sh文件。然后用一条作业提交命令将作业提交到系统作业队列中。系统有专门的作业调度进程负责从作业队列中选择作业,为被选取的作业创建一个父进程运行命令解释程序,解释执行作业控制说明书文件中的命令。

各种调度算法的特点
各种调度算法的特点找不到图片(Image not found)

内存管理

【考纲内容】

(一)内存管理基础

  1. 内存管理概念(逻辑地址与物理地址空间,地址变换, $\color{red}{\text{内存共享}}$ ,内存保护,内存分配与回收)
  2. 连续分配管理方式
  3. 非连续分配管理方式:
  4. 分页管理方式
  5. 分段管理方式
  6. 段页式管理方式

(二)虚拟内存管理

  1. 虚拟内存的基本概念
  2. 请求分页管理方式
  3. 页框分配
  4. 页面置换算法
  5. $\color{red}{\text{内存映射文件}}$ (Memory-Mapped Files)
  6. 虚拟存储器性能的影响因素及改进方式

【知识框架】

  • 程序执行过程
    • 编译、连接、装入
    • 逻辑地址和物理地址
  • 扩充内存————覆盖与变换
  • 连续分配
    • 单一连续分配
    • 固定分区分配————内部碎片
    • 动态分区分配
      • 外部碎片
      • 分配算法:首次、最佳、最坏、邻近适应
  • 非连续分配
    • 页式存储管理
      • 概念:页面、地址结构、页表
      • 地址变化机构及变换过程
      • 快表
    • 段式存储管理————段表、地址变换机构、段的共享与保护
    • 段式存储管理————段表、页表
  • 虚拟内存
    • 概念
      • 局部性原理
      • 特征:多次性、对换性、虚拟性
    • 请求分页
      • 组成:页表机构、缺页中断机构、地址变换机构
      • 页面置换算法
        • 最佳置换(OPT)
        • 先进先出(FIFO)————Belady异常
        • 最近最久未使用(LRU)
        • 时钟(CLOCK)算法
      • 页面分配策略
      • 抖动、工作集

【复习提示】

内存管理和进程管理是操作系统的核心内容,需要重点复习。本章围绕分页机制展开:通过分页管理方式在物理内存大小的基础上提高内存的利用率,再进一步引入请求分页管理方式,实现虚拟内存,使内存脱离物理大小的限制,从而提高处理器的利用率。

  • 需要注意:编译,链接,装入,执行,各个阶段的任务

内存管理概念

在学习本节时,请读者思考以下问题:

1)为什么要进行内存管理?

2)页式管理中每个页表项大小的下限如何决定?

3)多级页表解决了什么问题?又会带来什么问题?

在学习经典的管理方法前,同样希望读者先思考,自己给出一些内存管理的想法,并在学习过程中和经典方案进行比较。注意本节给出的内存管理是循序渐进的,后一种方法通常会解决前一种方法的不足。希望读者多多思考,比较每种方法的异同,着重掌握页式管理。

内存管理的基本原理和要求

内存管理(Memory Management)是操作系统设计中最重要和最复杂的内容之一。虽然计算机硬件技术一直在飞速发展,内存容量也在不断增大,但仍然不可能将所有用户进程和系统所需要的全部程序与数据放入主存,因此操作系统必须对内存空间进行合理的划分和有效的动态分配。操作系统对内存的划分和动态分配,就是内存管理的概念。

有效的内存管理在多道程序设计中非常重要,它不仅可以方便用户使用存储器、提高内存利用率,还可以通过虚拟技术从逻辑上扩充存储器。

内存管理的功能有:

  • $\color{green}{\text{内存空间的分配与回收}}$ 。由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高编程效率。
  • $\color{green}{\text{地址转换}}$ 。在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
  • $\color{green}{\text{内存空间的扩充}}$ 。利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
  • $\color{green}{\text{存储保护}}$ 。保证各道作业在各自的存储空间内运行,互不干扰。

在进行具体的内存管理之前,需要了解进程运行的基本原理和要求。

程序装入和链接

创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:

  • $\color{green}{\text{编译}}$ 。由编译程序将用户源代码编译成若干目标模块。
  • $\color{green}{\text{链接}}$ 。由链接程序将编译后形成的一组目标模块及所需的库函数链接在一起,形成一个完整的装入模块。
  • $\color{green}{\text{装入}}$ 。由装入程序将装入模块装入内存运行。

这三步过程如图3.1所示。

图3.1将用户程序变为可在内存中执行的程序的步骤找不到图片(Image not found)

程序的链接有以下三种方式。

  • $\color{green}{\text{静态链接}}$ 。在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行程序,以后不再拆开。
  • $\color{green}{\text{装入时动态链接}}$ 。将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的方式。
  • $\color{green}{\text{运行时动态链接}}$ 。对某些目标模块的链接,是在程序执行中需要该目标模块时才进行的。其优点是便于修改和更新,便于实现对目标模块的共享。

内存的装入模块在装入内存时,同样有以下三种方式:

1) $\color{green}{\text{绝对装入}}$ 。在编译时,若知道程序将驻留在内存的某个位置,则编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。由于程序中的逻辑地址与实际内存地址完全相同,因此不需对程序和数据的地址进行修改。绝对装入方式只适用于单道程序环境。另外,程序中所用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。而通常情况下在程序中采用的是符号地址,编译或汇编时再转换为绝对地址。

2) $\color{green}{\text{可重定位装入}}$ 。在多道程序环境下,多个目标模块的起始地址(简称 $\color{green}{\text{始址}}$ )通常都从0开始,程序中的其他地址都是相对于始址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。装入时对目标程序中指令和数据的修改﹒过程称为 $\color{green}{\text{重定位}}$ ,地址变换通常是在装入时一次完成的,所以又称静态重定位,如图3.2(a)所示。

静态重定位的特点是,一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则不能装入该作业。此外,作业一旦进入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间。

3) $\color{green}{\text{动态运行时装入}}$ ,也称动态重定位。程序在内存中若发生移动,则需要采用动态的装入方式。装入程序把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址均为相对地址。这种方式需要一个重定位寄存器的支持,如图3.2(b)所示。

动态重定位的特点如下:可以将程序分配到不连续的存储区中;在程序运行之前可以只装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。

图3.2 重定位类型找不到图片(Image not found)
逻辑地址空间与物理地址空间

编译后,每个目标模块都从0号单元开始编址,这称为该目标模块的相对地址(或逻辑地址)。当链接程序将各个模块链接成一个完整的可执行目标程序时,链接程序顺序依次按各个模块的相对地址构成统一的从0号单元开始编址的逻辑地址空间。用户程序和程序员只需知道逻辑地址,而内存管理的具体机制则是完全透明的,只有系统编程人员才会涉及内存管理的具体机制。不同进程可以有相同的逻辑地址,因为这些相同的逻辑地址可以映射到主存的不同位置。

物理地址空间是指内存中物理单元的集合,它是地址转换的最终地址,进程在运行时执行指令和访问数据,最后都要通过物理地址从主存中存取。当装入程序将可执行代码装入内存时,必须通过地址转换将逻辑地址转换成物理地址,这个过程称为 $\color{green}{\text{地址重定位}}$ 。

内存保护

内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。内存保护可采取两种方法:

1)在CPU 中设置一对上、下限寄存器,存放用户作业在主存中的下限和上限地址,每当CPU要访问一个地址时,分别和两个寄存器的值相比,判断有无越界。

2)采用 $\color{green}{\text{重定位寄存器}}$ (或基址寄存器)和 $\color{green}{\text{界地址寄存器}}$ (又称限长寄存器)来实现这种保护。重定位寄存器含最小的物理地址值,界地址寄存器含逻辑地址的最大值。每个逻辑地址值必须小于界地址寄存器;内存管理机构动态地将逻辑地址与界地址寄存器进行比较,若未发生地址越界,则加上重定位寄存器的值后映射成物理地址,再送交内存单元,如图3.3所示。

图3.3 重定位和界地址寄存器的硬件支持找不到图片(Image not found)

实现内存保护需要重定位寄存器和界地址寄存器,因此要注意两者的区别。重定位寄存器是用来“加”的,逻辑地址加上重定位寄存器中的值就能得到物理地址;界地址寄存器是用来“比”的,通过比较界地址寄存器中的值与逻辑地址的值来判断是否越界。

*覆盖与交换

覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法。

覆盖

早期的计算机系统中,主存容量很小,虽然主存中仅存放一道用户程序,但存储空间放不下用户进程的现象也经常发生,这一矛盾可以用覆盖技术来解决。

覆盖的基本思想如下:由于程序运行时并非任何时候都要访问程序及数据的各个部分(尤其是大程序),因此可把用户空间分成一个固定区和若干覆盖区。将经常活跃的部分放在固定区,其余部分按调用关系分段。首先将那些即将要访问的段放入覆盖区,其他段放在外存中,在需要调用前,系统再将其调入覆盖区,替换覆盖区中原有的段。

覆盖技术的特点是,打破了必须将一个进程的全部信息装入主存后才能运行的限制,但当同时运行程序的代码量大于主存时仍不能运行,此外,内存中能够更新的地方只有覆盖区的段,不在覆盖区中的段会常驻内存。

交换

交换(对换)的基本思想是,把处于等待状态(或在CPU 调度原则下被剥夺运行权利)的程序从内存移到辅存,把内存空间腾出来,这一过程又称换出;把准备好竞争CPU运行的程序从辅存移到内存,这一过程又称换入。第2章介绍的中级调度采用的就是交换技术。

例如,有一个CPU采用时间片轮转调度算法的多道程序环境。时间片到,内存管理器将刚刚执行过的进程换出,将另一进程换入刚刚释放的内存空间。同时,CPU调度器可以将时间片分配给其他已在内存中的进程。每个进程用完时间片都与另一进程交换。在理想情况下,内存管理器的交换过程速度足够快,总有进程在内存中可以执行。

有关交换,需要注意以下几个问题:

  • 交换需要备份存储,通常是快速磁盘。它必须足够大,并提供对这些内存映像的直接访问。
  • 为了有效使用CPU,需要使每个进程的执行时间比交换时间长,而影响交换时间的主要是转移时间。转移时间与所交换的内存空间成正比。
  • 若换出进程,则必须确保该进程完全处于空闲状态。
  • 交换空间通常作为磁盘的一整块,且独立于文件系统,因此使用起来可能很快。
  • 交换通常在有许多进程运行且内存空间吃紧时开始启动,而在系统负荷降低时就暂停。
  • 普通的交换使用不多,但交换策略的某些变体在许多系统(如UNIX系统)中仍发挥作用。

交换技术主要在不同进程(或作业)之间进行,而覆盖则用于同一个程序或进程中。由于覆盖技术要求给出程序段之间的覆盖结构,使得其对用户和程序员不透明,所以对于主存无法存放用户程序的矛盾,现代操作系统是通过虚拟内存技术来解决的,覆盖技术则已成为历史;而交换技术在现代操作系统中仍具有较强的生命力。

连续分配管理方式

连续分配方式是指为一个用户程序分配一个连续的内存空间,譬如某用户需要1GB的内存空间,连续分配方式就在内存空间中为用户分配一块连续的1GB空间。连续分配方式主要包括 $\color{green}{\text{单一连续分配}}$ 、 $\color{green}{\text{固定分区分配}}$ 和 $\color{green}{\text{动态分区分配}}$ 。

单一连续分配

内存在此方式下分为 $\color{green}{\text{系统区}}$ 和 $\color{green}{\text{用户区}}$ ,系统区仅供操作系统使用,通常在低地址部分;用户区是为用户提供的、除系统区之外的内存空间。这种方式无须进行内存保护。因为内存中永远只有一道程序,因此肯定不会因为访问越界而干扰其他程序。

这种方式的优点是简单、无外部碎片,可以采用覆盖技术,不需要额外的技术支持。缺点是只能用于单用户、单任务的操作系统中,有内部碎片,存储器的利用率极低。

固定分区分配

固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干固定大小的区域,每个分区只装入一道作业。当有空闲分区时,便可再从外存的后备作业队列中选择适当大小的作业装入该分区,如此循环。

固定分区分配在划分分区时有两种不同的方法,如图3.4所示。

图3.4 固定分区分配的两种方法找不到图片(Image not found)

为便于内存分配,通常将分区按大小排队,并为之建立一张分区说明表,其中各表项包括每个分区的始址、大小及状态(是否已分配),如图3.5(a)所示。当有用户程序要装入时,便检索该表,以找到合适的分区给予分配并将其状态置为“已分配”,未找到合适分区时,则拒绝为该用户程序分配内存。存储空间的分配情况如图3.5(b)所示。

图3.5 固定分区说明表和内存分配情况找不到图片(Image not found)

这种分区方式存在两个问题:一是程序可能太大而放不进任何一个分区中,这时用户不得不使用覆盖技术来使用内存空间;二是主存利用率低,当程序小于固定分区大小时,也占用一个完整的内存分区空间,这样分区内部就存在空间浪费,这种现象称为内部碎片。

固定分区是可用于多道程序设计的最简单的存储分配,无外部碎片,但不能实现多进程共享一个主存区,所以存储空间利用率低。固定分区分配很少用于现在通用的操作系统中,但在某些用于控制多个相同对象的控制系统中仍发挥着一定的作用。

动态分区分配

动态分区分配又称可变分区分配,是一种动态划分内存的分区方法。这种分区方法不预先划分内存,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此,系统中分区的大小和数目是可变的。

如图3.6所示,系统有64MB内存空间,其中低8MB固定分配给操作系统,其余为用户可用内存。开始时装入前三个进程,它们分别分配到所需的空间后,内存只剩下4MB,进程4无法装入。在某个时刻,内存中没有一个就绪进程,CPU出现空闲,操作系统就换出进程2,换入进程4。由于进程4比进程2小,这样在主存中就产生了一个6MB 的内存块。之后CPU又出现空闲,而主存无法容纳进程2,操作系统就换出进程1,换入进程2。

图3.6动态分区找不到图片(Image not found)

动态分区在开始分配时是很好的,但之后会导致内存中出现许多小的内存块。随着时间的推移,内存中会产生越来越多的碎片((图3.6中最后的4MB和中间的6MB,且随着进程的换入/换出,很可能会出现更多、更小的内存块),内存的利用率随之下降。这些小的内存块称为外部碎片,指在所有分区外的存储空间会变成越来越多的碎片,这与固定分区中的内部碎片正好相对。克服外部碎片可以通过紧凑(Compaction)技术来解决,即操作系统不时地对进程进行移动和整理。但这需要动态重定位寄存器的支持,且相对费时。紧凑的过程实际上类似于Windows系统中的磁盘整理程序,只不过后者是对外存空间的紧凑。

在进程装入或换入主存时,若内存中有多个足够大的空闲块,则操作系统必须确定分配哪个内存块给进程使用,这就是动态分区的分配策略。考虑以下几种算法:

1) $\color{green}{\text{首次适应}}$ (First Fit)算法。空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。

2) $\color{green}{\text{最佳适应}}$ (Best Fit)算法。空闲分区按容量递增的方式形成分区链,找到第一个能满足要求的空闲分区。

3) $\color{green}{\text{最坏适应}}$ (Worst Fit)算法。又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区,即挑选出最大的分区。

4) $\color{green}{\text{邻近适应}}$ (Next Fit)算法。又称循环首次适应算法,由首次适应算法演变而成。不同之处是,分配内存时从上次查找结束的位置开始继续查找。

在这几种方法中,首次适应算法不仅是最简单的,而且通常也是最好和最快的。在UNIX系统的最初版本中,就是使用首次适应算法为进程分配内存空间的,它使用数组的数据结构(而非链表)来实现。不过,首次适应算法会使得内存的低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此增加了查找的开销。

邻近适应算法试图解决这个问题。但实际上,它常常导致在内存的末尾分配空间(因为在一遍扫描中,内存前面部分使用后再释放时,不会参与分配)分裂成小碎片。它通常比首次适应算法的结果要差。

最佳适应算法虽然称为“最佳”,但是性能通常很差,因为每次最佳的分配会留下很小的难以利用的内存块,会产生最多的外部碎片。

最坏适应算法与最佳适应算法相反,它选择最大的可用块,这看起来最不容易产生碎片,但是却把最大的连续内存划分开,会很快导致没有可用的大内存块,因此性能也非常差。

Knuth 和 Shore分别就前三种方法对内存空间的利用情况做了模拟实验,结果表明:首次适应算法可能比最佳适应法效果好,而它们两者一定比最大适应法效果好。另外要注意,在算法实现时,分配操作中最佳适应法和最大适应法需要对可用块进行排序或遍历查找,而首次适应法和邻近适应法只需要简单查找;在回收操作中,当回收的块与原来的空闲块相邻时(有三种相邻的情况,比较复杂),需要将这些块合并。在算法实现时,使用数组或链表进行管理。除了内存的利用率,这里的算法开销也是操作系统设计需要考虑的一个因素。

三种内存分区管理方式的比较见表3.1。

表3.1 三种内存分区管理方式的比较找不到图片(Image not found)

以上三种内存分区管理方法有一个共同特点,即用户进程(或作业)在主存中都是连续存放的。这里对它们进行比较和总结。

非连续分配管理方式

非连续分配允许一个程序分散地装入不相邻的内存分区。在连续分配管理方式中,我们发现,即使内存有超过1GB的空闲空间,但若没有连续的1GB空间,则需要1GB空间的作业仍然是无法运行的;但若采用非连续分配管理方式,则作业所要求的1GB内存空间可以分散地分配在内存的各个区域,当然,这也需要额外的空间去存储它们(分散区域)的索引,使得非连续分配方式的存储密度低于连续存储方式的。

非连续分配管理方式根据分区的大小是否固定,分为 $\color{green}{\text{分页存储管理方式}}$ 和 $\color{green}{\text{分段存储管理方式}}$ 。

在分页存储管理方式中,又根据运行作业时是否要把作业的所有页面都装入内存才能运行,分为 $\color{green}{\text{基本分页存储管理方式}}$ 和 $\color{green}{\text{请求分页存储管理方式}}$ 。下面介绍基本分页存储管理方式。

基本分页存储管理方式

固定分区会产生内部碎片,动态分区会产生外部碎片,这两种技术对内存的利用率都比较低。我们希望内存的使用能尽量避免碎片的产生,这就引入了分页的思想:把主存空间划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,以块为单位逐个申请主存中的块空间。

分页的方法从形式上看,像分区相等的固定分区技术,分页管理不会产生外部碎片。但它又有本质的不同点:块的大小相对分区要小很多,而且进程也按照块进行划分,进程运行时按块申请主存可用空间并执行。这样,进程只会在为最后一个不完整的块申请一个主存块空间时,才产生主存碎片,所以尽管会产生内部碎片,但这种碎片相对于进程来说也是很小的,每个进程平均只产生半个块大小的内部碎片(也称页内碎片)。

分页存储的几个基本概念

① $\color{green}{\text{页面和页面大小}}$ 。进程中的块称为 $\color{green}{\text{页}}$ (Page),内存中的块称为 $\color{green}{\text{页框}}$ (Page Frame,或页帧)。外存也以同样的单位进行划分,直接称为 $\color{green}{\text{块}}$ (Block)。进程在执行时需要申请主存空间,即要为每个页面分配主存中的可用页框,这就产生了页和页框的一一对应。为方便地址转换,页面大小应是2的整数幂。同时页面大小应该适中,页面太小会使进程的页面数过多,这样页表就会过长,占用大量内存,而且也会增加硬件地址转换的开销,降低页面换入/换出的效率;页面过大又会使页内碎片增多,降低内存的利用率。所以页面的大小应该适中,要在空间效率和时间效率之间权衡。

② $\color{green}{\text{地址结构}}$ 。分页存储管理的逻辑地址结构如图3.7所示。

图3.7 分页存储管理的逻辑地址结构找不到图片(Image not found)

地址结构包含两部分:前一部分为页号P,后一部分为页内偏移量W。地址长度为32位,其中 0~11位为页内地址,即每页大小为4KB;12~31位为页号,地址空间最多允许 $2^{20}$ 页。

注意,地址结构决定了虚拟内存的寻址空间有多大。在实际问题中,页号、页内偏移、逻辑地址大多都是用十进制数给出的。题目用二进制地址的形式给出时,读者要会转换。

③页表。为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立张页表,它记录页面在内存中对应的物理块号,页表一般存放在内存中。页表是由页表项组成的,初学者容易混淆页表项与地址结构,页表项与地址都由两部构成,而且第一部分都是页号,但页表项的第二部分是物理内存中的块号,而地址的;二部分是页内偏移;页表项的第二部分与地址的第二部分共同组成物理地址。
在配置页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号。可见页表的作用是实现从页号到物理块号的地址映射,如图3.8所示。

图3.8 页表的作用找不到图片(Image not found)
基本地址变换机构

地址变换机构的任务是将逻辑地址转换为内存中的物理地址。地址变换是借助于页表实现的。图3.9给出了分页存储管理系统中的地址变换机构。

图3.9 分页存储管理系统中的地址变换机构找不到图片(Image not found)

在系统中通常设置一个页表寄存器(PTR),存放页表在内存的起始地址F和页表长度M。进程未执行时,页表的始址和长度存放在进程控制块中,当进程执行时,才将页表始址和长度存入页表寄存器。设页面大小为L,逻辑地址A到物理地址E的变换过程如下(逻辑地址、页号、每页的长度都是十进制数):

①计算页号P(P=AIL)和页内偏移量W(W=A%L)。

${\textstyle\unicode{x2461}}$ 比较页号Р和页表长度M,若P≥M,则产生越界中断,否则继续执行。

${\textstyle\unicode{x2462}}$ 页表中页号Р对应的页表项地址=页表始址F+页号Px页表项长度,取出该页表项内容b,即为物理块号。要注意区分页表长度和页表项长度。页表长度的值是指一共有多少页,页表项长度是指页地址占多大的存储空间。

④计算E=b×L+W,用得到的物理地址E去访问内存。

以上整个地址变换过程均是由硬件自动完成的。例如,若页面大小L为1KB,页号2对应的物理块为b=8,计算逻辑地址A=2500的物理地址E的过程如下:P=2500/1K=2,W = 2500%1K=452,查找得到页号2对应的物理块的块号为8,E=8×1024+452=8644。

要再次提醒读者的是,题目中条件用十进制数给出和用二进制数给出的处理过程会稍有不同。同时读者会发现,页式管理只需给出一个整数就能确定对应的物理地址,因为页面大小L是固定的。因此,页式管理中地址空间是 $\color{green}{\text{一维的}}$ 。

页表项的大小不是随意规定的,而是有所约束的。如何确定页表项的大小?

页表项的作用是找到该页在内存中的位置。以32位逻辑地址空间、字节编址单位、一页4KB为例,地址空间内一共有 $2^{32}$ B/4KB= 1M页,因此需要 $log_21M$ = 20位才能保证表示范围能容纳所有页面,又因为以字节作为编址单位,即页表项的大小≥ $\lceil 20/81 \rceil$ =3B。所以在这个条件下,为了保证页表项能够指向所有页面,页表项的大小应该大于3B,当然,也可选择更大的页表项让一个页面能够正好容下整数个页表项,进而方便存储(如取成4B,一页正好可以装下1K个页表项),或增加一些其他信息。

下面讨论分页管理方式存在的两个主要问题:①每次访存操作都需要进行逻辑地址到物理地址的转换,地址转换过程必须足够快,否则访存速度会降低;②每个进程引入页表,用于存储映射机制,页表不能太大,否则内存利用率会降低。

具有快表的地址变换机构

由上面介绍的地址变换过程可知,若页表全部放在内存中,则存取一个数据或一条指令至少要访问两次内存:第一次是访问页表,确定所存取的数据或指令的物理地址;第二次是根据该地址存取数据或指令。显然,这种方法比通常执行指令的速度慢了一半。

为此,在地址变换机构中增设一个具有并行查找能力的高速缓冲存储器—快表,又称相联存储器(TLB),用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,主存中的页表常称为慢表。具有快表的地址变换机构如图3.10所示。

图3.10 具有快表的地址变换机构找不到图片(Image not found)

在具有快表的分页机制中,地址的变换过程如下:

${\textstyle\unicode{x2460}}$ CPU给出逻辑地址后,由硬件进行地址转换,将页号送入高速缓存寄存器,并将此页号
与快表中的所有页号进行比较。

②若找到匹配的页号,说明所要访问的页表项在快表中,则直接从中取出该页对应的页框
号,与页内偏移量拼接形成物理地址。这样,存取数据仅一次访存便可实现。

${\textstyle\unicode{x2462}}$ 若未找到匹配的页号,则需要访问主存中的页表,在读出页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换。

注意:有些处理机设计为快表和慢表同时查找,若在快表中查找成功则终止慢表的查找。

一般快表的命中率可达90%以上,这样分页带来的速度损失就可降低至10%以下。快表的有效性基于著名的局部性原理,后面讲解虚拟内存时将会具体讨论它。

两级页表

由于引入了分页管理,进程在执行时不需要将所有页调入内存页框,而只需将保存有映射关系的页表调入内存。但是,我们仍然需要考虑页表的大小。以32位逻辑地址空间、页面大小4KB、页表项大小4B为例,若要实现进程对全部逻辑地址空间的映射,则每个进程需要 $2^{20}$ 即约100万个页表项。也就是说,每个进程仅页表这一项就需要4MB 主存空间,这显然是不切实际的。即便不考虑对全部逻辑地址空间进行映射的情况,一个逻辑地址空间稍大的进程,其页表大小也可能是过大的。以一个40MB的进程为例,页表项共40KB (40MB/4KBx4B),若将所有页表项内容保存在内存中,则需要10个内存页框来保存整个页表。整个进程大小约为1万个页面,而实际执行时只需要几十个页面进入内存页框就可运行,但若要求10个页面大小的页表必须全部进入内存,则相对实际执行时的几十个进程页面的大小来说,肯定降低了内存利用率;从另一方面来说,这10页的页表项也并不需要同时保存在内存中,因为在大多数情况下,映射所需要的页表项都在页表的同一个页面中。

为了压缩页表,我们进一步延伸页表映射的思想,就可得到二级分页,即使用层次结构的页表:将页表的10页空间也进行地址映射,建立上一级页表,用于存储页表的映射关系。这里对页表的10个页面进行映射只需要10个页表项,所以上一级页表只需要1页就已足够(可以存储210=1024个页表项)。在进程执行时,只需要将这一页的上一级页表调入内存即可,进程的页表和进程本身的页面可在后面的执行中再调入内存。根据上面提到的条件(32位逻辑地址空间、页面大小4KB、页表项大小4B,以字节为编址单位),我们来构造一个适合的页表结构。页面大小为4KB,页内偏移地址为 $log_2 4K$ =12位,页号部分为20位,若不采用分级页表,则仅页表就要占用 $2^{20}$ ×4B/4KB = 1024页,这大大超过了许多进程自身需要的页面,对于内存来说是非常浪费资源的,而且查询页表工作也会变得十分不便、试想若把这些页表放在连续的空间内,查询对应页的物理页号时可以通过页表首页地址+页号×4B的形式得到,而这种方法查询起来虽然相对方便,但连续的1024页对于内存的要求实在太高,并且上面也说到了其中大多数页面都是不会用到的,所以这种方法并不具有可行性。若不把这些页表放在连续的空间里,则需要一张索引表来告诉我们第几张页表该上哪里去找,这能解决页表的查询问题,且不用把所有的页表都调入内存,只在需要它时才调入(下节介绍的虚拟存储器思想),因此能解决占用内存空间过大的问题。读者也许发现这个方案就和当初引进页表机制的方式一模一样,实际上就是构造一个页表的页表,也就是二级页表。为查询方便,顶级页表最多只能有1个页面(一定要记住这个规定),因此顶级页表总共可以容纳4KB/4B= 1K个页表项,它占用的地址位数为 $log_2 lK$ = 10位,而之前已经计算出页内偏移地址占用了12位,因此一个32位的逻辑地址空间就剩下了10位,正好使得二级页表的大小在一页之内,这样就得到了逻辑地址空间的格式,如图3.11所示。

图3.11 逻辑地址空间的格式找不到图片(Image not found)

二级页表实际上是在原有页表结构上再加上一层页表,示意结构如图3.12所示。

图3.12 二级页表结构示意图找不到图片(Image not found)

建立多级页表的目的在于建立索引,以便不用浪费主存空间去存储无用的页表项,也不用盲目地顺序式查找页表项。

基本分段存储管理方式

分页管理方式是从计算机的角度考虑设计的,目的是提高内存的利用率,提升计算机的性能。分页通过硬件机制实现,对用户完全透明。分段管理方式的提出则考虑了用户和程序员,以满足方便编程、信息保护和共享、动态增长及动态链接等多方面的需要。

1)分段。段式管理方式按照用户进程中的自然段划分逻辑空间。例如,用户进程由主程序、两个子程序、栈和一段数据组成,于是可以把这个用户进程划分为5段,每段从0开始编址,并分配一段连续的地址空间(段内要求连续,段间不要求连续,因此整个作业的地址空间是二维的),其逻辑地址由段号S与段内偏移量w两部分组成。

在图3.13中,段号为16位,段内偏移量为16位,因此一个作业最多有 $2^{16}$ =65536段,最大段长为64KB。

图3.13 分段系统中的逻辑地址结构找不到图片(Image not found)

在页式系统中,逻辑地址的页号和页内偏移量对用户是透明的,但在段式系统中,段号和段内偏移量必须由用户显式提供,在高级程序设计语言中,这个工作由编译程序完成。

2)段表。每个进程都有一张逻辑空间与内存空间映射的段表,其中每个段表项对应进程的一段,段表项记录该段在内存中的始址和长度。段表的内容如图3.14所示。

图3.14 段表的内容找不到图片(Image not found)

配置段表后,执行中的进程可通过查找段表,找到每段所对应的内存区。可见,段表用于实现从逻辑段到物理内存区的映射,如图3.15所示。

3)地址变换机构。分段系统的地址变换过程如图3.16所示。为了实现进程从逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表始址F和段表长度M。从逻辑地址A到物理地址E之间的地址变换过程如下:

图3.15 利用段表实现物理内存区映射找不到图片(Image not found)
图3.16 分段系统的地址变换过程找不到图片(Image not found)

①从逻辑地址A中取出前几位为段号S,后几位为段内偏移量W,注意在段式存储管理的题目中,逻辑地址一般以二进制数给出,而在页式存储管理中,逻辑地址一般以十进制数给出,读者要具体问题具体分析。

②比较段号S和段表长度M,若S≥M,则产生越界中断,否则继续执行。

③段表中段号S对应的段表项地址=段表始址F+段号S×段表项长度,取出该段表项的前几位得到段长C。若段内偏移量≥C,则产生越界中断,否则继续执行。从这句话我们可以看出,段表项实际上只有两部分,前几位是段长,后几位是始址。

${\textstyle\unicode{x2463}}$ 取出段表项中该段的始址b,计算E=b+W,用得到的物理地址E去访问内存。

4)段的共享与保护。在分段系统中,段的共享是通过两个作业的段表中相应表项指向被共享的段的同一个物理副本来实现的。当一个作业正从共享段中读取数据时,必须防止另一个作业修改此共享段中的数据。不能修改的代码称为纯代码或可重入代码(它不属于临界资源),这样的代码和不能修改的数据可以共享,而可修改的代码和数据不能共享。

与分页管理类似,分段管理的保护方法主要有两种:一种是存取控制保护,另一种是地址越界保护。地址越界保护将段表寄存器中的段表长度与逻辑地址中的段号比较,若段号大于段表长度,则产生越界中断;再将段表项中的段长和逻辑地址中的段内偏移进行比较,若段内偏移大于段长,也会产生越界中断。分页管理中的地址越界保护只需要判断页号是否越界,页内偏移是不可能越界的。

与页式管理不同,段式管理不能通过给出一个整数便确定对应的物理地址,因为每段的长度是不固定的,无法通过整数除法得出段号,无法通过求余得出段内偏移,所以段号和段内偏移一定要显式给出(段号,段内偏移),因此分段管理的地址空间是 $\color{green}{\text{二维的}}$ 。

段页式管理方式

页式存储管理能有效地提高内存利用率,而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方法结合起来,便形成了段页式存储管理方式。

在段页式系统中,作业的地址空间首先被分成若干逻辑段,每段都有自己的段号,然后将每段分成若干大小固定的页。对内存空间的管理仍然和分页存储管理一样,将其分成若干和页面大小相同的存储块,对内存的分配以存储块为单位,如图3.17所示。

在段页式系统中,作业的逻辑地址分为三部分:段号、页号和页内偏移量,如图3.18所示。

图3.18 段页式系统的逻辑地址结构找不到图片(Image not found)

为了实现地址变换,系统为每个进程建立一张段表,每个分段有一张页表。段表表项中至少包括段号、页表长度和页表始址,页表表项中至少包括页号和块号。此外,系统中还应有一个段表寄存器,指出作业的段表始址和段表长度(段表寄存器和页表寄存器的作用都有两个,一是在段表或页表中寻址,二是判断是否越界)。

注意:在一个进程中,段表只有 $\color{green}{\text{一个}}$ ,而页表可能有 $\color{green}{\text{多个}}$ 。

在进行地址变换时,首先通过段表查到页表始址,然后通过页表找到页帧号,最后形成物理地址。如图3.19所示,进行一次访问实际需要三次访问主存,这里同样可以使用快表来加快查找速度,其关键字由段号、页号组成,值是对应的页帧号和保护码。

图3.19 段页式系统的地址变换机构找不到图片(Image not found)

结合上面对段式管理和页式管理的地址空间的分析,可以得出结论:段页式管理的地址空间是 $\color{green}{\text{二维的}}$ 。

本节小结

本节开头提出的问题的参考答案如下。

为什么要进行内存管理?

在单道批处理系统阶段,一个系统在一个时间段内只执行一个程序,内存的分配极其简单,即仅分配给当前运行的进程。引入多道程序的并发执行后,进程之间共享的不仅仅是处理机,还有主存储器。然而,共享主存会形成一些特殊的挑战。若不对内存进行管理,则容易导致内存数据的混乱,以至于限制进程的并发执行。因此,为了更好地支持多道程序并发执行,必须进行内存管理。

页式管理中每个页表项大小的下限如何决定?

页表项的作用是找到该页在内存中的位置。以32位逻辑地址空间、字节编址单位、一页4KB为例,地址空间内共含有 $2^{32}$ B/4KB= 1M页,需要 $log_2 1M$ = 20位才能保证表示范围能容纳所有页面,又因为以字节作为编址单位,即页表项的大小≥ $\lceil 20/8 \rceil$ =3B。所以在这个条件下,为了保证页表项能够指向所有页面,页表项的大小应该大于3B;当然,也可选择更大的页表项大小,让一个页面能够正好容下整数个页表项,以方便存储(例如取成4B,一页正好可以装下1K个页表项),或增加一些其他信息。

多级页表解决了什么问题?又会带来什么问题?

多级页表解决了当逻辑地址空间过大时,页表的长度会大大增加的问题。而采用多级页表时,一次访盘需要多次访问内存甚至磁盘,会大大增加一次访存的时间。

不少读者表示本节的内容难以掌握,实际上本节的内容并不难,只要抓住下列几个关键的线索,本节的所有知识点就能了然于胸。

无论是段式管理、页式管理还是段页式管理,读者都只需要关注三个问题:①逻辑地址结构,②表项结构,③寻址过程。搞清楚这三个问题,就相当于搞清楚了上面几种存储管理方式。再次提醒读者区分逻辑地址结构和表项结构。

虚拟内存管理

在学习本节时,请读者思考以下问题:

1)为什么要引入虚拟内存?

2)虚拟内存空间的大小由什么因素决定?

3)虚拟内存是怎么解决问题的?会带来什么问题?

读者要掌握虚拟内存解决问题的思想,并了解几种替换算法的优劣,熟练掌握虚实地址的变换方法。

虚拟内存的基本概念

传统存储管理方式的特征

3.1节讨论的各种内存管理策略都是为了同时将多个进程保存在内存中,以便允叶进仃多道程序设计。它们都具有以下两个共同的特征:

1) $\color{green}{\text{一次性}}$ 。作业必须一次性全部装入内存后,才能开始运行。这会导致两种情况:①当作业很大而不能全部被装入内存时,将使该作业无法运行;②当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。

2) $\color{green}{\text{驻留性}}$ 。作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程会因等待IO而被阻塞,可能处于长期等待状态。

由以上分析可知,许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间,而一些需要运行的作业又无法装入运行,显然浪费了宝贵的内存资源。

局部性原理

要真正理解虚拟内存技术的思想,首先须了解著名的局部性原理。Bill Joy (SUN公司CEO)说过:“在研究所时,我经常开玩笑地说高速缓存是计算机科学中唯一重要的思想。事实上,高速缓存技术确实极大地影响了计算机系统的设计。”快表、页高速缓存及虚拟内存技术从广义上讲,都属于高速缓存技术。这个技术所依赖的原理就是局部性原理。局部性原理既适用于程序结构,又适用于数据结构(更远地讲,Dijkstra 关于“goto 语句有害”的著名论文也出于对程序局部性原理的深刻认识和理解)。

局部性原理表现在以下两个方面:

1) $\color{green}{\text{时间局部性}}$ 。程序中的某条指令一旦执行,不久后该指令可能再次执行;某数据被访问过,不久后该数据可能再次被访问。产生时间局部性的典型原因是程序中存在着大量的循环操作。

2) $\color{green}{\text{空间局部性}}$ 。一旦程序访问了某个存储单元,在不久后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。

时间局部性通过将近来使用的指令和数据保存到高速缓冲存储器中,并使用高速缓存的层次结构实现。空间局部性通常使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上建立了“内存-外存”的两级存储器结构,利用局部性原理实现高速缓存。

虚拟存储器的定义和特征

基于局部性原理,在程序装入时,将程序的一部分装入内存,而将其余部分留在外存,就可启动程序执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为 $\color{green}{\text{虚拟存储器}}$ 。

之所以将其称为虚拟存储器,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入、请求调入和置换功能后(对用户完全透明),给用户的感觉是好像存在一个比实际物理内存大得多的存储器。虚拟存储器的大小由计算机的地址结构决定,并不是内存和外存的简单相加。虚拟存储器有以下三个主要特征:

1) $\color{green}{\text{多次性}}$ 。多次性是指无须在作业运行时一次性地全部装入内存,而允许被分成多次调入内存运行。

2) $\color{green}{\text{对换性}}$ 。对换性是指无须在作业运行时一直常驻内存,而允许在作业的运行过程中,进行换进和换出。

3) $\color{green}{\text{虚拟性}}$ 。虚拟性是指从逻辑上扩充内存的容量,使用户所看到的内存容量远大于实际的内存容量。

虚拟内存技术的实现

虚拟内存技术允许将一个作业分多次调入内存。采用连续分配方式时,会使相当一部分内存空间都处于暂时或“永久”的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。

虚拟内存的实现有以下三种方式:

  • 请求分页存储管理。
  • 请求分段存储管理。
  • 请求段页式存储管理。

不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:

  • 一定容量的 $\color{green}{\text{内存}}$ 和 $\color{green}{\text{外存}}$ 。
  • $\color{green}{\text{页表机制}}$ (或段表机制),作为主要的数据结构。
  • $\color{green}{\text{中断机构}}$ ,当用户程序要访问的部分尚未调入内存时,则产生中断。
  • $\color{green}{\text{地址变换机构}}$ ,逻辑地址到物理地址的变换。

请求分页管理方式

请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。

在请求分页系统中,只要求将当前需要的一部分页面装入内存,便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存中时,再通过调页功能将其调入,同时还可通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。

为了实现请求分页,系统必须提供一定的硬件支持。除了需要一定容量的内存及外存的计算机系统,还需要有页表机制、缺页中断机构和地址变换机构。

页表机制

请求分页系统的页表机制不同于基本分页系统,请求分页系统在一个作业运行之前不要求全部一次性调入内存,因此在作业的运行过程中,必然会出现要访问的页面不在内存中的情况,如何发现和处理这种情况是请求分页系统必须解决的两个基本问题。为此,在请求页表项中增加了4个字段,如图3.20所示。

图3.20 请求分页系统中的页表项找不到图片(Image not found)

增加的4个字段说明如下:

  • 状态位P。用于指示该页是否已调入内存,供程序访问时参考。
  • 访问字段A。用于记录本页在一段时间内被访问的次数,或记录本页最近已有多长时间未被访问,供置换算法换出页面时参考。
  • 修改位M。标识该页在调入内存后是否被修改过。
  • 外存地址。用于指出该页在外存上的地址,通常是物理块号,供调入该页时参考。
缺页中断机构

在请求分页系统中,每当所要访问的页面不在内存中时,便产生一个缺页中断,请求操作系统将所缺的页调入内存。此时应将缺页的进程阻塞(调页完成唤醒),若内存中有空闲块,则分配一个块,将要调入的页装入该块,并修改页表中的相应页表项,若此时内存中没有空闲块,则要淘汰某页(若被淘汰页在内存期间被修改过,则要将其写回外存)。

缺页中断作为中断,同样要经历诸如保护CPU环境、分析中断原因、转入缺页中断处理程序、恢复CPU环境等几个步骤。但与一般的中断相比,它有以下两个明显的区别:

  • 在指令执行期间而非一条指令执行完后产生和处理中断信号,属于内部中断。
  • 一条指令在执行期间,可能产生多次缺页中断。
地址变换机构

请求分页系统中的地址变换机构,是在分页系统地址变换机构的基础上,为实现虚拟内存,又增加了某些功能而形成的。

如图3.21所示,在进行地址变换时,先检索快表:

图3.21 请求分页中的地址变换过程找不到图片(Image not found)
  • 若找到要访问的页,则修改页表项中的访问位(写指令还需要重置修改位),然后利用页表项中给出的物理块号和页内地址形成物理地址。
  • 若未找到该页的页表项,则应到内存中去查找页表,再对比页表项中的状态位P,看该页是否已调入内存,未调入则产生缺页中断,请求从外存把该页调入内存。

页面置换算法(决定应该换入哪页、换出哪页)

进程运行时,若其访问的页面不在内存中而需将其调入,但内存已无空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区。

选择调出页面的算法就称为页面置换算法。好的页面置换算法应有较低的页面更换频率也就是说,应将以后不会再访问或以后较长时间内不会再访问的页面先调出。

常见的置换算法有以下4种。

最佳(OPT)置换算法

最佳(Optimal,OPT)置换算法选择的被淘汰页面是以后永不使用的页面,或是在最长时间内不再被访问的页面,以便保证获得最低的缺页率。然而,由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。

最佳置换算法可用来评价其他算法。假定系统为某进程分配了三个物理块,并考虑有页面号引用串7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1。进程运行时,先将7,0,1三个页面依次装入内存。进程要访问页面2时,产生缺页中断,根据最佳置换算法,选择将第18次访问才需调入的页面7淘汰。然后,访问页面0时,因为它已在内存中,所以不必产生缺页中断。访问页面3时,又会根据最佳置换算法将页面1淘汰……以此类推,如图3.22所示,从图中可以看出采用最佳置换算法时的情况。

图片详情找不到图片(Image not found)

最长时间不被访问和以后被访问次数最小是不同的概念,初学者在理解OPT 算法时千万不要混淆。

可以看到,发生缺页中断的次数为9,页面置换的次数为6。

先进先出(FIFO)页面置换算法

优先淘汰最早进入内存的页面,即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。

这里仍用上面的实例采用FIFO算法进行页面置换。进程访问页面2时,把最早进入内存的页面7换出。然后访问页面3时,把2,0,1中最先进入内存的页面0换出。由图3.23可以看出,利用FIFO算法时进行了12次页面置换,比最佳置换算法正好多一倍。

FIFO算法还会产生所分配的物理块数增大而页故障数不减反增的异常现象,这由Belady于1969年发现,因此称为Belady异常。只有FIFO算法可能出现 $\color{green}{\text{Belady异常}}$ ,LRU和OPT算法永远不会出现Belady 异常。

图3.23 利用FIFO置换算法时的置换图找不到图片(Image not found)

如图3.24所示,页面访问顺序为3,2,1,0,3,2,4,3,2,1,0,4。若采用FIFO置换算法,当分配的物理块为3个时,缺页次数为9次;当分配的物理块为4个时,缺页次数为10次。分配给进程的物理块增多,但缺页次数不减反增。

图3.24 Belady异常找不到图片(Image not found)
最近最久未使用(LRU)置换算法

选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。

再对上面的实例采用LRU算法进行页面置换,如图3.25所示。进程第一次对页面2访问时,将最近最久未被访问的页面7置换出去。然后在访问页面3时,将最近最久未使用的页面1换出。

3.25 LRU页面置换算法时的置换图找不到图片(Image not found)

在图3.25中,前5次置换的情况与最佳置换算法相同,但两种算法并无必然联系。实际上,LRU算法根据各页以前的情况,是“向前看”的,而最佳置换算法则根据各页以后的使用情况,是“向后看”的。

LRU算法的性能较好,但需要寄存器和栈的硬件支持。LRU是堆栈类的算法。理论上可以证明, $\color{green}{\text{堆栈类算法}}$ 不可能出现Belady异常。FIFO算法基于队列实现,不是堆栈类算法。

时钟(CLOCK)置换算法

LRU算法的性能接近于OPT算法,但实现起来比较困难,且开销大;FIFO算法实现简单,但性能差。因此,操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU算法的性能,这类算法都是CLOCK算法的变体。因为算法要循环扫描缓冲区,像时钟的指针一样转动,所以称为CLOCK算法。

简单的CLOCK算法给每帧关联一个附加位,称为 $\color{green}{\text{使用位}}$ 。当某页首次装入主存时,将该帧的使用位设置为 1;当该页随后再被访问到时,其使用位也被置为1。对于页替换算法,用于替换的候选帧集合可视为一个循环缓冲区,并有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;若在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;若所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并停留在最初的位置上,替换该帧中的页。由于该算法循环检查各页面的情况,因此称 $\color{red}{\text{CLOCK算法}}$ ,又称 $\color{green}{\text{最近未用}}$ (Not RecentlyUsed,NRU)算法。

CLOCK算法的性能比较接近LRU算法,而通过增加使用的位数目,可以使得CLOCK算法更加高效。在使用位的基础上再增加一个修改位,则得到改进型CLOCK置换算法。这样,每帧都处于以下4种情况之一:

  • 最近未被访问,也未被修改(u = 0,m = 0)
  • 最近被访问,但未被修改(u = 1,m = 0)
  • 最近未被访问,但被修改(u = 0, m = 1)
  • 最近被访问,被修改(u = 1, m= 1)

算法执行如下操作步骤:

1)从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u =0, m = 0)用于替换。

2)若第1)步失败,则重新扫描,查找(u=0,m= 1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。

3)若第2)步失败,则指针将回到它的最初位置,且集合中所有帧的使用位均为0。重复第1)步,并且若有必要,重复第2)步,以便可以找到供替换的帧。

改进型CLOCK算法优于简单CLOCK算法的地方在于替换时首选没有变化的页。由于修改过的页在被替换之前必须写回,因而这样做会节省时间。

有些读者会认为CLOCK算法和改进型CLOCK算法记忆起来不易。为方便记忆,我们将其总结如下。

操作系统中任何经过优化而有效的页面置换算法都有一个原则,即尽可能保留曾经使用过的页面,而淘汰未使用的页面,认为这样可以在总体上减少换页次数。CLOCK 算法只考虑到是否被访问过,因此被访问过的当然尽可能留下,未使用过的就淘汰;而改进型CLOCK算法对使用过的页面又做了细分,分为使用过但未修改过和使用过且修改过。因此,若有未使用过的页面,则当然首先把它换出,若全部页面都使用过,则当然优先把未修改过的页面换出。

  • 替换优先级(在前面的优先被替换): $\color{green}{\text{未修改}}$ > $\color{green}{\text{未使用}}$ > $\color{green}{\text{修改}}$

为帮助读者理解,这里举一个例子。假设系统给某进程分配了5个页框,刚开始,进程依次访问1,3,4,2,5号页面,系统会将这些页面连成一个循环队列,刚开始扫描指针指向第一个被访问的页面(即1号页),如图3.26所示。

图3.26 刚开始扫描时指针指向的页面找不到图片(Image not found)

图3.26中,小括号内的数字就是使用位。接下来,若进程请求访问6号页面,则由于此时分配给进程的5个页框都被使用,因此必须选择一个页面置换出去。按照CLOCK置换算法的规则,在第一轮扫描中,指针扫过的页面的使用位应置为0。第一轮扫描的过程如图3.27所示。

图3.27 第一轮扫描的过程找不到图片(Image not found)

第一轮扫描中,未找到使用位为0的页面,因此需要进行第二轮扫描。第二轮扫描中,1号页面的使用位为0,因此将1号页面换出,将6号页面换入,将6号页的访问位设置为1,并将扫描指针后移(若下次需要换出页面,则从3号页面开始扫描),如图3.28所示。

注意一个小细节:假设1号页面原先占有的是 $x$ 号物理块(页框),则6号页面换入内存后也放在 $x$ 号物理块中。

图3.28 第二轮扫描后指针指向的页面找不到图片(Image not found)

页面分配策略

驻留集大小

对于分页式的虚拟内存,在进程准备执行时,不需要也不可能把一个进程的所有页都读入主存。因此,操作系统必须决定读取多少页,即决定给特定的进程分配几个页框。给一个进程分配的物理页框的集合就是这个进程的 $\color{green}{\text{驻留集}}$ 。需要考虑以下几点:

1)分配给一个进程的存储量越小,任何时候驻留在主存中的进程数就越多,从而可以提高处理机的时间利用效率。

2)若一个进程在主存中的页数过少,则尽管有局部性原理,页错误率仍然会相对较高。

3)若页数过多,则由于局部性原理,给特定的进程分配更多的主存空间对该进程的错误率没有明显的影响。

基于这些因素,现代操作系统通常采用三种策略:

1) $\color{green}{\text{固定分配局部置换}}$ 。它为每个进程分配一定数目的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后调入需要的页面。实现这种策略时,难以确定应为每个进程分配的物理块数目:太少会频繁出现缺页中断,太多又会使CPU和其他资源利用率下降。

2) $\color{green}{\text{可变分配全局置换}}$ 。这是最易于实现的物理块分配和置换策略,它为系统中的每个进程分配一定数目的物理块,操作系统自身也保持一个空闲物理块队列。当某进程发生缺页时,系统从空闲物理块队列中取出一个物理块分配给该进程,并将欲调入的页装入其中。这种方法比固定分配局部置换更加灵活,可以动态增加进程的物理块,但也存在弊端如它会盲目地给进程增加物理块,从而导致系统多道程序的并发能力下降。

3) $\color{green}{\text{可变分配局部置换}}$ 。它为每个进程分配一定数目的物理块,当某个进程发生缺页时,只允许从该进程在内存的页面中选出一页换出,因此不会影响其他进程的运行。若进程在运行中频繁地缺页,则系统再为该进程分配若干物理块,直至该进程缺页率趋于适当程度;反之,若进程运行中的缺页率特别低,则可适当减少分配给该进程的物理块。比起可变分配全局置换,这种方法不仅可以动态增加进程物理块的数量,还能动态减少进程物理块的数量,在保证进程不会过多地调页的同时,也保持了系统的多道程序并发能力。当然它需要更复杂的实现,也需要更大的开销,但对比频繁地换入/换出所浪费的计算机资源,这种牺牲是值得的。

页面分配策略在2015年的统考选择题中出现过,考查的是这三种策略的名称。往年很多读者看到这里时,由于认为不是重点,复习时便一带而过,最后在考试中失分。在这种基础题上失分是十分可惜的。再次提醒读者,考研成功的秘诀在于“反复多次”和“全面”。

调入页面的时机

为确定系统将进程运行时所缺的页面调入内存的时机,可采取以下两种调页策略:

1)预调页策略。根据局部性原理,一次调入若干相邻的页可能会比一次调入一页更高效。但若调入的一批页面中大多数都未被访问,则又是低效的。因此,需要采用以预测为基础的预调页策略,将预计在不久之后便会被访问的页面预先调入内存。但目前预调页的成功率仅约50%。因此这种策略主要用于进程的首次调入,由程序员指出应先调入哪些页。

2)请求调页策略。进程在运行中需要访问的页面不在内存而提出请求,由系统将所需页面调入内存。由这种策略调入的页一定会被访问,且这种策略比较易于实现,因此在目前的虚拟存储器中大多采用此策略。它的缺点是每次只调入一页,调入/调出页面数多时会花费过多的IO开销。

预调入实际上就是运行前的调入,请求调页实际上就是运行期间调入。一般情况下,两种调页策略会同时使用。

从何处调入页面

请求分页系统中的外存分为两部分:用于存放文件的文件区和用于存放对换页面的对换区。对换区通常采用连续分配方式,而文件区采用离散分配方式,因此对换区的磁盘IO速度比文件区的更快。这样,从何处调入页面就存在三种情况:

1)系统拥有足够的对换区空间。可以全部从对换区调入所需页面,以提高调页速度。为此,在进程运行前,需将与该进程有关的文件从文件区复制到对换区。

2)系统缺少足够的对换区空间。凡不会被修改的文件都直接从文件区调入;而当换出这些页面时,由于它们未被修改而不必再将它们换出。但对于那些可能被修改的部分,在将它们换出时须调到对换区,以后需要时再从对换区调入(因为读的速度比写的速度快)

3)UNIX方式。与进程有关的文件都放在文件区,因此未运行过的页面都应从文件区调入曾经运行过但又被换出的页面,由于放在对换区,因此下次调入时应从对换区调入。进程请求的共享页面若被其他进程调入内存,则无须再从对换区调入。

抖动

在页面置换过程中,一种最糟糕的情形是,刚刚换出的页面马上又要换入主存,刚刚换入的页面马上又要换出主存,这种频繁的页面调度行为称为抖动或颠簸。若一个进程在换页上用的时间多于执行时间,则这个进程就在颠簸。

频繁发生缺页中断(抖动)的主要原因是,某个进程频繁访问的页面数目高于可用的物理页帧数目。虚拟内存技术可在内存中保留更多的进程以提高系统效率。在稳定状态,几乎主存的所有空间都被进程块占据,处理机和操作系统可以直接访问到尽可能多的进程。然而,如果管理不当,那么处理机的大部分时间都将用于交换块,即请求调入页面的操作,而不是执行进程的指令,因此会大大降低系统效率。

工作集

工作集是指在某段时间间隔内,进程要访问的页面集合。基于局部性原理,可以用最近访问过的页面来确定工作集。一般来说,工作集W可由时间t和工作集窗口大小△来确定。例如,某进程对页面的访问次序如下:

图片详情找不到图片(Image not found)

地址翻译

本小节引入一个实例来说明虚实地址的变换过程,考虑到统考试题近来出现了学科综合的趋势,这里结合“计算机组成原理”中的Cache部分进行讲解。对于不参加统考的读者,可以看到翻译出实地址为止,对于参加统考却还没有复习计算机组成原理的读者,可在复习完“计算机组成原理”后,再回来看本章的内容。

设某系统满足以下条件:

图片详情找不到图片(Image not found)
  • $\color{red}{\text{Q:}}$ 为什么虚拟地址和物理地址的位数可以不一致
  • $\color{green}{\text{A:}}}$ 确实可以,只要按照低位对齐就好了

本章小结

为什么要引入虚拟内存?

上一节提到过,多道程序并发执行不仅使进程之间共享了处理器,而且同时共享了主存。然而,随着对处理器需求的增长,进程的执行速度会以某种合理平滑的方式慢下来。但是,若同时运行的进程太多,则需要很多的内存,当一个程序没有内存空间可用时,那么它甚至无法运行。所以,在物理上扩展内存相对有限的条件下,应尝试以一些其他可行的方式在逻辑上扩充内存。

虚拟内存(虚存)空间的大小由什么因素决定?

虚存的容量要满足以下两个条件:

①虚存的实际容量≤内存容量和外存容量之和,这是硬件的硬性条件规定的,若虚存的实际容量超过了这个容量,则没有相应的空间来供虚存使用。

${\textstyle\unicode{x2461}}$ 虚存的最大容量≤计算机的地址位数能容纳的最大容量。假设地址是32位的,按字节编址,一个地址代表1B存储空间,则虚存的最大容量≤4GB( $2^{32}$ B)。这是因为若虚存的最大容量超过4GB,则32位的地址将无法访问全部虚存,也就是说4GB 以后的空间被浪费了,相当于没有一样,没有任何意义。

实际虚存的容量是取条件①和②的交集,即两个条件都要满足,仅满足一个条件是不行的。

虚拟内存是怎么解决问题的?会带来什么问题?

虚拟内存使用外存上的空间来扩充内存空间,通过一定的换入/换出,使得整个系统在逻辑上能够使用一个远远超出其物理内存大小的内存容量。因为虚拟内存技术调换页面时需要访问外存,会导致平均访存时间增加,若使用了不合适的替换算法,则会大大降低系统性能。

本节学习了4种页面置换算法,要把它们与处理机调度算法区分开。当然,这些调度算法之间也是有联系的,它们都有一个共同点,即通过一定的准则决定资源的分配对象。在处理机调度算法中这些准则比较多,有优先级、响应比、时间片等,而在页面调度算法中就比较简单,即是否被用到过或近段时间内是否经常使用。在操作系统中,几乎每类资源都会有相关的调度算法,读者通过将这些调度算法作为线索,可把整个操作系统的课程连成一个整体。

本章疑难点

分页管理方式和分段管理方式在很多地方是相似的,比如在内存中都是不连续的、都有地址变换机构来进行地址映射等。但两者也存在许多区别,表3.7列出了分页管理方式和分段管理方式各方面的对比。

表3.7分页管理方式和分段管理方式的比较找不到图片(Image not found)

文件管理

【考纲内容】

(一)文件系统基础

  1. 文件的概念
  2. 文件的逻辑结构: $\color{red}{\text{文件元数据}}$ 和索引节点(inode)
  3. 文件的操作(建立,删除,打开,关闭,读,写)
  4. 文件的保护
  5. 文件的逻辑结构
  6. 文件的物理结构

(二)目录

  1. 目录的基本概念
  2. 树形目录
  3. $\color{red}{\text{目录的操作}}$
  4. 硬链接和软链接

(二)文件系统

  1. $\color{red}{\text{文件系统的全局结构}}$ (layout)(文件系统在外存中的结构,文件系统在内存中的结构)
  2. 外存空闲空间管理办法
  3. $\color{red}{\text{虚拟文件系统}}$
  4. $\color{red}{\text{文件系统挂载}}$ (mounting)
  • 概念——定义、属性、基本操作、打开与关闭
  • 文件逻辑结构
    • 无结果文件(流式文件)
    • 有结构文件(记录式文件)
      • 顺序文件
      • 索引文件
      • 索引顺序文件
  • 目录结构
    • 文件控制块(FCB)、索引结点
    • 单级目录结构、两级目录结构、树形目录结构、图形目录结构
  • 文件共享
    • 基于索引结点(硬链接)
    • 利用符号链实现(软链接)
  • 文件保护————访问类型、访问控制
  • 实现
    • 层次结构
    • 目录实现————线性列表、哈希表
    • 文件分配
      • 连续分配
      • 链接分配
      • 索引分配————索引链接、多层索引、混合索引
    • 文件存储空间管理
      • 空闲表法
      • 空闲链表法
      • 位示图法成组链接法
  • 磁盘
    • 访问时间——寻道时间、延迟时间、传输时间
    • 调度算法
      • 先来先服务(FCFS)————公平
      • 最短寻找时间优先(SSTF)————“饥饿”现象
      • 扫描算法(SCAN)
      • 循环扫描(C-SCAN)
    • 磁盘的管理————初始化、引导块、坏块

【复习提示】

本章内容较为具体,要注意对概念的理解。重点掌握文件系统的结构及其实现、磁盘的相关知识点等。要掌握文件系统的文件控制块、物理分配方法、索引结构,以及磁盘特性和结构、磁盘调度算法,能分析磁盘相关的性能等。这些都是综合题易考查的内容。

文件系统基础

在学习本节时,请读者思考以下问题:

1)什么是文件?什么是文件系统?

2)文件系统要完成哪些功能?

本节内容较为抽象,对于初学者,推荐配合相关教材的相关章节进行学习。学习过程中要注意区分文件的逻辑结构和物理结构,不要把二者混为一谈。在读者的学习过程中,可尝试以上面的两个问题为线索,构建整个文件系统的概念,先思考有什么方法可以实现文件的共享和保护,再将自己的方法与书上的方法相比较。

在前面的学习中,曾经提醒过读者不要忽略对基本概念的理解。操作系统这门课程在统考中算是比较容易得分的一门课程,从历年的情况来看,大部分同学对进程管理、内存管理有较好的掌握,但对于文件管理及下一章的IO管理,往往理解不太深入,记忆不太牢固,在考试中,即使面对一些关于本章内容的基本问题也容易失分,这十分可惜。主要原因还是对概念的理解不够全面和透彻,希望各位读者能够关注这个问题。

文件的概念

文件的定义

文件(File)是操作系统中的一个重要概念。文件是以计算机硬盘为载体的存储在计算机上的信息集合,文件可以是文本文档、图片、程序等。在系统运行时,计算机以进程为基本单位进行资源的调度和分配;而在用户进行的输入、输出中,则以文件为基本单位。大多数应用程序的输入都是通过文件来实现的,其输出也都保存在文件中,以便信息的长期存储及将来的访问。当用户将文件用于应用程序的输入、输出时,还希望可以访问文件、修改文件和保存文件等,实现对文件的维护管理,这就需要系统提供一个文件管理系统,操作系统中的 $\color{green}{\text{文件系统}}$ (File System)就是用于实现用户的这些管理要求的。

要清晰地理解文件的概念,就要了解文件究竟由哪些东西组成。

首先,文件中肯定包括一块存储空间,更准确地说,是存储空间中的数据;其次,由于操作系统要管理成千上万的数据,因此必定需要对这些数据进行划分,然后贴上“标签”,以便于分类和索引,所以文件必定包含分类和索引的信息;最后,不同的用户拥有对数据的不同访问权限,因此文件中一定包含一些关于访问权限的信息。

再举生活中的一个直观例子来类比文件,相信读者了解这个例子后会更深入地了解文件。这个例子就是图书馆中的书,可以认为,计算机中的一个文件相当于图书馆中的一本书,操作系统管理文件,相当于图书管理员管理图书馆中的书。

首先,一本书的主体一定是书中的内容,相当于文件中的数据;其次,不同类别的书需要放在不同的书库,然后加上编号,再把编号登记在图书管理系统中,方便读者查阅,相当于文件的分类和查找;最后,有些已经绝版或价格比较高的外文书籍,只能借给VIP会员或权限比较高的其他读者,而有些普通的书籍可供任何人借阅,这就是文件中的访问权限。

所举的例子与实际操作系统中的情形并不绝对等价,读者应能找出类比中的不严谨之处,但对于某些关键的属性,图书馆管理图书和操作系统管理文件的思想却有相一致的地方,因此通过这种类比可使初学者快速认识陌生的概念。

从用户的角度看,文件系统是操作系统的重要部分之一。用户关心的是如何命名、分类和查找文件,如何保证文件数据的安全性及对文件可以进行哪些操作等。而对其中的细节,如文件如何存储在辅存上、如何管理文件辅存区域等关心甚少。

文件系统提供了与二级存储相关的资源的抽象,让用户能在不了解文件的各种属性、文件存储介质的特征及文件在存储介质上的具体位置等情况下,方便快捷地使用文件。

用户通过文件系统建立文件,提供应用程序的输入、输出,对资源进行管理。首先了解文件的结构,我们通过自底向上的方式来定义。

1)数据项。数据项是文件系统中最低级的数据组织形式,可分为以下两种类型:

  • 基本数据项。用于描述一个对象的某种属性的一个值,如姓名、日期或证件号等,是数据中可命名的最小逻辑数据单位,即原子数据。
  • 组合数据项。由多个基本数据项组成。

2)记录。记录是一组相关的数据项的集合,用于描述一个对象在某方面的属性,如一名考生的报名记录包括考生姓名、出生日期、报考学校代号、身份证号等一系列域。

3)文件。文件是指由创建者所定义的一组相关信息的集合,逻辑上可分为有结构文件和无结构文件两种。在有结构文件中,文件由一组相似的记录组成,如报考某学校的所有考生的报考信息记录,又称记录式文件;而无结构文件则被视为一个字符流,比如一个二进制文件或字符文件,又称流式文件。

虽然上面给出了结构化的表还,但头协上NJ大#制代码,其基太访问单元可以是字节、行或程序和数据组织成文件。文件可以定效丁、于出m中允许可控制的进程间共享访问,能够被组记录。文件可以长期存储于硬盘或其他二级存储器中,允许可控制的进程间共享访问,能够被组织成复杂的结构。

文件的属性

文件具有一定的属性,系统不同,属性也会有所不同,但通常都包括如下属性。

1) $\color{green}{\text{名称}}$ 。文件名称唯一,以容易读取的形式保存。

2) $\color{green}{\text{标识符}}$ 。标识文件系统内文件的唯一标签,通常为数字,是对人不可读的一种内部名称。

3) $\color{green}{\text{类型}}$ 。被支持不同类型的文件系统所使用。

4) $\color{green}{\text{位置}}$ 。指向设备和设备上文件的指针。

5) $\color{green}{\text{大小}}$ 。文件当前大小(用字节、字或块表示),也可包含文件允许的最大值。

6) $\color{green}{\text{保护}}$ 。对文件进行保护的访问控制信息。

7) $\color{green}{\text{时间、日期和用户标识}}$ 。文件创建、上次修改和上次访问的相关信息,用于保护和跟踪文件的使用。

所有文件的信息都保存在目录结构中,而目录结构保存在外存上。文件信息在需要时才调入内存。通常,目录条目包括文件名称及其唯一的标识符,而标识符定位其他属性的信息。

文件的基本操作

文件属于抽象数据类型。为了恰当地定义文件,需要考虑有关文件的操作。操作系统提供系统调用,它对文件进行创建、写、读、重定位、删除和截断等操作。

1) $\color{green}{\text{创建文件}}$ 。创建文件有两个必要步骤:一是在文件系统中为文件找到空间;二是在目录中为新文件创建条目,该条目记录文件名称、在文件系统中的位置及其他可能的信息。

2) $\color{green}{\text{写文件}}$ 。为了写文件,执行一个系统调用,指明文件名称和要写入文件的内容。对于给定文件名称,系统搜索目录以查找文件位置。系统必须为该文件维护一个写位置的指针。每当发生写操作时,便更新写指针。

3) $\color{green}{\text{读文件}}$ 。为了读文件,执行一个系统调用,指明文件名称和要读入文件块的内存位置。同样,需要搜索目录以找到相关目录项,系统维护一个读位置的指针。每当发生读操作时,更新读指针。一个进程通常只对一个文件读或写,因此当前操作位置可作为每个进程当前文件位置的指针。由于读和写操作都使用同一指针,因此节省了空间,也降低了系统复杂度。

4) $\color{green}{\text{文件重定位}}$ (文件寻址)。按某条件搜索目录,将当前文件位置设为给定值,并且不会读、写文件。

5) $\color{green}{\text{删除文件}}$ 。先从目录中找到要删除文件的目录项,使之成为空项,然后回收该文件所占用的存储空间。

6) $\color{green}{\text{截断文件}}$ 。允许文件所有属性不变,并删除文件内容,即将其长度设为0并释放其空间。这6个基本操作可以组合起来执行其他文件操作。例如,一个文件的复制,可以创建新文件、从旧文件读出并写入新文件。

这6个基本操作可以组合起来执行其他文件操作。例如,一个文件的复制,可以创建新文件、从旧文件读出并写入新文件。

文件的打开与关闭

因为许多文件操作都涉及为给定文件搜索相关目录条目,因此许多系统要求在首次使用文件时,使用系统调用open将指明文件的 $\color{green}{\text{属性}}$ (包括该文件在外存上的物理位置)从外存 $\color{green}{\text{复制}}$ 到内存打开文件表的一个表目中,并将该表目的编号(也称索引)返回给用户。操作系统维护一个包含所有打开文件信息的表(打开文件表,open-file table)。当用户需要一个文件操作时,可通过该表的一个索引指定文件,因此省略了搜索环节。当文件不再使用时,进程可以关闭它,操作系统从打开文件表中删除这一条目。

大部分操作系统要求在文件使用之前就被显式地打开。操作 open 会根据文件名搜索目录,并将目录条目复制到打开文件表。若调用open 的请求(创建、只读、读写、添加等)得到允许,则进程就可打开文件,而 open通常返回一个指向打开文件表中的一个条目的指针。通过使用该指针(而非文件名)进行所有IO操作,以简化步骤并节省资源。

注意,在open调用完成后,操作系统对该文件的任何操作都不再需要文件名,而只需要open调用返回的指针。

整个系统表包含进程相关信息,如文件在磁盘的位置、访问日期和大小。一个进程打开一个文件,系统打开文件表就会为打开的文件增加相应的条目。当另一个进程执行open时,只不过是在其进程打开表中增加一个条目,并指向整个系统表的相应条目。通常,系统打开文件表的每个文件时,还用一个文件打开计数器(Open Count),以记录多少进程打开了该文件。每个关闭操作close使count递减,当打开计数器为0时,表示该文件不再被使用,系统将回收分配给该文件的内存空间等资源。若文件被修改过,则将文件写回外存,并将系统打开文件表中的相应条目删除,最后释放文件的文件控制块(File Control Block,FCB)。

每个打开文件都有如下关联信息:

  • 文件指针。系统跟踪上次的读写位置作为当前文件位置的指针,这种指针对打开文件的某个进程来说是唯一的,因此必须与磁盘文件属性分开保存。
  • 文件打开计数。文件关闭时,操作系统必须重用其打开文件表条目,否则表内空间会不够用。因为多个进程可能打开同一个文件,所以系统在删除打开文件条目之前,必须等待最后一个进程关闭文件。计数器跟踪打开和关闭的数量,计数为0时,系统关闭文件,删除该条目。
  • 文件磁盘位置。绝大多数文件操作都要求系统修改文件数据。该信息保存在内存中,以免为每个操作都从磁盘中读取。
  • 访问权限。每个进程打开文件都需要有一个访问模式(创建、只读、读写、添加等)。该信息保存在进程的打开文件表中,以便操作系统能够允许或拒绝之后的I/O请求。

文件的逻辑结构

文件的逻辑结构是从用户观点出发看到的文件的组织形式。文件的物理结构(又称文件的存储结构,见4.2.1节)是从实现观点出发看到的文件在外存上的存储组织形式。文件的逻辑结构与存储介质特性无关,但文件的物理结构与存储介质的特性有很大关系。文件的逻辑结构实际上是指在文件的内部,数据逻辑上是如何组织起来的。

按逻辑结构,文件可划分为无结构文件和有结构文件两种。

无结构文件(流式文件)

无结构文件是最简单的文件组织形式。无结构文件将数据按顺序组织成记录并积累、保存,它是有序相关信息项的集合,以字节(Byte)为单位。由于无结构文件没有结构,因而对记录的访问只能通过穷举搜索的方式,因此这种文件形式对大多数应用不适用。但字符流的无结构文件管理简单,用户可以方便地对其进行操作。所以,那些对基本信息单位操作不多的文件较适于采用字符流的无结构方式,如源程序文件、目标代码文件等。

有结构文件(记录式文件)

有结构文件按记录的组织形式可以分为如下几种:

1)顺序文件。文件中的记录一个接一个地顺序排列,记录通常是定长的,可以顺序存储或以链表形式存储,在访问时需要顺序搜索文件。顺序文件有以下两种结构:第一种是串结构,记录之间的顺序与关键字无关。通常的办法是由时间决定,即按存入时间的先后排列,最先存入的记录作为第1条记录,其次存入的为第2条记录,以此类推。第二种是顺序结构,指文件中的所有记录按关键字顺序排列。

在对记录进行批量操作,即每次要读或写一大批记录时,顺序文件的效率是所有逻辑文件中最高的;此外,也只有顺序文件才能存储在磁带上,并能有效地工作,但顺序文件对查找、修改、增加或删除单条记录的操作比较困难。

2)索引文件。索引文件示意图如图4.1所示。对于定长记录文件,要查找第i条记录,可直接根据下式计算得到第i条记录相对于第1条记录的地址:

然而,对于可变长记录的文件,要查找第i条记录,必须顺序地查找前 $i$ –1条记录,从而获得相应记录的长度L,进而按下式计算出第 $i$ 条记录的首址:

注意:假定每条记录前用一个字节指明该记录的长度。

变长记录文件只能顺序查找,系统开销较大。为此,可以建立一张索引表以加快检索速度,索引表本身是定长记录的顺序文件。在记录很多或访问要求高的文件中,需要引入索引以提供有效的访问。实际中,通过索引可以成百上千倍地提高访问速度。

图4.1索引文件示意图找不到图片(Image not found)

3)索引顺序文件。索引顺序文件是顺序和索引两种组织形式的结合。索引顺序文件将顺序文件中的所有记录分为若干组,为顺序文件建立一张索引表,在索引表中为每组中的第一条记录建立一个索引项,其中含有该记录的关键字值和指向该记录的指针。

如图4.2所示,主文件名包含姓名和其他数据项。姓名为关键字,索引表中为每组的第一条记录(不是每条记录)的关键字值,用指针指向主文件中该记录的起始位置。索引表只包含关键字和指针两个数据项,所有姓名关键字递增排列。主文件中记录分组排列,同一个组中的关键字可以无序,但组与组之间的关键字必须有序。查找一条记录时,首先通过索引表找到其所在的组,然后在该组中使用顺序查找,就能很快地找到记录。

图4.2索引顺序文件示意图找不到图片(Image not found)

对于含有N条记录的顺序文件,查找某关键字值的记录时,平均需要查找N/2次。在索引顺序文件中,假设N条记录分为 $\sqrt{N}$ 组,索引表中有$\sqrt{N}$个表项,每组有$\sqrt{N}$条记录,在查找某关键字值的记录时,先顺序查找索引表,需要查找√N/2次,然后在主文件中对应的组中顺序查找,也需要查找$\sqrt{N}$/2次,因此共需查找$\sqrt{N}$ /2+$\sqrt{N}$ /2=$\sqrt{N}$ 次。显然,索引顺序文件提高了查找效率,若记录数很多,则可采用两级或多级索引。

索引文件和索引顺序文件都提高了存取的速度,但因为配置索引表而增加了存储空间。

4)直接文件或散列文件(Hash File)。给定记录的键值或通过散列函数转换的键值直接决定记录的物理地址。这种映射结构不同于顺序文件或索引文件,没有顺序的特性。

散列文件有很高的存取速度,但是会引起冲突,即不同关键字的散列函数值相同。

复习了数据结构的读者读到这里时,会有这样的感觉:有结构文件逻辑上的组织,是为在文件中查找数据服务的(顺序查找、索引查找、索引顺序查找、哈希查找)。

目录结构

与文件管理系统和文件集合相关联的是文件目录,它包含有关文件的信息如属性、位置和所有权等,这些信息主要由操作系统进行管理。首先我们来看目录管理的基本要求:从用户的角度看,目录在用户(应用程序)所需要的文件名和文件之间提供一种映射,所以目录管理要实现“按名存取”;目录存取的效率直接影响到系统的性能,所以要提高对目录的检索速度;在共享系统中,目录还需要提供用于控制访问文件的信息。此外,文件允许重名也是用户的合理和必然要求,目录管理通过树形结构来解决和实现。

前面介绍了文件内部的逻辑结构,下面介绍多个文件之间在逻辑上是如何组织的,这实际上是文件“外部”的逻辑结构的问题。

文件控制块和索引结点

与进程管理一样,为实现目录管理,操作系统中引入了文件控制块的数据结构。

1)文件控制块。文件控制块(FCB)是用来存放控制文件需要的各种信息的数据结构以实现“按名存取”。FCB的有序集合称为文件目录,一个FCB就是一个文件目录项为了创建一个新文件,系统将分配一个FCB并存放在文件目录中,成为目录项。

FCB主要包含以下信息:

  • 基本信息,如文件名、文件的物理位置、文件的逻辑结构、文件的物理结构等。
  • 存取控制信息,如文件存取权限等。
  • 使用信息,如文件建立时间、修改时间等。

2)索引结点。在检索目录文件的过程中,只用到了文件名,仅当找到一个目录项(查找文件名与目录项中文件名匹配)时,才需要从该目录项中读出该文件的物理地址。也就是说,在检索目录时,文件的其他描述信息不会用到,也不需要调入内存。因此,有的系统(如UNIX,见表4.1)采用了文件名和文件描述信息分开的方法,文件描述信息单独形成一个称为索引结点的数据结构,简称i结点。在文件目录中的每个目录项仅由文件名和指向该文件所对应的i结点的指针构成。

表4.1UNIX的文件目录结构找不到图片(Image not found)

一个FCB的大小是64B,盘块大小是1KB,因此在每个盘块中可以存放16个FCB(注意,FCB必须连续存放)。而在 UNIX系统中,一个目录项仅占16B,其中14B是文件名,2B是i结点指针。在1KB的盘块中可存放64个目录项。这样,就可使查找文件时的平均启动磁盘次数减少到原来的1/4,大大节省了系统开销。

存放在磁盘上的索引结点称为磁盘索引结点,UNIX中的每个文件都有一个唯一的磁盘索引结点,主要包括以下几个方面:

  • $\color{green}{\text{文件主标识符}}$ ,拥有该文件的个人或小组的标识符。
  • $\color{green}{\text{文件类型}}$ ,包括普通文件、目录文件或特别文件。文件存取权限,各类用户对该文件的存取权限。
  • $\color{green}{\text{文件物理地址}}$ ,每个索引结点中含有13个地址项,即 iaddr(O)一iaddr(12),它们以直接或间接方式给出数据文件所在盘块的编号。
  • $\color{green}{\text{文件长度}}$ ,以字节为单位。
  • $\color{green}{\text{文件链接计数}}$ ,在本文件系统中所有指向该文件的文件名的指针计数。
  • $\color{green}{\text{文件存取时间}}$ ,本文件最近被进程存取的时间、最近被修改的时间及索引结点最近被修改的时间。

文件被打开时,磁盘索引结点复制到内存的索引结点中,以便于使用。在内存索引结点中又增加了以下内容:

  • 索引结点编号,用于标识内存索引结点。
  • 状态,指示i结点是否上锁或被修改。
  • 访问计数,每当有一进程要访问此i结点时,计数加1,访问结束减1。
  • 逻辑设备号,文件所属文件系统的逻辑设备号。
  • 链接指针,设置分别指向空闲链表和散列队列的指针。

FCB或索引结点相当于图书馆中图书的索书号,我们可以在图书馆网站上找到图书的索书号,然后根据索书号找到想要的书本。

目录结构

在理解一个文件系统的需求前,我们首先考虑在目录这个层次上所需要执行的操作,这有助于后面文件系统的整体理解。

  • 搜索。当用户使用一个文件时,需要搜索目录,以找到该文件的对应目录项。
  • 创建文件。当创建一个新文件时,需要在目录中增加-个目录项。
  • 删除文件。当删除一个文件时,需要在目录中删除相应的目录项。
  • 显示目录。用户可以请求显示目录的内容,如显示该用户目录中的所有文件及属性。
  • 修改目录。某些文件属性保存在目录中,因而这些属性的变化需要改变相应的目录项。操作时,考虑以下几种目录结构:

操作时,考虑以下几种目录结构:

1)$\color{green}{\text{单级目录结构}}$ 。在整个文件系统中只建立一张目录表,每个文件占一个目录项,如图4.3所示。

图4.3单级目录结构找不到图片(Image not found)

当访问一个文件时,先按文件名在该目录中查找到相应的FCB,经合法性检查后执行相应的操作。当建立一个新文件时,必须先检索所有目录项以确保没有“重名”的情况,然后在该目录中增设一项,把FCB 的全部信息保存在该项中。当删除一个文件时,先从该目录中找到该文件的目录项,回收该文件所占用的存储空间,然后清除该目录项。

单级目录结构实现了“按名存取”,但是存在查找速度慢、文件不允许重名、不便于文件共享等缺点,而且对于多用户的操作系统显然是不适用的。

2)两级目录结构。单级目录很容易造成文件名称的混淆,因此可以考虑采用两级方案,将文件目录分成主文件目录(Master File Directory,MFD)和用户文件目录(User FileDirectory, UFD)两级,如图4.4所示。

主文件目录项记录用户名及相应用户文件目录所在的存储位置。用户文件目录项记录该用户文件的FCB 信息。当某用户欲对其文件进行访问时,只需搜索该用户对应的UFD,这既解决了不同用户文件的“重名”问题,又在一定程度上保证了文件的安全。

两级目录结构可以解决多用户之间的文件重名问题,文件系统可以在目录上实现访问限制。但是两级目录结构缺乏灵活性,不能对文件分类。

图4.4两级目录结构找不到图片(Image not found)

3)多级目录结构(树形目录结构)。将两级目录结构的层次关系加以推广,就形成了多级目录结构,即树形目录结构,如图4.5所示。

用户要访问某个文件时,用文件的路径名标识文件,文件路径名是个字符串,由从根目录出发到所找文件通路上所有目录名与数据文件名用分隔符“/”链接而成。从根目录出发的路径称为绝对路径。当层次较多时,每次从根目录查询会浪费时间,于是加入了当前目录(又称工作目录),进程对各文件的访问都是相对于当前目录进行的。当用户要访问某个文件时,使用相对路径标识文件,相对路径由从当前目录出发到所找文件通路上所有目录名与数据文件名用分隔符“/”链接而成。

图4.5树形目录结构找不到图片(Image not found)

图4.5是Linux 操作系统的目录结构,”/dev/hda”就是一个绝对路径。若当前目录为“/bin”,则“./1s”就是一个相对路径,其中符号“.”表示当前工作目录。

通常,每个用户都有各自的“当前目录”,登录后自动进入该用户的“当前目录”。操作系统提供一条专门的系统调用,供用户随时改变“当前目录”。例如,在 UNIX系统中,”/etc/passwd”文件就包含有用户登录时默认的“当前目录”,可用cd命令改变“当前目录”。

树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。但是,在树形目录中查找一个文件时,需要按路径名逐级访问中间结点,这就增加了磁盘访问次数,无疑将影响查询速度。

4)无环图目录结构。树形目录结构能便于实现文件分类,但不便于实现文件共享,为此在树形目录结构的基础上增加了一些指向同一结点的有向边,使整个目录成为一个有向无环图。引入无环图目录结构是为了实现文件共享,如图4.6所示。

图4.6无环图目录结构找不到图片(Image not found)

当某用户要求删除一个共享结点时,若系统只是简单地将它删除,则当另一共享用户需要访问时,会因无法找到这个文件而发生错误。为此,可为每个共享结点设置一个共享计数器,每当图中增加对该结点的共享链时,计数器加1;每当某用户提出删除该结点时,计数器减1。仅当共享计数器为0时,才真正删除该结点,否则仅删除请求用户的共享链。共享文件(或目录)不同于文件拷贝(副本)。若有两个文件拷贝,则每个程序员看到的是拷贝而不是原件;然而,若一个文件被修改,则另一个程序员的拷贝不会改变。对于共享文件,只存在一个真正的文件,任何改变都会为其他用户所见。

无环图目录结构方便地实现了文件的共享,但使得系统的管理变得更加复杂。

文件共享

文件共享使多个用户(进程)共享同一个文件,系统中只需保留该文件的一个副本。若系统不能提供共享功能,则每个需要该文件的用户都要有各自的副本,会造成对存储空间的极大浪费。随着计算机技术的发展,文件共享的范围已由单机系统发展到多机系统,进而通过网络扩展到全球。这些文件的分享是通过分布式文件系统、远程文件系统、分布式信息系统实现的。这些系统允许多个客户通过C/S模型共享网络中的服务器文件。

现代常用的两种文件共享方法如下。

基于索引结点的共享方式(硬链接)

在树形结构的目录中,当有两个或多个用户要共享一个子目录或文件时,必须将共享文件或子目录链接到两个或多个用户的目录中,才能方便地找到该文件,如图4.7所示。

图4.7基于索引结点的共享方式找不到图片(Image not found)

在这种共享方式中,诸如文件的物理地址及其他的文件属性等信息,不再放在目录项中,而放在索引结点中。在文件目录中只设置文件名及指向相应索引结点的指针。在索引结点中还应有一个链接计数count,用于表示链接到本索引结点(即文件)上的用户目录项的数目。当count = 2时,表示有两个用户目录项链接到本文件上,或者说有两个用户共享此文件。

用户A创建一个新文件时,它便是该文件的所有者,此时将count置为1。用户B要共享此文件时,在用户B的目录中增加一个目录项,并设置一个指针指向该文件的索引结点。此时,文件主仍然是用户A,count=2。用户A不再需要此文件,不能将文件直接删除。因为若删除了该文件,则必然也删除了该文件的索引结点,这样便会使用户B的指针悬空,而用户B可能正在此文件上执行写操作,此时用户B会无法访问到文件。因此用户A不能删除此文件,只是将该文件的count减1,然后删除自己目录中的相应目录项。用户B仍可以使用该文件。当count =0时,表示没有用户使用该文件,系统将负责删除该文件。如图4.8给出了用户B链接到文件上的前、后情况。

图4.8文件共享中的链接计数找不到图片(Image not found)
利用符号链实现文件共享(软链接)

为使用户B能共享用户A的一个文件F,可以由系统创建一个LINK类型的新文件也取名为F,并将文件F写入用户B的目录中,以实现用户B的目录与文件F的链接。在新文件中只包含被链接文件F的路径名。这样的链接方法被称为符号链接。

新文件中的路径名只被视为符号链,当用户B要访问被链接的文件F且正要读LINK类新文件时,操作系统根据新文件中的路径名去读该文件,从而实现用户B对文件F的共享。

在利用符号链方式实现文件共享时,只有文件的拥有者才拥有指向其索引结点的指针而共享该文件的其他用户只有该文件的路径名,并不拥有指向其索引结点的指针。这样,也就不会发生在文件主删除一个共享文件后留下一个悬空指针的情况。当文件的拥有者把一个共享文件删除后,其他用户通过符号链去访问它时,会出现访问失败,于是将符号链删除,此时不会产生任何影响。当然,利用符号链实现文件共享仍然存在问题。例如,一个文件采用符号链方式共享,当文件拥有者将其删除,而在共享的其他用户使用其符号链接访问该文件之前,又有人在同一路径下创建了另一个具有同样名称的文件,则该符号链将仍然有效,但访问的文件已经改变,从而导致错误。

在符号链的共享方式中,当其他用户读共享文件时,需要根据文件路径名逐个地查找目录,直至找到该文件的索引结点。因此,每次访问时,都可能要多次地读盘,使得访问文件的开销变大并增加了启动磁盘的频率。此外,符号链的索引结点也要耗费一定的磁盘空间。

符号链方式有一个很大的优点,即网络共享只需提供该文件所在机器的网络地址及该机器中的文件路径。

上述两种链接方式都存在一个共同的问题,即每个共享文件都有几个文件名。换言之,每增加一条链接,就增加一个文件名。这实质上是每个用户都使用自己的路径名去访问共享文件。当我们试图去遍历整个文件系统时,将会多次遍历到该共享文件。

硬链接和软链接都是文件系统中的静态共享方法,在文件系统中还存在着另外的共享需求,即两个进程同时对同一个文件进行操作,这样的共享称为 $\color{green}{\text{动态共享}}$ 。

可以这样说:文件共享,“软”“硬”兼施。硬链接就是多个指针指向一个索引结点,保证只要还有一个指针指向索引结点,索引结点就不能删除;软链接就是把到达共享文件的路径记录下来,当要访问文件时,根据路径寻找文件。可以想象,硬链接的查找速度要比软链接的快。

文件保护

为了防止文件共享可能会导致文件被破坏或未经核准的用户修改文件,文件系统必须控制用户对文件的存取,即解决对文件的读、写、执行的许可问题。为此,必须在文件系统中建立相应的文件保护机制。

文件保护通过 $\color{green}{\text{口令保护}}$ 、 $\color{green}{\text{加密保护}}$ 和 $\color{green}{\text{访问控制}}$ 等方式实现。其中,口令保护和加密保护是为了防止用户文件被他人存取或窃取,而访问控制则用于控制用户对文件的访问方式。

访问类型

对文件的保护可从限制对文件的访问类型中出发。可加以控制的访问类型主要有以下几种。

  • 读。从文件中读。
  • 写。向文件中写。
  • 执行。将文件装入内存并执行。
  • 添加。将新信息添加到文件结尾部分。
  • 删除。删除文件,释放空间。
  • 列表清单。列出文件名和文件属性。

此外还可以对文件的重命名、复制、编辑等加以控制。这些高层的功能可以通过系统程序调用低层系统调用来实现。保护可以只在低层提供。例如,复制文件可利用一系列的读请求来完成,这样,具有读访问权限的用户同时也就具有了复制和打印权限。

访问控制

解决访问控制最常用的方法是根据用户身份进行控制。而实现基于身份访问的最为普通的方法是,为每个文件和目录增加一个 $\color{green}{\text{访问控制列表}}$ (Access-Control List,ACL),以规定每个用户名及其所允许的访问类型。

这种方法的优点是可以使用复杂的访问方法,缺点是长度无法预计并且可能导致复杂的空间管理,使用精简的访问列表可以解决这个问题。

精简的访问列表采用拥有者、组和其他三种用户类型。

1)拥有者。创建文件的用户。

2)组。一组需要共享文件且具有类似访问的用户。

3)其他。系统内的所有其他用户。这样,只需用三个域即可列出访问表中这三类用户的访问权限。文件拥有者在创建文件时,说明创建者用户名及所在的组名,系统在创建文件时也将文件主的名字、所属组名列在该文件的FCB中。用户访问该文件时,按照拥有者所拥有的权限访问文件,若用户和拥有者在同一个用户组,则按照同组权限访问,否则只能按其他用户权限访问。UNIX操作系统即采用此种方法。

口令和密码是另外两种访问控制方法。

口令指用户在建立一个文件时提供一个口令,系统为其建立FCB 时附上相应口令,同时告诉允许共享该文件的其他用户。用户请求访问时必须提供相应的口令。这种方法时间和空间的开销不多,缺点是口令直接存在系统内部,不够安全。

密码指用户对文件进行加密,文件被访问时需要使用密钥。这种方法保密性强,节省了存储空间,不过编码和译码要花费一定的时间。

口令和密码都是防止用户文件被他人存取或窃取,并没有控制用户对文件的访问类型。注意两个问题:

1)现代操作系统常用的文件保护方法是,将访问控制列表与用户、组和其他成员访问控制方案一起组合使用。

2)对于多级目录结构而言,不仅需要保护单个文件,而且需要保护子目录内的文件,即需要提供目录保护机制。目录操作与文件操作并不相同,因此需要不同的保护机制。

本节小结

本节开头提出的问题的参考答案如下。

1)什么是文件?什么是文件系统?

文件是以计算机硬盘为载体的存储在计算机上的信息集合,它的形式多样,可以是文本文档、图片、程序等。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:与文件管理有关的软件、被管理文件及实施文件管理所需的数据结构。

2)文件系统要完成哪些功能?

对于用户而言,文件系统最主要的功能是实现对文件的基本操作,让用户可以按名存储和查找文件,组织成合适的结构,并应当具有基本的文件共享和文件保护功能。对于操作系统本身而言,文件系统还需要管理与磁盘的信息交换,完成文件逻辑结构和物理结构上的变换,组织文件在磁盘上的存放,采取好的文件排放顺序和磁盘调度方法以提升整个系统的性能。

学习到这里时,读者应会有这样的一种体会:现代操作系统的管理思想中,到处能够见到面向对象程序设计的影子。本节我们学习的一个新概念————文件,实质上就是一个抽象数据类型,也就是一种 $\color{red}{\text{数据结构}}$ ,若读者在复习操作系统之前已复习完数据结构,则遇到一种新的数据结构时,一定会有这样的意识:要认识它的逻辑结构、物理结构,以及对这种数据结构的操作。本节我们已经学完文件的 $\color{green}{\text{逻辑结构}}$ ,下一节将介绍文件的实现,也就是文件的 $\color{green}{\text{物理结构}}$ 。操作系统对文件的操作不是本课程关心的问题,我们不去研究。

文件系统实现

在学习本节时,请读者思考以下问题:

1)在目录中查找某个文件可以使用什么方法?

2)文件的逻辑结构和物理结构有何区别?单个文件的逻辑结构和物理结构之间是否存在某些制约关系?上节介绍了目录和文件的逻辑结构,本节将介绍文件物理结构和目录的实现。建议读者阅读之前先回顾上节的内容,并自己思考相应功能的实现方法,在学习过程中和本节的方法进行对比,这样能更好地理解本节的内容。

图4.9文件系统层次结构找不到图片(Image not found)

文件系统层次结构

现代操作系统有多种文件系统类型(如FAT32,NTFS,ext2,ext3,ext4等),因此文件系统的层次结构也不尽相同。图4.9是一种合理的层次结构。

图4.9文件系统层次结构找不到图片(Image not found)
用户调用接口

文件系统为用户提供与文件及目录有关的调用,如新建、打开、读写、关闭、删除文件,建立、删除目录等。此层由若干程序模块组成,每个模块对应一条系统调用,用户发出系统调用时,控制即转入相应的模块。

文件目录系统

文件目录系统的主要功能是管理文件目录,其任务有管理活跃文件目录表、管理读写状态信息表、管理用户进程的打开文件表、管理与组织存储设备上的文件目录结构、调用下一级存取控制模块。

存取控制验证模块

实现文件保护主要由该级软件完成,它把用户的访问要求与FCB中指示的访问控制权限进行比较,以确认访问的合法性。

逻辑文件系统与文件信息缓冲区

逻辑文件系统与文件信息缓冲区的主要功能是,根据文件的逻辑结构将用户要读写的逻辑记录转换成文件逻辑结构内的相应块号。

物理文件系统

物理文件系统的主要功能是把逻辑记录所在的相对块号转换成实际的物理地址。

辅助分配模块

分配模块的主要功能是管理辅存空间,即负责分配辅存空闲空间和回收辅存空间。

设备管理程序模块

设备管理程序模块的主要功能是分配设备、分配设备读写用缓冲区、磁盘调度、启动设备、处理设备中断、释放设备读写缓冲区、释放设备等。

对于文件管理系统的层次结构我们不能忽略,因为它是重要考点之一,当然也不需要死记硬背,我们可以通过用户请求访问某个文件时发生的一系列事情来辅助记忆文件系统的层次结构。

例如,用户要查看文件F中的内容,对操作系统发出命令(操作系统有面向用户的接口),于是就经过了第О级的用户调用接口。

操作系统得到命令后,需要查找目录以查找文件F的索引信息,可能是FCB,也可能是索引结点,经过了第1级文件目录系统。

通过目录找到文件FCB后,需要查看文件FCB上的信息,看看那个用户有没有访问该文件的权限,于是经过了存取控制验证模块。

用户通过验证后,就真正开始寻址。经历第3章的学习后,我们有这样的意识:操作系统的寻址往往要先得到逻辑地址,再得到物理地址,于是在开始寻址时,操作系统经过逻辑文件系统与文件信息缓冲区,得到了相应文件的内容的逻辑地址。

把 $\color{green}{\text{逻辑地址}}$ 转换为 $\color{green}{\text{物理地址}}$ ,是在 $\color{green}{\text{物理文件系统}}$ 中完成的。

至此为止,寻址就已完成。寻址完成后,我们关心的是找到的这块空间应该如何管理,若要释放这块空间,则任务就交给辅助分配模块,若要把这块空间分配给设备用于输入/输出,则把任务交给设备管理程序模块。

目录实现

在读文件前,必须先打开文件。打开文件时,操作系统利用路径名找到相应目录项,目录项中提供了查找文件磁盘块所需要的信息。目录实现的基本方法有线性列表和哈希表两种,要注意目录的实现就是为了查找,因此线性列表实现对应线性查找,哈希表的实现对应散列查找。

线性列表

最简单的目录实现方法是使用存储文件名和数据块指针的线性表。创建新文件时,必须首先搜索目录表以确定没有同名的文件存在,然后在目录表后增加一个目录项。删除文件则根据给定的文件名搜索目录表,接着释放分配给它的空间。重用目录项有许多方法:可以将目录项标记为不再使用,或将它加到空闲目录项表上,还可以将目录表中的最后一个目录项复制到空闲位置,并降低目录表长度。采用链表结构可以减少删除文件的时间,其优点在于实现简单,不过由于线性表的特殊性,比较费时。

哈希表

哈希表根据文件名得到一个值,并返回一个指向线性列表中元素的指针。这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些预备措施来避免冲突。最大的困难是哈希表长度固定以及哈希函数对表长的依赖性。

目录查询是通过在磁盘上反复搜索完成的,需要不断地进行I/O操作,开销较大。所以如前所述,为了减少I/O操作,把当前使用的文件目录复制到内存,以后要使用该文件时只需在内存中操作,因此降低了磁盘操作次数,提高了系统速度。

文件实现—文件分配方式

前面说过,文件实际上是一种抽象数据类型,我们要研究它的逻辑结构、物理结构以及关于它的一系列操作(不是统考关注的内容)。文件的实现就是研究文件的物理结构,即文件数据在物理存储设备上是如何分布和组织的。同一个问题有两个方面的回答:一是文件的分配方式,讲的是对磁盘非空闲块的管理;二是文件存储空间管理,讲的是对磁盘空闲块的管理。

文件分配对应于文件的物理结构,是指如何为文件分配磁盘块。常用的磁盘空间分配方法有三种:连续分配、链接分配和索引分配。有的系统(如RDOS操作系统)对三种方法都支持,但更普遍的是一个系统只支持一种方法。对于本节的内容,读者要注意与文件的逻辑结构区分,从历年的经验来看,这是很多读者容易搞混的地方(读者复习完数据结构后,应该了解 $\color{green}{\text{线性表}}$ 、 $\color{green}{\text{顺序表}}$ 和 $\color{green}{\text{链表之间}}$ 的关系,类比到这里就不易混淆)。

连续分配

连续分配方法要求每个文件在磁盘上占有一组连续的块,如图4.10所示。磁盘地址定义了磁盘上的一个线性排序。这种排序使作业访问磁盘时需要的寻道数和寻道时间最小。

图4.10连续分配找不到图片(Image not found)

文件的连续分配可以用第一块的磁盘地址和连续块的数量来定义。若文件长 $n$ 块并从位置 $b$ 开始,则该文件将占有块b,b+ 1,b+2,…,b+n-1。一个文件的目录条目包括开始块的地址和该文件所分配区域的长度。

连续分配支持顺序访问和直接访问。其优点是实现简单、存取速度快。缺点是文件长度不宜动态增加,因为一个文件末尾后的盘块可能已分配给其他文件,一旦需要增加,就需要大量移动盘块。此外,反复增删文件后会产生外部碎片(与内存管理分配方式中的碎片相似),且很难确定一个文件需要的空间大小,因而只适用于 $\color{green}{\text{长度固定}}$ 的文件。

链接分配

链接分配采取离散分配的方式,消除了外部碎片,因此显著提高了磁盘空间的利用率;又因为根据文件的当前需求为其分配必需的盘块,当文件动态增长时,可以动态地再为它分配盘块,因此无须事先知道文件的大小。此外,对文件的增、删、改也非常方便。链接分配又可以分为隐式链接和显式链接两种形式。

隐式链接如图4.11所示。每个文件对应一个磁盘块的链表;磁盘块分布在磁盘的任何地方,除最后一个盘块外,每个盘块都有指向下一个盘块的指针,这些指针对用户是透明的。目录包括文件第一块的指针和最后一块的指针。

图4.11隐式链接分配找不到图片(Image not found)

创建新文件时,目录中增加一个新条目。每个目录项都有一个指向文件首块的指针。该指针初始化为NULL 以表示空文件,大小字段为0。写文件会通过空闲空间管理系统找到空闲块,将该块链接到文件的尾部,以便写入。读文件则通过块到块的指针顺序读块。

$\color{green}{\text{隐式链接}}$ 分配的缺点是无法直接访问盘块,只能通过指针顺序访问文件,且盘块指针会消耗一定的存储空间。隐式链接分配的稳定性也是一个问题,系统在运行过程中由于软件或硬件错误导致链表中的指针丢失或损坏,会导致文件数据的丢失。

$\color{green}{\text{显式链接}}$ 是指把用于链接文件各物理块的指针,从每个物理块的块末尾中提取出来,显式地存放在内存的一张链接表中。该表在整个磁盘中仅设置一张,称为文件分配表(File AllocationTable,FAT)。每个表项中存放对应块的下一块链接指针,即下一个盘块号。文件的第一个盘块号记录在目录中,后续的盘块可通过查FAT找到。例如,某磁盘共有100个磁盘块,存放了两个文件:文件“aa”占三个盘块,依次是2→8→5;文件“bbb”占两个盘块,依次是7→1。其余盘块都是空闲盘块,则该磁盘的FAT表如图4.12所示。

图4.12文件分配表找不到图片(Image not found)

不难看出,FAT的表项与全部磁盘块一一对应,并且可以用一个特殊的数字-1表示文件的最后一块,用-2表示这个磁盘块是空闲的(当然也可指定为-3,-4)。因此,文件分配表(FAT)不仅记录了文件各块之间的先后链接关系,同时还标记了空闲的磁盘块,操作系统也可以通过FAT对文件存储空间进行管理。当某进程请求操作系统分配一个磁盘块时,操作系统只需从FAT中找到-2的表项,并将对应的磁盘块分配给进程即可。

FAT表在系统启动时就会被读入内存,因此查找FAT的过程是在内存中进行的,因此不仅显著地提高了检索速度,而且明显减少了访问磁盘的次数。

索引分配

链接分配解决了连续分配的外部碎片和文件大小管理的问题。但是,链接分配不能有效支持直接访问(FAT 除外)。索引分配解决了这个问题,它把每个文件的所有的盘块号都集中放在一起构成索引块(表),如图4.13所示。

图4.13 索引分配找不到图片(Image not found)

每个文件都有其 $\color{green}{\text{索引块}}$ ,这是一个磁盘块地址的数组。索引块的第i个条目指向文件的第i个块。目录条目包括索引块的地址。要读第 i块,通过索引块的第 i个条目的指针来查找和读入所需的块。

创建文件时,索引块的所有指针都设为空。首次写入第i块时,先从空闲空间中取得一个块,再将其地址写到索引块的第i个条目。索引分配支持直接访问,且没有外部碎片问题。其缺点是由于索引块的分配,增加了系统存储空间的开销。索引块的大小是一个重要的问题,每个文件必须有一个索引块,因此索引块应尽可能小,但索引块太小就无法支持大文件。可以采用以下机制来处理这个问题。

  • 链接方案。一个索引块通常为一个磁盘块,因此它本身能直接读写。为了处理大文件,可以将多个索引块链接起来。
  • 多层索引。多层索引使第一层索引块指向第二层的索引块,第二层索引块再指向这种方法根据最大文件大小的要求,可以继续到第三层或第四层。例如,4096B 的块,能在索引块中存入1024个4B的指针。两层索引允许1048576个数据块,即允许最大文件为4GB。
  • 混合索引。将多种索引分配方式相结合的分配方式。例如,系统既采用直接地址单级索引分配方式或两级索引分配方式(混合索引是本章最综合的高频考点,可先学习本章疑难点4,然后回来接着学习)。

表4.2是三种分配方式的比较。

表4.2文件三种分配方式的比较找不到图片(Image not found)

此外,访问文件需要两次访问外存——首先要读取索引块的内容,然后访问具体的磁盘块,因而降低了文件的存取速度。为了解决这一-问题,通常将文件的索引块读入内存的缓冲区中,以加快文件的访问速度。

文件实现————文件存储空间管理

(1)文件存储器空间的划分与初始化

一般来说,一个文件存储在一个文件卷中。文件卷可以是物理盘的一部分,也可以是整个物理盘,支持超大型文件的文件卷也可由多个物理盘组成,如图4.14所示。

在一个文件卷中,文件数据信息的空间(文件区)和存放文件控制信息FCB的空间(目录区)是分离的。由于存在很多种类的文件表示和存放格式,所以现代操作系统中一般都有很多不同的文件管理模块,通过它们可以访问不同格式的逻辑卷中的文件。逻辑卷在提供文件服务前,必须由对应的文件程序进行初始化,划分好目录区和文件区,建立空闲空间管理表格及存放逻辑卷信息的 $\color{green}{\text{超级块}}$ 。

图4.14逻辑卷与物理盘的关系找不到图片(Image not found)

(2)文件存储器空间管理

文件存储设备分成许多大小相同的物理块,并以 $\color{green}{\text{块}}$ 为单位交换信息,因此,文件存储设备的管理实质上是对空闲块的组织和管理,它包括空闲块的组织、分配与回收等问题。

空闲表法

空闲表法属于连续分配方式,它与内存的动态分配方式类似,为每个文件分配一块连续的存储空间。系统为外存上的所有空闲区建立一张空闲盘块表,每个空闲区对应于一个空闲表项,其中包括表项序号、该空闲区第一个盘块号、该区的空闲盘块数等信息。再将所有空闲区按其起始盘块号递增的次序排列,如表4.3所示。

空闲盘区的分配与内存的动态分配类似,同样采用首次适应算法、循环首次适应算法等。例如,在系统为某新创建的文件分配空闲盘块时,先顺序地检索空闲盘块表的各表项,直至找到第一个其大小能满足要求的空闲区,再将该盘区分配给用户,同时修改空闲盘块表。

系统在对用户所释放的存储空间进行回收时,也采取类似于内存回收的方法,即要考虑回收区是否与空闲表中插入点的前区和后区相邻接,对相邻接者应予以合并。

表4.3空闲盘块表找不到图片(Image not found)
空闲链表法

将所有空闲盘区拉成一条空闲链,根据构成链所用的基本元素不同,可把链表分成两种形式:空闲盘块链和空闲盘区链。

空闲盘块链将磁盘上的所有空闲空间以盘块为单位拉成—条链。当用户因创建文件而请求分配存储空间时,系统从链首开始,依次摘下适当数目的空闲盘块分配给用户。当用户因删除文件而释放存储空间时,系统将回收的盘块依次插入空闲盘块链的末尾。这种方法的优点是分配和回收一个盘块的过程非常简单,但在为一个文件分配盘块时可能要重复多次操作。

空闲盘区链将磁盘上的所有空闲盘区(每个盘区可包含若干盘块)拉成一条链。在每个盘区上除含有用于指示下一个空闲盘区的指针外,还应有能指明本盘区大小(盘块数)的信息。分配盘区的方法与内存的动态分区分配类似,通常采用首次适应算法。在回收盘区时,同样也要将回收区与相邻接的空闲盘区合并。

位示图法

位示图利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。当其值为“0”时,表示对应的盘块空闲;当其值为“1”时,表示对应的盘块已分配。位示图法示意如图4.15所示。

图4.15位示图法示意图找不到图片(Image not found)

盘块的分配:

${\textstyle\unicode{x2460}}$ 顺序扫描位示图,从中找出一个或一组其值为“0”的二进制位。

②将找到的一个或一组二进制位,转换成与之对应的盘块号。若找到的其值为“0O”的二进制位位于位示图的第 $i$ 行、第 $j$ 列,则其相应的盘块号应按下式计算( $n$ 代表每行的位数):

③修改位示图,令map[ i,j ]= 1。盘块的回收:

①将回收盘块的盘块号转换成位示图中的行号和列号。转换公式为

②修改位示图,令map[ i,j ]=0。

成组链接法

空闲表法和空闲链表法都不适用于大型文件系统,因为这会使空闲表或空闲链表太大。在UNIX系统中采用的是成组链接法,这种方法结合了空闲表和空闲链表两种方法,克服了表太大的缺点。其大致思想是:把顺序的n个空闲扇区地址保存在第一个空闲扇区内,其后一个空闲扇区内则保存另一顺序空闲扇区的地址,如此继续,直至所有空闲扇区均予以链接。系统只需要保存一个指向第一个空闲扇区的指针。假设磁盘最初全为空闲扇区,其成组链接如图4.16所示。通过这种方式可以迅速找到大批空闲块地址。

图4.16成组链接法示意图找不到图片(Image not found)

表示文件存储器空闲空间的“位向量”表或第一个成组链块,以及卷中的目录区、文件区划分信息都需要存放在辅存储器中,一般放在卷头位置,在UNIX系统中称为超级块。在对卷中的文件进行操作前,超级块需要预先读入系统空闲的主存,并且经常保持主存超级块与辅存卷中超级块的一致性。

注意:本书如无特别提示,所使用的位示图法中行和列都从1开始编号。特别注意,若题目中指明从0开始编号,则上述计算方法要进行相应调整。

本节小结

本节开头提出的问题的参考答案如下。

在目录中查找某个文件可以使用什么方法?

可以采用线性列表法或哈希表法。线性列表把文件名组织成一个线性表,查找时依次与线性表中的每个表项进行比较。若把文件名按序排列,则使用折半查找法可以降低平均的查找时间,但建立新文件时会增加维护线性表的开销。哈希表用文件名通过哈希函数得到一个指向文件的指针,这种方法非常迅速,但要注意避免冲突。

文件的逻辑结构和物理结构有何区别?单个文件的逻辑结构和物理结构之间是否存在着某些制约关系?

文件的逻辑结构是用户可见的结构,即用户使用文件的结构。文件的物理结构是文件在存储器上的组织结构,它表示一个文件在辅存上安置、链接、编目的方法。它和文件的存取方法以及辅存设备的特性等都有着密切的联系。单个文件的逻辑结构和物理结构之间虽无明显的制约或关联关系,但是如果物理结构选择不慎,也很难体现出逻辑结构的特点,比如一个逻辑结构是顺序结构,而物理结构是隐式链接结构的文件,即使理论上可以很快找出某条记录的地址,而实际找时仍然需要在磁盘上一块一块地找。

磁盘组织与管理

在学习本节时,请读者思考以下问题:

1)在磁盘上进行一次读写操作需要哪几部分时间?其中哪部分时间最长?

2)存储一个文件时,当一个磁道存储不下时,剩下部分是存在同一个盘面的不同磁道好,还是存在同一个柱面上的不同盘面好?

本节主要介绍文件系统管理磁盘的方式,由于内容较少且属于实现部分,不需要问题来引导学习,因此本节不给出问题。学习本节时,要重点掌握计算一次磁盘操作的时间,以及对于给定访盘的磁道序列,按照特定算法求出磁头通过的总磁道数及平均寻道数。

磁盘的结构

磁盘(Disk)是由表面涂有磁性物质的金属或塑料构成的圆形盘片,通过一个称为磁头的导体线圈从磁盘存取数据。在读/写操作期间,磁头固定,磁盘在下面高速旋转。如图4.17所示,磁盘盘面上的数据存储在一组同心圆中,称为 $\color{green}{\text{磁道}}$ 。每个磁道与磁头一样宽,一个盘面有上千个磁道。磁道又划分为几百个扇区,每个扇区固定存储大小(通常为512B),一个 $\color{green}{\text{扇区}}$ 称为一个 $\color{green}{\text{盘块}}$ 。相邻磁道及相邻扇区间通过一定的间隙分隔开,以避免精度错误。注意,由于扇区按固定圆心角度划分,所以密度从最外道向里道增加,磁盘的存储能力受限于最内道的最大记录密度。

磁盘安装在一个磁盘驱动器中,它由磁头臂、用于旋转磁盘的主轴和用于数据输入/输出的电子设备组成。如图4.18所示,多个盘片垂直堆叠,组成 $\color{green}{\text{磁盘组}}$ ,每个盘面对应一个磁头,所有磁头固定在一起,与磁盘中心的距离相同且一起移动。所有盘片上相对位置相同的磁道组成柱面。按照这种物理结构组织,扇区就是磁盘可寻址的最小存储单位,磁盘地址用“柱面号·盘面号·扇区号(或块号)”表示。

图4.17磁盘盘片 图4.18 磁盘驱动器找不到图片(Image not found)

磁盘按不同的方式可分为若干类型:磁头相对于盘片的径向方向固定的,称为固定头磁盘,每个磁道一个磁头;磁头可移动的,称为活动头磁盘,磁头臂可来回伸缩定位磁道;磁盘永久固定在磁盘驱动器内的,称为固定盘磁盘;可移动和替换的,称为可换盘磁盘。

前面说过,操作系统中几乎每介绍一类资源及对这类资源的管理时,都要涉及一类调度算法。用户访问文件,需要操作系统的服务,文件实际上存储在磁盘中,操作系统接收用户的命令后,经过一系列的检验访问权限和寻址过程后,最终都会到达磁盘,控制磁盘把相应的数据信恳读出或修改。当有多个请求同时到达时,操作系统就要决定先为哪个请求服务,这就是磁盘调度算法
要解决的问题。

磁盘调度算法

一次磁盘读写操作的时间由寻找(寻道)时间、旋转延迟时间和传输时间决定。

1)寻找时间 $T_s$ ,。活动头磁盘在读写信息前,将磁头移动到指定磁道所需要的时间。这个时间除跨越 $n$ 条磁道的时间外,还包括启动磁臂的时间 $s$ ,即

式中,m是与磁盘驱动器速度有关的常数,约为0.2ms,磁臂的启动时间约为2ms。

2)旋转延迟时间 $T_s$ 。磁头 $\color{green}{\text{定位}}$ 到某一磁道的扇区所需要的时间,设磁盘的旋转速度为 $r$ ,则

对于硬盘,典型的旋转速度为5400转/分,相当于一周11.1ms,则 $T_r$ 为5.55ms;对于软盘,其旋转速度为300~600转/分,则 $T_r$ 为50~100ms。

3)传输时间 $T_t$ 。从磁盘读出或向磁盘写入数据所经历的时间,这个时间取决于每次所读/写的字节数 $b$ 和磁盘的旋转速度:

式中, $r$ 为磁盘每秒的转数, $N$ 为一个磁道上的字节数。

在磁盘存取时间的计算中,寻道时间与磁盘调度算法相关,下面将会介绍分析几种算法;而延迟时间和传输时间都与磁盘旋转速度相关,且为 $\color{green}{\text{线性相关}}$ ,所以在硬件上,转速是磁盘性能的一个非常重要的参数。

总平均存取时间 $T_a$ 可以表示为

虽然这里给出了总平均存取时间的公式,但是这个平均值是没有太大实际意义的,因为在实际的磁盘IO操作中,存取时间与磁盘调度算法密切相关。调度算法直接决定寻找时间从而决定总的存取时间。

目前常用的磁盘调度算法有以下几种。

先来先服务(First Come First Served,FCFS)算法

FCFS算法根据进程请求访问磁盘的先后顺序进行调度,这是一种最简单的调度算法,如图4.19所示。该算法的优点是具有公平性。若只有少量进程需要访问,且大部分请求都是访问簇聚的文件扇区,则有望达到较好的性能;若有大量进程竞争使用磁盘,则这种算法在性能上往往接近于随机调度。所以,实际磁盘调度中会考虑一些更为复杂的调度算法。

图4.19 FCFS磁盘调度算法找不到图片(Image not found)

例如,磁盘请求队列中的请求顺序分别为55,58,39,18,90,160,150,38,184,磁头的初始位置是磁道100,采用FCFS算法时磁头的运动过程如图4.19所示。磁头共移动了(45+3+19+21+72+70+10+112+146)= 498个磁道,平均寻找长度=498/9=55.3。

最短寻找时间优先( Shortest Seek Time First, SSTF)算法

SSTF算法选择调度处理的磁道是与当前磁头所在磁道距离最近的磁道,以便使每次的寻找时间最短。当然,总是选择最小寻找时间并不能保证平均寻找时间最小,但能提供比 FCFS算法更好的性能。这种算法会产生“饥饿”现象。如图4.20所示,若某时刻磁头正在18号磁道,而在18号磁道附近频繁地增加新的请求,则SSTF 算法使得磁头长时间在18号磁道附近工作,将使184号磁道的访问被无限期地延迟,即被“饿死”。

4.20 SSTF磁盘调度算法找不到图片(Image not found)

例如,磁盘请求队列中的请求顺序分别为55,58,39,18,90,160,150,38,184,磁头初始位置是磁道100,采用SSTF算法时磁头的运动过程如图4.20所示。磁头共移动了10+32+3+16+1+ 20+132+10+24= 248个磁道,平均寻找长度=248/9 = 27.5。

扫描(SCAN)算法(又称电梯调度算法)
  • scan求移动的磁道数的快速方法:找到「 $\color{green}{\text{枢纽}}$ 」,两边减去枢纽

SCAN算法在磁头当前移动方向上选择与当前磁头所在磁道距离最近的请求作为下一次服务的对象,实际上就是在最短寻找时间优先算法的基础上规定了磁头运动的方向,如图4.21所示。由于磁头移动规律与电梯运行相似,因此又称电梯调度算法。SCAN算法对最近扫描过的区域不公平,因此它在访问局部性方面不如 FCFS 算法和SSTF算法好。

图4.21SCAN磁盘调度算法找不到图片(Image not found)

例如,磁盘请求队列中的请求顺序分别为55,58, 39,18,90,160,150,38,184,磁头初始位置是磁道100。采用SCAN算法时,不但要知道磁头的当前位置,而且要知道磁头的移动方向,假设磁头沿磁道号增大的顺序移动,则磁头的运动过程如图4.21所示。移动磁道的顺序为100,150,160,184,200,90,58,55,39,38,18。磁头共移动了(50+10+24+16+110+32+3+16+1+20)=282个磁道,平均寻道长度=282/9= 31.33。

循环扫描( Circular SCAN, C-SCAN)算法

在扫描算法的基础上规定磁头单向移动来提供服务,回返时直接快速移动至起始端而不服务任何请求。由于SCAN算法偏向于处理那些接近最里或最外的磁道的访问请求,所以使用改进型的C-SCAN 算法来避免这个问题,如图4.22所示。

采用SCAN算法和C-SCAN 算法时,磁头总是严格地遵循从盘面的一端到另一端,显然,在实际使用时还可以改进,即磁头移动只需要到达最远端的一个请求即可返回,不需要到达磁盘端点。这种形式的SCAN算法和C-SCAN算法称为LOOK调度(见图4.23-1)和C-LOOK(见图4.23-2)调度,因为它们在朝一个给定方向移动前会查看是否有请求。

4.22 C-SCAN磁盘调度算法找不到图片(Image not found)
4.23-1 LOOK磁盘调度算法找不到图片(Image not found)

注意,若无特别说明,也可以默认SCAN算法和C-SCAN算法为LOOK和C-LOOK调度(请读者认真领悟,并通过结合后面的习题进一步加深对以上相关算法的理解)。

4.23-2C-LOOK磁盘调度算法找不到图片(Image not found)

例如,磁盘请求队列中的请求顺序为55,58,39,18,90,160,150,38,184,磁头初始位置是磁道100。采用C-SCAN算法时,假设磁头沿磁道号增大的顺序移动,则磁头的运动过程如图4.23所示。移动磁道的顺序为100,150,160,184,200,0,18,38,39,55,58,90。磁头共移动50+10+24+16+200+ 18+20+1+16+3+32=390个磁道,平均寻道长度=390/9= 43.33。

不太熟悉操作系统整体框架的读者经常混淆磁盘调度算法中的循环扫描算法和页面调度算法中的CLOCK算法,请读者注意区分。

对比以上几种磁盘调度算法,FCFS 算法太过简单, $\color{green}{\text{性能较差}}$ ,仅在请求队列长度接近于1时才较为理想;SSTF 算法较为通用和自然;SCAN算法和C-SCAN算法在磁盘 $\color{green}{\text{负载较大}}$ 时比较占优势。它们之间的比较见表4.4。

表4.4磁盘调度算法比较找不到图片(Image not found)

除减少寻找时间外,减少延迟时间也是提高磁盘传输效率的重要因素。可以对盘面扇区进行交替编号,对磁盘片组中的不同盘面错位命名。假设每个盘面有8个扇区,磁盘片组共8个盘面,则可以采用如图4.24所示的编号。

磁盘是连续自转设备,磁头读/写一个物理块后,需要经过短暂的处理时间才能开始读/马下一块。假设逻辑记录数据连续存放在磁盘空间中,若在盘面上按扇区交替编号连续存放,则连续读/写多条记录时能减少磁头的延迟时间;同柱面不同盘面的扇区若能错位编号,连续读/写相邻两个盘面的逻辑记录时也能减少磁头延迟时间。

图4.24 磁盘片组扇区编号找不到图片(Image not found)

以图4.24为例,在随机扇区访问情况下,定位磁道中的一个扇区平均需要转过4个扇区,这时,延迟时间是传输时间的4倍,这是一种非常低效的存取方式。理想化的情况是不需要定位而直接连续读取扇区,没有延迟时间,这样磁盘数据存取效率可以成倍提高。但由于读取扇区的顺序是不可预测的,所以延迟时间不可避免。图4.24中的编号方式是读取连续编号扇区时的一种方法。

磁盘寻块时间分为三个部分,即寻道时间、延迟时间和传输时间,寻道时间和延迟时间属于“找”的时间,凡是“找”的时间都可以通过一定的方法削减,但传输时间是磁盘本身性质所决定的,不能通过一定的措施减少。

磁盘的管理

磁盘初始化

一个新的磁盘只是一个含有磁性记录材料的空白盘。在磁盘能存储数据之前,它必须分成扇区以便磁盘控制器能进行读和写操作,这个过程称为 $\color{green}{\text{低级格式化}}$ (物理分区)。低级格式化为磁盘的每个扇区采用特别的数据结构。每个扇区的数据结构通常由 $\color{green}{\text{头}}$ 、 $\color{green}{\text{数据区域}}$ (通常为512B大小)和 $\color{green}{\text{尾部}}$ 组成。头部和尾部包含了一些磁盘控制器所使用的信息。

为了使用磁盘存储文件,操作系统还需要将自己的数据结构记录在磁盘上:第一步将磁盘分为由一个或多个柱面组成的 $\color{green}{\text{分区}}$ (即我们熟悉的C盘、D盘等形式的分区);第二步对物理分区进行 $\color{green}{\text{逻辑格式化}}$ (创建文件系统),操作系统将初始的文件系统数据结构存储到磁盘上,这些数据结构包括空闲和已分配的空间及一个初始为空的目录。

引导块

计算机启动时需要运行一个初始化程序( $\color{green}{\text{自举程序}}$ ),它初始化CPU、寄存器、设备控制器和内存等,接着启动操作系统。为此,该自举程序应找到磁盘上的操作系统内核,装入内存,并转到起始地址,从而开始操作系统的运行。

自举程序通常保存在ROM 中,为了避免改变自举代码而需要改变ROM硬件的问题,因此只在ROM中保留很小的自举装入程序,将完整功能的自举程序保存在磁盘的启动块上,启动块位于磁盘的固定位。拥有启动分区的磁盘称为启动磁盘或系统磁盘。

坏块

由于磁盘有移动部件且容错能力弱,因此容易导致一个或多个扇区损坏。部分磁盘甚至从出厂时就有坏扇区。根据所使用的磁盘和控制器,对这些块有多种处理方式。

对于简单磁盘,如电子集成驱动器(IDE),坏扇区可手工处理,如 MS-DOS的Format 命令执行逻辑格式化时便会扫描磁盘以检查坏扇区。坏扇区在FAT表上会标明,因此程序不会使用。

对于复杂的磁盘,如小型计算机系统接口(SCSI),其控制器维护一个磁盘坏块链表该链表在出厂前进行低级格式化时就已初始化,并在磁盘的整个使用过程中不断更新。低级格式化将一些块保留作为备用,对操作系统透明。控制器可用备用块来逻辑地替代坏块,这种方案称为 $\color{green}{\text{扇区备用}}$ 。

对坏块的处理实质上就是用某种机制,使系统不去使用坏块。坏块属于硬件故障,操作系统是不能修复坏块的。

本节小结

本节开头提出的问题的参考答案如下。

在磁盘上进行一次读写操作需要哪几部分时间?其中哪部分时间最长?

在磁盘上进行一次读写操作花费的时间由寻道时间、延迟时间和传输时间决定。其中寻道时间是将磁头移动到指定磁道所需要的时间,延迟时间是磁头定位到某一磁道的扇区(块号)所需要的时间,传输时间是从磁盘读出或向磁盘写入数据所经历的时间。一般来说, $\color{green}{\text{寻道时间}}$ 因为要移动磁臂,所以占用时间最长。

存储一个文件时,当一个磁道存储不下时,剩下部分是存在同一个盘面的不同磁道好,还是存在同一个柱面上的不同盘面好?

上一问已经说到,寻道时间对于一次磁盘访问的影响是最大的,若存在同一个盘面的不同磁道,则磁臂势必要移动,这样会大大增加文件的访问时间,而存在同一个柱面上的不同盘面就不需要移动磁道,所以一般情况下存在同一个柱面上的不同盘面更好。

本章疑难点

磁盘结构

引导控制块(Boot Control Block)包括系统从该分区引导操作系统所需要的信息。若磁盘没有操作系统,则这块的内容为空。它通常为分区的第一块。UFS称为引导块(Boot Block);NTFS称为分区引导扇区(Partition Boot Sector)。

分区控制块(Partition Control Block)包括分区详细信息,如分区的块数、块的大小、空闲块的数量和指针、空闲FCB 的数量和指针等。UFS称为超级块(Super Block),而NTFS称为主控文件表(Master File Table)。

内存结构

内存分区表包含所有安装分区的信息。

内存目录结构用来保存近来访问过的目录信息。对安装分区的目录,可以包括一个指向分区表的指针。

系统范围的打开文件表,包括每个打开文件的FCB复制和其他信息。

单个进程的打开文件表,包括一个指向系统范围内已打开文件表中合适条目和其他信息的指针。

文件系统实现概述

为了创建一个文件,应用程序调用逻辑文件系统。逻辑文件系统知道目录结构形式,它将为文件分配一个新的 FCB,把相应目录读入内存,用新的文件名更新该目录和FCB,并将结果写回磁盘。图4.25显示了一个典型的FCB。

图4.25典型的FCB找不到图片(Image not found)

一旦文件被创建,它就能用于IO,不过首先要打开文件。调用open将文件名传给文件系统,文件系统根据给定文件名搜索目录结构。部分目录结构通常缓存在内存中以加快目录操作。找到文件后,其FCB复制到系统范围的打开文件表。该表不但存储FCB,而且存储打开该文件的进程数量的条目。

然后,单个进程的打开文件表中会增加一个条目,并通过指针将系统范围的打开文件表的条目与其他域(文件当前位置的指针和文件打开模式等)相连。调用open返回的是一个指向单个进程的打开文件表中合适条目的指针,所以文件操作都是通过该指针进行的。

文件名不必是打开文件表的一部分,因为一旦完成对FCB在磁盘上的定位,系统就不再使用文件名。对于访问打开文件表的索引,UNIX称之为文件描述符(File Descriptor),而Windows2000称之为文件句柄(Fiie Handle)。因此,只要文件未被关闭,所有文件操作就通过打开文件表来进行。

当一个进程关闭文件时,就会删除单个进程打开文件表中的一个相应条目,即目录项,系统范围内打开文件表的打开数也会递减。当打开文件的所有用户都关闭了一个文件时,更新的文件信息会复制到磁盘的目录结构中,系统范围的打开文件表的条目也将删除。

在实际中,系统调用open 时会首先搜索系统范围的打开文件表,以确定某文件是否已被其他进程所使用。如果是,就在单个进程的打开文件表中创建一项,并指向现有系统范围的打开文件表的相应条目。该算法在文件已打开时,能节省大量开销。

混合索引分配的实现

混合索引分配已在UNIX系统中采用。在UNIX System V的索引结点中,共设置了13个地址项,即 iaddr(0)~iaddr(12),如图4.26所示。在BSD UNIX 的索引结点中,共设置了13个地址项,它们都把所有的地址项分成两类,即直接地址和间接地址。

(1)直接地址

为了提高对文件的检索速度,在索引结点中可设置10个直接地址项,即用iaddr(O)一iaddr(9)来存放直接地址。换言之,这里每项中所存放的是该文件数据所在盘块的盘块号。假如每个盘块的大小为4KB,当文件不大于40KB时,便可直接从索引结点中读出该文件的全部盘块号。

(2)一次间接地址

对于大、中型文件,只采用直接地址并不现实。可再利用索引结点中的地址项iaddr(10)来提供一次间接地址。这种方式的实质就是一级索引分配方式。图中的一次间址块也就是索引块,系统将分配给文件的多个盘块号记入其中。在一次间址块中可存放1024个盘块号,因而允许文件长达 4MB。

(3)多次间接地址

当文件长度大于4MB+40KB(一次间接地址与10个直接地址项)时,系统还须采用二次间接地址分配方式。这时,用地址项iaddr(11)提供二次间接地址。该方式的实质是两级索引分配方式。系统此时在二次间接地址块中记入所有一次间接地址块的盘号。在采用二次间接地址方式时,文件的最大长度可达4GB。同理,地址项iaddr(12)作为三次间接地址,其允许的文件最大长度可达4TB。

4.26UNIX系统的inode结构示意图找不到图片(Image not found)

最后,我们对本章内容再进行一次宏观上的把握。贯穿本章内容的有两条主线:第一条主线是介绍一种新的抽象数据类型、文件,从逻辑结构和物理结构两个方面进行;第二条主线是操作系统是如何管理“文件”这种数据结构的,介绍了多文件的逻辑结构的组织,即目录,还介绍了如何处理用户对文件的服务请求,即磁盘管理。但宏观认识是远远不够的,从宏观上把握知识的目的是从微观上更加准确地掌控细微知识点,在考试中得到好成绩。读者要通过反复做题、对答案,不断加深自己对知识点的认知程度。

输入/输出(I/O)管理

【考纲内容】
(一)I/O管理基础

  1. 设备(设备的基本概念,设备的分类,I/O接口,I/O端口)
  2. I/O控制方式(轮询方式、中断驱动方式、DMA方式)
  3. I/O 软件层次结构(用户层I/O、设备独立性软件、设备驱动层、中断处理层、硬件层)
  4. $\color{red}{\text{输入/输出}}$ 应用程序接口(字符设备接口,块设备接口,网络设备接口,阻塞/非阻塞I/O)

(二)设备独立软件

  1. 缓冲区管理
  2. 设备分配与回收
  3. 假脱机技术(SPOOLing)
  4. $\color{red}{\text{设备驱动程序接口}}$

(三)外存管理

  1. 磁盘(磁盘结构,格式化,分区,磁盘调度方法)
  2. $\color{red}{\text{固态硬盘}}$ (读写性能特性,磨损均衡)
  • 概述
    • I/O设备分类
    • I/O控制方式—程序直接控制、中断驱动方式、DMA方式、通道方式
    • I/O层次结构——用户层I/O、设备独立性软件、设备驱动层、中断处理层、硬件层
  • 缓冲区
    • 单缓冲
    • 双缓冲
    • 循环缓冲
    • 缓冲池
    • 缓冲区与高速缓存的对比
  • 设备分配
    • 概述
      • 独占设备————独占式使用
      • 共享设备————分时式共享
      • 虚拟设备————SPOOLing方式
    • 数据结构————DCT、COCT、CHCT、SDT
    • 策略————静态分配、动态分配
    • 逻辑设备名到物理设备名的映射
  • SPOOLing系统(虚拟设备技术)————组成、实例

【复习提示】

本章的内容较为分散,重点掌握的内容是I/O设备的基本特性、I/O子系统的特性、三种IO控制方式、高速缓存与缓冲区、SPOOLing 技术。本章的知识点很多,如I/O方式、设备控制等内容与硬件直接相关,建议结合计算机组成原理中的对应章节一起复习。本章内容与组成原理中的交叉较多,很多考点既可作为本章的考点,又可作为组成原理中的考点,因此还未复习组成原理的读者需要清楚地把握本章的每个知识点,为组成原理的学习打下基础,已复习过组成原理的读者遇到比较熟悉的内容时可以跳过,学习本章中组成原理未涉及的部分即可。另外,未复习过组成原理的读者可能会觉得本章的习题较难,但不需要担心。

本章的内容历年来在统考题目中所占的比例不大,若统考中出现本章的题目,则基本上可以断定一定非常简单,看过相关内容的读者就一定会做,而未看过的读者基本上只能靠“蒙”。考研成功的秘诀是复习要反复多次并全面,偷工减料是要吃亏的,希望读者重视本章的内容。

I/O管理概述

学习本章时,可与计算机组成原理的相关知识相结合,并思考I/O管理要完成哪些功能。

I/O设备

I/O设备管理是操作系统设计中最凌乱也最具挑战性的部分。由于它包含了很多领域的不同设备及与设备相关的应用程序,因此很难有一个通用且一致的设计方案。所以在理解设备管理之前,应该先了解具体的IO设备类型。

计算机系统中的IO设备按使用特性可分为以下类型:

1) $\color{green}{\text{人机交互类外部设备}}$ 。用于与计算机用户之间交互的设备,如打印机、显示器、鼠标、键盘等。这类设备的数据交换速度相对较慢,通常是以字节为单位进行数据交换的。

2) $\color{green}{\text{存储设备}}$ 。用于存储程序和数据的设备,如磁盘、磁带、光盘等。这类设备用于数据交换,速度较快,通常以多字节组成的块为单位进行数据交换。

3) $\color{green}{\text{网络通信设备}}$ 。用于与远程设备通信的设备,如各种网络接口、调制解调器等。其速度介于前两类设备之间。网络通信设备在使用和管理上与前两类设备也有很大不同。

除了上面最常见的分类方法,IO设备还可以按以下方法分类。

按传输速率分类

1) $\color{red}{\text{低速设备}}$ 。传输速率仅为每秒几字节到数百字节的一类设备,如 $\color{green}{\text{键盘}}$ 、 $\color{green}{\text{鼠标}}$ 等。

2) $\color{red}{\text{中速设备}}$ 。传输速率为每秒数千字节至数万字节的一类设备,如 $\color{green}{\text{行式打印机}}$ 、 $\color{green}{\text{激光打印机}}$ 等。

3) $\color{red}{\text{高速设备}}$ 。传输速率在数百千字节至千兆字节的一类设备,如 $\color{green}{\text{磁带机}}$ 、 $\color{green}{\text{磁盘机}}$ 、 $\color{green}{\text{光盘机}}$ 等。

按信息交换的单位分类

1) $\color{red}{\text{块设备}}$ 。由于信息的存取总是以数据块为单位的,所以存储信息的设备称为块设备。它属于有结构设备,如 $\color{green}{\text{磁盘}}$ 等。磁盘设备的基本特征是传输速率较高、可寻址,即对它可随机地读/写任一块。

2) $\color{red}{\text{字符设备}}$ 。用于数据输入/输出的设备为字符设备,因为其传输的基本单位是字符。它属于无结构类型,如 $\color{green}{\text{交互式终端机}}$ 、 $\color{green}{\text{打印机}}$ 等。它们的基本特征是传输速率低、不可寻址,并且在输入/输出时常采用中断驱动方式。

I/O控制方式

设备管理的主要任务之一是控制设备和内存或处理机之间的数据传送。外围设备和内存之间的输入/输出控制方式有4种,下面分别加以介绍。

程序直接控制方式

如图5.1(a)所示,计算机从外部设备读取数据到存储器,每次读一个字的数据。对读入的每个字,CPU需要对外设状态进行循环检查,直到确定该字已经在IO控制器的数据寄存器中。在程序直接控制方式中,由于CPU的高速性和IO设备的低速性,致使CPU的绝大部分时间都处于等待IO设备完成数据IO的循环测试中,造成了CPU资源的极大浪费。在该方式中,CPU之所以要不断地测试IO设备的状态,就是因为在CPU中未采用中断机构,使IO设备无法向CPU报告它已完成了一个字符的输入操作。

程序直接控制方式虽然简单且易于实现,但其缺点也显而易见,由于CPU和I/O设备只能串行工作,导致CPU的利用率相当低。

中断驱动方式

中断驱动方式的思想是,允许IO设备主动打断CPU的运行并请求服务,从而“解放”CPU,使得其向I/O控制器发送读命令后可以继续做其他有用的工作。如图5.1(b)所示,我们从IO控制器和CPU两个角度分别来看中断驱动方式的工作过程。

图5.1 I/O控制方式找不到图片(Image not found)

从I/O控制器的角度来看,I/O控制器从CPU接收一个读命令,然后从外围设备读数据。一旦数据读入该I/O控制器的数据寄存器,便通过控制线给CPU发出一个中断信号,表示数据已准备好,然后等待CPU请求该数据。I/O控制器收到CPU 发出的取数据请求后,将数据放到数据总线上,传到CPU的寄存器中。至此,本次I/O操作完成,IO控制器又可开始下一次I/O操作。

从CPU的角度来看,CPU 发出读命令,然后保存当前运行程序的上下文(现场,包括程序计数器及处理机寄存器),转去执行其他程序。在每个指令周期的末尾,CPU 检查中断。当有来自I/O控制器的中断时,CPU保存当前正在运行程序的上下文,转去执行中断处理程序以处理该中断。这时,CPU 从IO控制器读一个字的数据传送到寄存器,并存入主存。接着,CPU恢复发出1O命令的程序(或其他程序)的上下文,然后继续运行。

中断驱动方式比程序直接控制方式有效,但由于数据中的每个字在存储器与I/O控制器之间的传输都必须经过CPU,这就导致了中断驱动方式仍然会消耗较多的CPU时间。

DMA方式

在中断驱动方式中,I/O 设备与内存之间的数据交换必须要经过CPU中的寄存器,所以速度还是受限,而DMA(直接存储器存取)方式的基本思想是在IO设备和内存之间开辟直接的数据交换通路,彻底“解放”CPU。DMA方式的特点如下:

1)基本单位是数据块。

2)所传送的数据,是从设备直接送入内存的,或者相反。

3)仅在传送一个或多个数据块的开始和结束时,才需CPU干预,整块数据的传送是在DMA控制器的控制下完成的。

图5.2列出了DMA控制器的组成。

图5.2 DMA控制器的组成找不到图片(Image not found)

要在主机与控制器之间实现成块数据的直接交换,须在DMA控制器中设置如下4类寄存器:1)命令/状态寄存器(CR)。用于接收从CPU 发来的I/O命令或有关控制信息,或设备的状态。

2)内存地址寄存器(MAR)。在输入时,它存放把数据从设备传送到内存的起始目标地址;在输出时,它存放由内存到设备的内存源地址。

3)数据寄存器(DR)。用于暂存从设备到内存或从内存到设备的数据。

4)数据计数器(DC)。存放本次要传送的字(节)数。

如图5.1(c)所示,DMA方式的工作过程是:CPU 接收到IO设备的DMA请求时,它给TO控制器发出一条命令,启动DMA 控制器,然后继续其他工作。之后CPU就把控制操作委托给DMA控制器,由该控制器负责处理。DMA 控制器直接与存储器交互,传送整个数据块,每次传送一个字,这个过程不需要CPU参与。传送完成后,DMA 控制器发送一个中断信号给处理器。因此只有在传送开始和结束时才需要CPU的参与。

DMA 控制方式与中断驱动方式的主要区别是,中断驱动方式在每个数据需要传输时中断CPU,而DMA控制方式则是在所要求传送的一批数据全部传送结束时才中断CPU;此外,中断驱动方式数据传送是在中断处理时由CPU控制完成的,而 DMA 控制方式则是在 DMA控制器的控制下完成的。

通道控制方式

I/O通道是指专门负责输入/输出的处理机。I/O通道方式是DMA方式的发展,它可以进一步减少CPU的干预,即把对一个数据块的读(或写)为单位的干预,减少为对一组数据块的读(或写)及有关控制和管理为单位的干预。同时,又可以实现CPU、通道和IO设备三者的并行操作,从而更有效地提高整个系统的资源利用率。

例如,当CPU要完成一组相关的读(或写)操作及有关控制时,只需向IO通道发送一条I/O指令,以给出其所要执行的通道程序的首地址和要访问的IO设备,通道接到该指令后,执行通道程序便可完成CPU指定的IO任务,数据传送结束时向CPU 发中断请求。

IO通道与一般处理机的区别是:通道指令的类型单一,没有自己的内存,通道所执行的通道程序是放在主机的内存中的,也就是说通道与CPU $\color{green}{\text{共享内存}}$ 。

IO通道与DMA方式的区别是:DMA方式需要CPU来控制传输的数据块大小、传输的内存位置,而通道方式中这些信息是由通道控制的。另外,每个 DMA控制器对应一台设备与内存传递数据,而一个通道可以控制多台设备与内存的数据交换。

下面用一个例子来总结以上4种IO控制方式。想象一位客户要去裁缝店做一批衣服的情形。采用程序直接控制时,裁缝没有客户的联系方式,客户必须每隔一段时间去裁缝店看看裁缝把衣服做好了没有,这就浪费了客户不少的时间。

采用中断驱动方式时,裁缝有客户的联系方式,每当他完成一件衣服后,给客户打一个电话,让客户去拿,与程序直接控制能省去客户不少麻烦,但每完成一件衣服就让客户去拿一次,仍然比较浪费客户的时间。

采用DMA方式时,客户花钱雇一位单线秘书,并向秘书交代好把衣服放在哪里(存放仓库),裁缝要联系就直接联系秘书,秘书负责把衣服取回来并放在合适的位置,每处理完100件衣服,秘书就要给客户报告一次(大大节省了客户的时间)。

采用通道方式时,秘书拥有更高的自主权,与 DMA方式相比,他可以决定把衣服存放在哪里,而不需要客户操心。而且,何时向客户报告,是处理完100件衣服就报告,还是处理完10000件衣服才报告,秘书是可以决定的。客户有可能在多个裁缝那里订了货,一位DMA类的秘书只能负责与一位裁缝沟通,但通道类秘书却可以与多名裁缝进行沟通。

I/O子系统的层次结构

IO 软件涉及的面非常广,往下与硬件有着密切的联系,往上又与用户直接交互,它与进程管理、存储器管理、文件管理等都存在着一定的联系,即它们都可能需要IO软件来实现IO操作。

为了使复杂的IO软件具有清晰的结构、良好的可移植性和适应性,在IO软件中普遍采用了层次式结构,将系统输入/输出功能组织成一系列的层次,每层都利用其下层提供的服务,完成输入/输出功能中的某些子功能,并屏蔽这些功能实现的细节,向高层提供服务。在层次式结构的I/O软件中,只要层次间的接口不变,对某一层次中的软件的修改都不会引起其下层或高层代码的变更,仅最低层才涉及硬件的具体特性。

一个比较合理的层次划分如图5.3所示。整个IO系统可以视为具有4个层次的系统结构,各层次及其功能如下:

图5.3 I/O层次结构找不到图片(Image not found)

1) $\color{red}{\text{用户层IO软件}}$ 。实现与用户交互的接口,用户可直接调用在用户层提供的、与I/O操作有关的 $\color{green}{\text{库函数}}$ ,对设备进行操作。

一般而言,大部分的I/O软件都在操作系统内部,但仍有一小部分在用户层,包括与用户程序链接在一起的库函数,以及完全运行于内核之外的一些程序。用户层软件必须通过一组系统调用来获取操作系统服务。

2) $\color{red}{\text{设备独立性软件}}$ 。用于实现用户程序与设备驱动器的统一接口、设备命令、设备保护及设备分配与释放等,同时为设备管理和数据传送提供必要的存储空间。

设备独立性也称设备无关性,使得应用程序独立于具体使用的物理设备。为实现设备独立性而引入了逻辑设备和物理设备这两个概念。在应用程序中,使用逻辑设备名来请求使用某类设备;而在系统实际执行时,必须将逻辑设备名映射成物理设备名使用。使用逻辑设备名的好处是:①增加设备分配的灵活性;②易于实现IO重定向,所谓IO重定向,是指用于IO操作的设备可以更换(即重定向),而不必改变应用程序。

为了实现设备独立性,必须再在驱动程序之上设置一层设备独立性软件。总体而言,设备独立性软件的主要功能可分为以下两个方面:

①执行所有设备的公有操作。包括:对设备的分配与回收;将逻辑设备名映射为物理设备名;对设备进行保护,禁止用户直接访问设备;缓冲管理;差错控制;提供独立于设备的大小统一的逻辑块,屏蔽设备之间信息交换单位大小和传输速率的差异。

②向用户层(或文件层)提供统一接口。无论何种设备,它们向用户所提供的接口应是相同的。例如,对各种设备的读/写操作,在应用程序中都统一使用read/write命令等。

3) $\color{red}{\text{设备驱动程序}}$ 。与硬件直接相关,负责具体实现系统对设备发出的操作指令,驱动IO设备工作的驱动程序。

通常,每类设备配置一个设备驱动程序,它是I/O进程与设备控制器之间的通信程序,常以进程形式存在。设备驱动程序向上层用户程序提供一组标准接口,设备具体的差别被设备驱动程序所封装,用于接收上层软件发来的抽象IO要求,如read和 write命令,转换为具体要求后,发送给设备控制器,控制IO设备工作;它也将由设备控制器发来的信号传送给上层软件,从而为IO内核子系统隐藏设备控制器之间的差异。

4) $\color{red}{\text{中断处理程序}}$ 。用于保存被中断进程的CPU环境,转入相应的中断处理程序进行处理,处理完并恢复被中断进程的现场后,返回到被中断进程。

中断处理层的主要任务有:进行进程上下文的切换,对处理中断信号源进行测试,读取设备状态和修改进程状态等。由于中断处理与硬件紧密相关,对用户而言,应尽量加以屏蔽,因此应放在操作系统的底层,系统的其余部分尽可能少地与之发生联系。

5) $\color{red}{\text{硬件设备}}$ 。I/O设备通常包括一个机械部件和一个电子部件。为了达到设计的模块性和通用性,一般将其分开:电子部件称为设备控制器(或适配器),在个人计算机中,通常是一块插入主板扩充槽的印制电路板;机械部件则是设备本身。

设备控制器通过寄存器与CPU通信,在某些计算机上,这些寄存器占用内存地址的一部分,称为内存映像IO;另一些计算机则采用IO专用地址,寄存器独立编址。操作系统通过向控制器寄存器写命令字来执行IO功能。控制器收到一条命令后,CPU可以转向进行其他工作,而让设备控制器自行完成具体的I/O操作。当命令执行完毕后,控制器发出一个中断信号,操作系统重新获得CPU的控制权并检查执行结果,此时,CPU仍旧从控制器寄存器中读取信息来获得执行结果和设备的状态信息。

设备控制器的主要功能如下:

1)接收和识别CPU或通道发来的命令,如磁盘控制器能接收读、写、查找等命令。

2)实现数据交换,包括设备和控制器之间的数据传输;通过数据总线或通道,控制器和主存之间的数据传输。

3)发现和记录设备及自身的状态信息,供CPU处理使用。

4)设备地址识别。

为实现上述功能,设备控制器(见图5.4)必须包含以下组成部分:

① $\color{green}{\text{设备控制器与CPU的接口}}$ 。该接口有三类信号线:数据线、地址线和控制线。数据线通常与两类寄存器相连:数据寄存器(存放从设备送来的输入数据或从CPU送来的输出数据)和控制/状态寄存器(存放从CPU送来的控制信息或设备的状态信息)。

② $\color{green}{\text{设备控制器与设备的接口}}$ 。设备控制器连接设备需要相应数量的接口,一个接口连接一台设备。每个接口中都存在数据、控制和状态三种类型的信号。

${\textstyle\unicode{x2462}}$ $\color{green}{\text{IO控制逻辑}}$ 。用于实现对设备的控制。它通过一组控制线与CPU交互,对从CPU收到的I/O命令进行译码。CPU启动设备时,将启动命令发送给控制器,同时通过地址线把地址发送给控制器,由控制器的IO逻辑对地址进行译码,并相应地对所选设备进行控制。

图5.4设备控制器的组成找不到图片(Image not found)

类似于文件系统的层次结构,IO子系统的层次结构也是我们需要记忆的内容,但记忆不是死记硬背,我们以用户对设备的一次命令来总结各层次的功能,帮助各位读者记忆。

例如,当用户要读取某设备的内容时,通过操作系统提供的read命令接口,这就经过了用户层。

操作系统提供给用户使用的接口,一般是统一的通用接口,也就是几乎每个设备都可以响应的统一命令,如read 命令,用户发出的read命令,首先经过设备独立层进行解析,然后交往下层。

接下来,不同类型的设备对read命令的行为会有所不同,如磁盘接收read 命令后的行为与打印机接收read命令后的行为是不同的。因此,需要针对不同的设备,把read 命令解析成不同的指令,这就经过了设备驱动层。

命令解析完毕后,需要中断正在运行的进程,转而执行read 命令,这就需要中断处理程序。

最后,命令真正抵达硬件设备,硬件设备的控制器按照上层传达的命令操控硬件设备,完成相应的功能。

本节小结

本节开头提出的问题的参考答案如下。

IO管理要完成哪些功能?

I/O管理需要完成以下4部分内容:

1)状态跟踪。要能实时掌握外部设备的状态。

2)设备存取。要实现对设备的存取操作。

3)设备分配。在多用户环境下,负责设备的分配与回收。

4)设备控制。包括设备的驱动、完成和故障的中断处理。

I/O核心子系统

在学习本节时,请读者思考以下问题:

1)当处理机和外部设备速度差距较大时,并且此时不想让其中一方等待,有什么办法可以解决问题?

2)什么是设备的独立性?引入设备的独立性有什么好处?

I/O子系统概述

由于IO设备种类繁多,功能和传输速率差异巨大,因此需要多种方法来进行设备控制。这些方法共同组成了操作系统内核的I/O子系统,它将内核的其他方面从繁重的IO设备管理中解放出来。I/O核心子系统提供的服务主要有I/O调度、缓冲与高速缓存、设备分配与回收、假脱机、设备保护和差错处理等。

I/O调度概念

IO 调度就是确定一个好的顺序来执行这些IO请求。应用程序所发布的系统调用的顺序不一定总是最佳选择,所以需要IO调度来改善系统整体性能,使进程之间公平地共享设备访问,减少I/O完成所需要的平均等待时间。

操作系统开发人员通过为每个设备维护一个请求队列来实现调度。当一个应用程序执行阻塞IO系统调用时,该请求就加到相应设备的队列上。IO调度会重新安排队列顺序,以改善系统总体效率和应用程序的平均响应时间。

IO子系统还可使用主存或磁盘上的存储空间的技术,如缓冲、高速缓存、假脱机等来改善计算机效率。

4.3节的磁盘调度算法其实就是IO调度的一种。

高速缓存与缓冲区

磁盘高速缓存( Disk Cache)

操作系统中使用磁盘高速缓存技术来提高磁盘的IO速度,对高速缓存复制的访问要比原始数据访问更为高效。例如,正在运行的进程的指令既存储在磁盘上,又存储在物理内存上,也被复制到CPU的二级和一级高速缓存中。

不过,磁盘高速缓存技术不同于通常意义下的介于CPU与内存之间的小容量高速存储器,而是指利用内存中的存储空间来暂存从磁盘中读出的一系列盘块中的信息。因此,磁盘高速缓存 $\color{red}{\text{逻辑}}$ 上属于 $\color{green}{\text{磁盘}}$ , $\color{red}{\text{物理}}$ 上则是驻留在 $\color{green}{\text{内存}}$ 中的盘块。

高速缓存在内存中分为 $\color{green}{\text{两种形式}}$ :一种是在内存中开辟一个单独的存储空间作为磁盘高速缓存,大小固定;另一种是把未利用的内存空间作为一个缓冲池,供请求分页系统和磁盘IO时共享。

缓冲区(Buffer)

在设备管理子系统中,引入缓冲区的目的主要如下:

1)缓和CPU与VO设备间速度不匹配的矛盾。

2)减少对CPU的中断频率,放宽对CPU中断响应时间的限制。

3)解决基本数据单元大小(即数据粒度)不匹配的问题。

4)提高CPU 和IO设备之间的并行性。

其实现方法如下:

1)采用硬件缓冲器,但由于成本太高,除一些关键部位外,一般不采用硬件缓冲器。

2)采用缓冲区(位于内存区域)。

缓冲区有一个特点,即当缓冲区的数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出;当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满后,才能从缓冲区把数据传出。

根据系统设置缓冲器的个数,缓冲技术可以分为如下几种:

1) $\color{green}{\text{单缓冲}}$ 。在设备和处理机之间设置一个缓冲区。设备和处理机交换数据时,先把被交换数据写入缓冲区,然后需要数据的设备或处理机从缓冲区取走数据。

如图5.5所示,在块设备输入时,假定从磁盘把一块数据输入缓冲区的时间为T,操作系统将该缓冲区中的数据传送到用户区的时间为M,而 CPU对这一块数据处理的时间为C。在研究各种缓冲技术的每块数据的处理时间时,有一个技巧:假设一种初始状态,然后计算下一次到达相同状态时所需要的时间,就是处理一块数据所需要的时间。在单缓冲中,这种初始状态为:工作区是满的,缓冲区是空的。如题目没有明确说明,一般认为缓冲区的大小和工作区的大小相等。

我们假设T>C,从初始状态开始,当工作区数据处理完后,时间为C,缓冲区还没充满,当缓冲区充满时,经历了T时间,停止再冲入数据,然后缓冲区向工作区传送数据,当工作区满了以后,缓冲区的数据同时也为空,用时为M,到达下一个开始状态,整个过程用时M+T;若T< C,同理,整个过程用时M+C。所以单缓冲区处理每块数据的用时为max(C, T)+M。.

图5.5单缓冲工作示意图找不到图片(Image not found)

2) $\color{green}{\text{双缓冲}}$ 。根据单缓冲的特点,CPU在传送时间M内处于空闲状态,由此引入双缓冲。IO设备输入数据时先装填到缓冲区1,在缓冲区1填满后才开始装填缓冲区2,与此同时处理机可以从缓冲区1中取出数据放入用户进程处理,当缓冲区1中的数据处理完后,若缓冲区2已填满,则处理机又从缓冲区2中取出数据放入用户进程处理,而I/O设备又可以装填缓冲区1。注意,必须等缓冲区2充满才能让处理机从缓冲区2取出数据。双缓冲机制提高了处理机和输入设备的并行操作的程度。

为了研究双缓冲处理一块数据的用时,我们先规定一种初始状态:工作区是空的,其中一个缓冲区是满的,另外一个缓冲区是空的;我们不妨假设缓冲区1是空的,缓冲区2是满的。

如图5.6所示,我们假设T< C+M,缓冲区2开始向工作区传送数据,缓冲区1开始冲入数据,当工作区充满数据后,缓冲区为空,时间为M,然后工作区开始处理数据,缓冲区1继续冲入数据,因为此时只有一个IO设备,所以缓冲区2虽然为空,但不能冲入数据。当缓冲区1充满数据后,工作区的数据还未处理完毕,时间为T,当工作区数据处理完毕后,此时工作区为空,缓冲区1满,缓冲区2为空,达到下一个初始状态,用时C+M。

图5.6双缓冲工作示意图找不到图片(Image not found)

我们再来分析T>C+M的情况。缓冲区2开始向工作区传送数据,缓冲区1开始冲入数据,当工作区充满数据并处理完后,用时C+M,但缓冲区1的数据还未充满;当时间为T时,缓冲区1的数据充满,到达下一个初始状态。

总结:双缓冲区处理一块数据的用时为max(C+ M, T)。

若M+C< T,则可使块设备连续输入;若C+ M > T,则可使CPU不必等待设备输入。对于字符设备,若采用行输入方式,则采用双缓冲可使用户在输入第一行后,在CPU执行第一行中的命令的同时,用户可继续向第二缓冲区输入下一行数据。而单缓冲情况下则必须等待一行数据被提取完毕才可输入下一行的数据。

若两台机器之间通信仅配置了单缓冲,如图5.7(a)所示,则它们在任意时刻都只能实现单方向的数据传输。例如,只允许把数据从A机传送到B机,或从B机传送到A机,而绝不允许双方同时向对方发送数据。为了实现双向数据传输,必须在两台机器中都设置两个缓冲区,一个用作发送缓冲区,另一个用作接收缓冲区,如图5.7(b)所示。

图5.7 双机通信时缓冲区的设置找不到图片(Image not found)

3) $\color{green}{\text{循环缓冲}}$ 。包含多个大小相等的缓冲区,每个缓冲区中有一个链接指针指向下一个缓冲区,最后一个缓冲区指针指向第一个缓冲区,多个缓冲区构成一个环形。

循环缓冲用于输入/输出时,还需要有两个指针in和 out。对输入而言,首先要从设备接收数据到缓冲区中,in指针指向可以输入数据的第一个空缓冲区;当运行进程需要数据时,从循环缓冲区中取一个装满数据的缓冲区,并从此缓冲区中提取数据,out指针指向可以提取数据的第一个满缓冲区。输出则正好相反。

4) $\color{green}{\text{缓冲池}}$ 。由多个系统公用的缓冲区组成,缓冲区按其使用状况可以形成三个队列:空缓冲队列、装满输入数据的缓冲队列(输入队列)和装满输出数据的缓冲队列(输出队列)。

还应具有4种缓冲区:用于收容输入数据的工作缓冲区、用于提取输入数据的工作缓冲区、用于收容输出数据的工作缓冲区及用于提取输出数据的工作缓冲区,如图5.8所示。

图5.8缓冲池的工作方式找不到图片(Image not found)

当输入进程需要输入数据时,便从空缓冲队列的队首摘下一个空缓冲区,把它作为收容输入工作缓冲区,然后把输入数据输入其中,装满后再将它挂到输入队列队尾。当计算进程需要输入数据时,便从输入队列取得一个缓冲区作为提取输入工作缓冲区,计算进程从中提取数据,数据用完后再将它挂到空缓冲队列尾。当计算进程需要输出数据时,便从空缓冲队列的队首取得一个空缓冲区,作为收容输出工作缓冲区,当其中装满输出数据后,再将它挂到输出队列队尾。当要输出时,由输出进程从输出队列中取得一个装满输出数据的缓冲区,作为提取输出工作缓冲区,当数据提取完后,再将它挂到空缓冲队列的队尾。

对于循环缓冲和缓冲池,我们只是定性地介绍它们的机理,而不去定量研究它们平均处理一块数据所需要的时间。而对于单缓冲和双缓冲,我们只要按照上面的模板分析,就可以解决任何计算单缓冲和双缓冲情况下数据块处理时间的问题,以不变应万变。

高速缓存与缓冲区的对比

高速缓存是可以保存数据拷贝的高速存储器,访问高速缓存比访问原始数据更高效,速度更快。高速缓存和缓冲区的对比见表5.1。

表5.1 高速缓存和缓冲区的对比找不到图片(Image not found)

设备分配与回收

设备分配概述

设备分配是指根据用户的IO请求分配所需的设备。分配的总原则是充分发挥设备的使用效率,尽可能地让设备忙碌,又要避免由于不合理的分配方法造成进程死锁。从设备的特性来看,采用下述三种使用方式的设备分别称为独占设备、共享设备和虚拟设备。

1) $\color{green}{\text{独占式使用设备}}$ 。指在申请设备时,若设备空闲,则将其独占,不再允许其他进程申请使用,一直等到该设备被释放才允许其他进程申请使用。例如,打印机,在使用它打印时,只能独占式使用,否则在同一张纸上交替打印不同任务的内容,无法正常阅读。

2) $\color{green}{\text{分时式共享使用设备}}$ 。独占式使用设备时,设备利用率很低,当设备没有独占使用的要求时,可以通过分时共享使用提高利用率。例如,对磁盘设备的IO操作,各进程的每次I/O操作请求可以通过分时来交替进行。

3) $\color{green}{\text{以SPOOLing方式使用外部设备}}$ 。SPOOLing (Simultaneous Peripheral Operation On-Line)技术是在批处理操作系统时代引入的,即假脱机I/O技术。这种技术用于对设备的操作,实质上就是对IO操作进行批处理。SPOOLing 技术实质上是一种以空间换时间的技术,而我们熟悉的请求分页系统中的页面调度算法就刚好相反,是以时间换空间的技术。

设备分配的数据结构

设备分配依据的主要数据结构有设备控制表(DCT)、控制器控制表(COCT)、通道控制表(CHCT)和系统设备表(SDT),各数据结构功能如下。
设备控制表(DCT):我们可以认为,一个设备控制表就表征一个设备,而这个控制表中的表项就是设备的各个属性,如图5.9所示。

图5.9设备控制表找不到图片(Image not found)

前面我们学过4种IO控制方式,通道方式显然要比其他几种方式更加优越,因此现代操作系统的I/O控制采用的都是通道控制。设备控制器控制设备与内存交换数据,而设备控制器又需要请求通道为它服务,因此每个COCT[见图5.10(a) ]必定有一个表项存放指向相应通道控制表(CHCT)[见图5.10(b) ]的指针,而一个通道可为多个设备控制器服务,因此CHCT中必定有一个指针,指向一个表,这个表上的信息表达的是CHCT提供服务的那几个设备控制器。CHCT 与COCT的关系是 $\color{green}{\text{一对多}}$ 的关系。

系统设备表(SDT):整个系统只有一张 SDT,如图5.10(c)所示。它记录已连接到系统中的所有 $\color{green}{\text{物理设备的情况}}$ ,每个物理设备占一个表目。

图5.10COCT、CHCT 和 SDT找不到图片(Image not found)

由于在多道程序系统中,进程数多于资源数,会引起资源的竞争,因此要有一套合理的分配原则,主要考虑的因素有:I/O设备的固有属性、I/O设备的分配算法、I/O设备分配的安全性以及IO设备的独立性。

设备分配的策略

1)设备分配原则。设备分配应根据设备特性、用户要求和系统配置情况。分配的总原则是:既要充分发挥设备的使用效率,又要避免造成进程死锁,还要将用户程序和具体设备隔离开。

2)设备分配方式。设备分配方式有静态分配和动态分配两种。

$\color{green}{\text{静态分配}}$ 主要用于对独占设备的分配,它在用户作业开始执行前,由系统一次性分配该作业所要求的全部设备、控制器(如通道等)。一旦分配,这些设备、控制器(和通道)就一直为该作业所占用,直到该作业被撤销。静态分配方式不会出现死锁,但设备的使用效率低。因此,静态分配方式并不符合分配的总原则。

$\color{green}{\text{动态分配}}$ 在进程执行过程中根据执行需要进行。当进程需要设备时,通过系统调用命令向系统提出设备请求,由系统按照事先规定的策略给进程分配所需要的设备、IO控制器,一旦用完,便立即释放。动态分配方式有利于提高设备的利用率,但若分配算法使用不当,则有可能造成进程死锁。

3)设备分配算法。常用的动态设备分配算法有先请求先分配、优先级高者优先等。

对于独占设备,既可以采用动态分配方式,又可以采用静态分配方式,但往往采用静态分配方式,即在作业执行前,将作业所要用的这一类设备分配给它。共享设备可被多个进程所共享,一般采用动态分配方式,但在每个IO传输的单位时间内只被一个进程所占有,通常采用先请求先分配和优先级高者优先的分配算法。

设备分配的安全性

设备分配的安全性是指设备分配中应防止发生进程死锁。

1) $\color{green}{\text{安全分配方式}}$ 。每当进程发出IO请求后便进入阻塞态,直到其IO操作完成时才被唤醒。这样,一旦进程已经获得某种设备后便阻塞,不能再请求任何资源,而且在它阻塞时也不保持任何资源。优点是设备分配安全;缺点是CPU和IO设备是串行工作的(对同一进程而言)。

2) $\color{green}{\text{不安全分配方式}}$ 。进程在发出I/O请求后继续运行,需要时又发出第二个、第三个I/O请求等。仅当进程所请求的设备已被另一进程占用时,才进入阻塞态。优点是一个进程可同时操作多个设备,从而迅速推进进程;缺点是这种设备分配有可能产生 $\color{green}{\text{死锁}}$ 。

逻辑设备名到物理设备名的映射

为了提高设备分配的灵活性和设备的利用率,方便实现IO重定向,引入了设备独立性。设备独立性是指应用程序独立于具体使用的物理设备。

为了实现设备独立性,在应用程序中使用逻辑设备名来请求使用某类设备,在系统中设置一张 $\color{green}{\text{逻辑设备表}}$ (Logical Unit Table,LUT),用于将逻辑设备名映射为物理设备名。LUT表项包括逻辑设备名、物理设备名和设备驱动程序入口地址;当进程用逻辑设备名来请求分配设备时,系统为它分配相应的物理设备,并在LUT中建立一个表项,以后进程再利用逻辑设备名请求I/O操作时,系统通过查找LUT来寻找相应的物理设备和驱动程序。

在系统中可采取两种方式建立逻辑设备表:

1)在整个系统中只设置一张 LUT。这样,所有进程的设备分配情况都记录在这张表中,因此不允许有相同的逻辑设备名,主要适用于单用户系统。

2)为每个用户设置一张 LUT。当用户登录时,系统便为该用户建立一个进程,同时也为之建立一张LUT,并把该表放入进程的PCB。

SPOOLing技术(假脱机技术)

为了缓和 CPU的高速性与I/O设备低速性之间的矛盾,引入了脱机输入/输出技术。该技术利用专门的外围控制机,将低速IO设备上的数据传送到高速磁盘上,或者相反。SPOOLing 的意思是外部设备同时联机操作,又称假脱机输入/输出操作,是操作系统中采用的一项将独占设备改造成共享设备的技术。

SPOOLing系统的组成如图5.11所示。

图5.11SPOOLing系统的组成找不到图片(Image not found)
输入井和输出井

输入井和输出井是指在磁盘上开辟出的两个存储区域。输入井模拟脱机输入时的磁盘,用于收容IO设备输入的数据。输出井模拟脱机输出时的磁盘,用于收容用户程序的输出数据。

输入缓冲区和输出缓冲区

输入缓冲区和输出缓冲区是在内存中开辟的两个缓冲区。输入缓冲区用于暂存由输入设备送来的数据,以后再传送到输入井。输出缓冲区用于暂存从输出井送来的数据,以后再传送到输出设备。

输入进程和输出进程

输入进程模拟脱机输入时的外围控制机,将用户要求的数据从输入机通过输入缓冲区再送到输入井。当CPU需要输入数据时,直接将数据从输入井读入内存。输出进程模拟脱机输出时的外围控制机,把用户要求输出的数据先从内存送到输出井,待输出设备空闲时,再将输出井中的数据经过输出缓冲区送到输出设备。

共享打印机是使用SPOOLing 技术的一个实例,这项技术已被广泛地用于多用户系统和局域网络。当用户进程请求打印输出时,SPOOLing 系统同意为它打印输出,但并不真正立即把打印机分配给该用户进程,而只为它做两件事:

1)由输出进程在输出井中为之申请一个空闲磁盘块区,并将要打印的数据送入其中。

2)输出进程再为用户进程申请一张空白的用户请求打印表,并将用户的打印要求填入其中,再将该表挂到请求打印队列上。

SPOOLing系统的主要特点有:提高了IO 的速度;将独占设备改造为共享设备﹔实现了虚拟设备功能。

前面我们提到过SPOOLing技术是一种以空间换时间的技术,我们很容易理解它牺牲了空间,因为它开辟了磁盘上的空间作为输入井和输出井,但它又如何节省时间呢?

从前述内容我们了解到,磁盘是一种高速设备,在与内存交换数据的速度上优于打印机、键盘、鼠标等中低速设备。试想一下,若没有SPOOLing 技术,CPU要向打印机输出要打印的数据,打印机的打印速度比较慢,CPU就必须迁就打印机,在打印机把数据打印完后才能继续做其他的工作,浪费了CPU的不少时间。在SPOOLing 技术下,CPU要打印机打印的数据可以先输出到磁盘的输出井中(这个过程由输出进程控制),然后做其他的事情。若打印机此时被占用,则SPOOLing 系统就会把这个打印请求挂到等待队列上,待打印机有空时再把数据打印出来。向磁盘输出数据的速度比向打印机输出数据的速度快,因此就节省了时间。

本节小结

本节开头提出的问题的参考答案如下。

当处理机和外部设备速度差距较大时,并且此时不想让其中一方等待,有什么办法可以解决问题?

可以采用缓冲技术来缓解处理机与外部设备速度上的矛盾,即在某块地方(一般为主存)设立一片缓冲区,外部设备与处理机的输入/输出都经过缓冲区,这样外部设备和处理机就都不用互相等待。

什么是设备的独立性?引入设备的独立性有什么好处?

设备独立性是指用户在编程序时使用的设备与实际设备无关。一个程序应独立于分配给它的某类设备的具体设备,即在用户程序中只指明IO使用的设备类型即可。

设备独立性有以下优点:

${\textstyle\unicode{x2460}}$ 方便用户编程。

${\textstyle\unicode{x2461}}$ 使程序运行不受具体机器环境的限制。

${\textstyle\unicode{x2462}}$ 便于程序移植。