文章目录
- 一、操作系统概述
- 二、进程和调度
- 三、内存管理/存储器管理
- 四、文件管理
- 五、设备管理
一、操作系统概述
摘要
- 关于现代操作系统的四种基本观点 ( What’s OS )
- 现代操作系统功能和非功能性需求(按软件工程的观点分析 OS 的结构)
- 操作系统的发展、类型及特征
- 现代操作系统体系结构基础知识
1.1 什么是操作系统
操作系统(Operating System,简称OS)是一种计算机系统软件,它管理和控制计算机硬件与软件资源,井合理地组织调度计算机工作和资源分配,以提供给用户和其他软件方便的接口和环境。它是计算机系统中最基本的系统软件,也是配置在计算机硬件上的第一层软件。
关于现代0S的四种基本观点
-
(从外部看0S)
- (计算机用户的观点):用户环境观点
- 操作系统方便用户使用
- (应用程序员的观点):虚拟机器观点
- 操作系统是虚拟机,虚拟机指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。
- (计算机用户的观点):用户环境观点
-
(从内部看0S)
- (0S开发者观点之一):资源管理观点(系统观点)
- 操作系统是计算机系统的资源管理程序:处理机管理、存储器管理、文件管理、设备管理。
- (0S开发者观点之二):作业组织观点(进程观点)
- 操作系统由若干个可独立运行的进程和一个对这些进程协调的核心组成。
- (0S开发者观点之一):资源管理观点(系统观点)
-
User/Computer Interface:该观点认为OS是计算机用户使用计算机系统的接口,它为计算机用户提供了方便的工作环境。
-
Virtual Machine:该观点认为OS是建立在计算机硬件平台上的虚拟机器,它为应用软件提供了许多比计算机硬件功能更强或计算机硬件所没有的功能。
-
Resource Manager:该观点认为OS是计算机系统中各类资源的管理者,它负责分配、回收以及控制系统中的各种软硬件资源。
-
Job Organizer :该观点认为OS是计算机系统工作流程的组织者,它负责协调在系统中运行的各个应用软件的运行次序。
1.2 OS的系统需求
-> 需求分析 -> 系统设计 -> 编码实现 -> 产品测试 ->
- 软件系统的系统需求
- OS的功能性需求
- OS的非功能性需求
- OS对硬件平台的依赖
软件系统的系统需求
所谓软件系统的系统需求是指,人们从软件系统的外部对软件系统提出的诸多期望。
这些期望包括三种类型:
- 系统能提供的服务;
- 软件系统在提供这些服务时,需要满足的限制条件:
- 软件系统具有适应某些变化的能力。
第一类系统需求是后两类系统需求赖以存在的基础,称之为软件系统的功能性需求,后两类系统需求称为软件系统的非功能性需求。
OS的功能性需求
- 计算机用户需要的用户命令
- 由OS实现的所有用户命令所构成的集合常被人们称为 OS 的 Interface(用户接口);有时也称为命令接口。
- 应用软件需要的 System Call(系统调用)
- 由 OS 实现的所有系统调用所构成的集合被人们称为程序接口或应用编程接口(Application Programming Interface, API)。
系统调用(广义指令即系统调用指令):操作系统作为用户和计算机硬件之间的接口,需要向上提供一些简单易用的服务。主要包括命令接口和程序接口。其中,程序接口由一组系统调用组成。“系统调用”是操作系统提供给应用程序(程序员/编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以通过系统调用来请求获得操作系统内核的服务。
系统调用相关功能涉及系统资源管理、进程管理之类的操作,运行在核心态。所以应用程序要通过系统调用请求操作系统的服务。系统中的各种共享资源都由操作系统内核统一掌管,因此凡是与共享资源有关的操作(如存储分配、I/O操作、文件管理等),都必须通过系统调用的方式向操作系统内核提出服务请求,由操作系统内核代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。
按功能分类:设备管理、文件管理、进程控制、进程通信、内存管理。
系统调用的过程:传递系统调用参数 -> 执行陷入指令(用户态)->执行相应的请求内核程序处理系统调用(核心态)->返回应用程序
整个过程是用户程序调用陷入指令(trap指令或访管指令)CPU先从用户态进入核心态,当系统调用的程序执行完会返回用户程序,CPU再从核心态转换为用户态。
注意:
- 陷入指令(访管指令)是在用户态执行的,执行陷入指令之后立即引发一个内中断,使CPU进入核心态。
- 发出系统调用请求是在用户态,而对系统调用的相应处理在核心态下进行。
- 先传参再执行陷入指令
Interface
- (用户)命令:指计算机用户要求计算机系统为其工作的指示。
- 命令的表示形式
- 字符形式
- 菜单形式
- 图形形式
- 命令的使用方式
- 脱机使用方式(off-line)
- 联机使用方式(on-line)
System Call
- System Call : 指由 OS 实现的应用软件在运行过程中可以引用的 System Service.
- 当前两种常用的API : POSIX.1、WIN32 API
注:程序接口事实上定义了一台虚拟计算机。该虚拟计算机包含一组抽象概念以及与这组抽象概念相关的系统服务。
OS的非功能性需求
- Performance(性能)or Efficiency(效率)
- maximize throughput, minimize response time, and in the case of time sharing, accommodate as many users as possible
- Fairness(公平性)
- give equal and fair access to all processes
- Reliability(可靠性)
- Security(安全性)
- Scalability(可伸缩性)
- Extensibility((可扩展性)
- Portability(可移植性)
- … …
OS对硬件平台的依赖
- Timer
- I/O Interrupts
- DMA or Channel
- Privileged Instructions
- Memory Protection Mechanism
- … …
基本概念:Job(作业)
- Job 是指,计算机用户在一次上机过程中要求计算机系统为其所做工作的集合;作业中的每项相对独立的工作称为作业步。
- 通常,人们用一组命令来描述作业;其中,每个命令定义一个作业步。
- … …
Job Control Language (JCL)
- Special type of programming language
- Provides instruction to the monitor监控程序)
- what compiler to use
- what data to use
作业的基本类型
- Off-line Job : 计算机用户不能在此类作业被计算机系统处理时改变已定义好的作业步。
- On-line Job : 计算机用户可以在此类作业被计算机系统处理时随时改变其作业步.
基本概念:Thread Process
- Thread 是指,程序的一次相对独立的运行过程;在现代OS中,线程是系统调度的最小单位。
- Process 是指,系统分配资源的基本对象;在现代OS中,进程仅仅是系统中拥有资源的最小实体;不过,在传统OS中,进程同时也是系统调度的最小单位。
基本概念:Virtual Memory & File
- Virtual Memory(虚拟存储),简单地说,就是进程的逻辑地址空间;它是现代OS对计算机系统中多级物理存储体系进行高度抽象的结果。
- File(文件),简单地说,就是命名了的字节流;它是现代OS对计算机系统中种类繁多的外部设备进行高度抽象的结果。
1.3 OS的演变、类型及特征
OS的演变
Ease of Evolution of an Operating System
- Fixes(修改)
- New Services
- Hardware Upgrade Plus New Types of Hardware
- Efficiency
Serial Processing(串行处理)
- 没有操作系统
- 机器在一个控制台上运行,控制台包括显示灯、触发器、某种类型的输入设备和打印机。两个主要问题:
- 调度(Scheduling):时间浪费
- 准备时间: 称为作业的单个程序,可能会向内存中加载编译器和高级语言程序(源程序),保存编译好的程序(目标程序),然后加载目标程序和公用函数并进行链接。
Simple Batch Systems(简单批处理系统)
为了解决人机矛盾及CPU和I/O设备之间速度不匹配的矛盾,出现了批处理系统。换言之,批处理系统旨在提高系统资源的利用率和系统吞吐量。
- Monitors(监督程序):控制程序运行的软件
- 监控程序控制事件的顺序
- 大部分监控程序必须总是处于内存中并且可执行,称为常驻监控程序(resident monitor)
- 批处理作业一起执行
- 当程序完成执行后,程序会返回到监控程序。
- “控制权交给作业”:处理器当前取的和执行的都是用户程序中的指令
- “控制权返回给监控程序”:处理器当前从监控程序中取指令并执行指令
处理器仍然经常处于空闲状态. 因为 I/O 设备相对于处理器而言速度太慢.
Uniprogramming(单道程序设计)
系统对作业的处理是成批进行的,但内存中始终保持一道作业。单道批处理系统是在解决人机矛盾及CPU和I/O设备之间速度不匹配的矛盾中形成的。引入了脱机输入/输出技术(用外围机+磁带完成),并由监督程序(操作系统的雏形)负责控制作业的输入、输出。
主要特征:
- 自动性。在顺利的情况下,磁带上的一批作业能自动地逐个运行,而无须人工干预。
- 顺序性。磁带上的各道作业顺序地进入内存,各道作业的完成顺序与它们进入内存的顺序在正常情况下应完全相同,亦即先调入内存的作业先完成。
- 单道性。内存中仅有一道程序运行,即监督程序每次从磁带上只调入一道程序进入内存运行,当该程序完成或发生异常情况时,才换入其后继程序进入内存运行。
最主要缺点:
- 系统中的资源得不到充分的利用。内存中仅有一道程序,处理器花费一定的运行时间进行计算,直到遇到一个 I/O指令,这时它须等到该 I/O 指令结束后才能继续进行。
- 效率低。 I/O 设备的低速,使CPU的利用率低。
Multiprogramming(多道程序设计)
一个支持 Multiprogramming 的系统允许多道程序同时准备运行;当正在运行的那道程序因为某种原因(比如等待输入或输出数据)暂时不能继续运行时,系统将自动地启动另一道程序运行;一旦原因消除(比如数据已经到达或数据已经输出完毕),暂时停止运行的那道程序在将来某个时候还可以被系统重新启动继续运行。在该系统中,用户所提交的作业先存放在外存上,并排成一个队列,称为“后备队列”。然后再从后备队列中成批地把作业送入计算机内存,再由作业调度程序自动地选择作业运行。
中断技术使得多道批处理系统和I/O设备可与CPU并行工作,同时也提高了多道程序运行环境中CPU的利用率。
- 当一个作业需要等待 I/O 时,处理器可以切换到另一个可能并不在等待 I/O 的作业。
- 进一步还可以拓展内存以保存三四个甚至更多的程序, 且在它们之间进行切换 。
特点:
- 多道。计算机内存中同时存放多道互相独立的程序。
- 宏观上并行。同时进入系统的多道程序都处于运行过程中,即它们先后开始各自的运行,但都未运行完毕。
- 微观上串行。内存中的多道程序轮流占有 CPU,交替执行。
优点:
- 资源利用率高。引入多道批处理能使多道程序交替运行,以保持 CPU 处于忙碌状态;在内存中装入多道程序可提高内存的利用率;此外还可以提高 I/O 设备的利用率。
- 系统吞吐量。CPU 和其他资源保持“忙碌”状态;仅当作业完成时或运行不下去时才进行切换,系统开销小。
缺点:
- 平均周转时间长。由于作业要排队依次进行处理,因而作业的周转时间较长,通常需几个小时,甚至几天。
- 无交互能力。用户一旦把作业提交给系统后,直到作业完成,用户都不能与自己的专业进行交互,修改和调试程序极不方便。
与单道程序系统相比,多道程序系统的优点是 CPU 利用率高、系统吞吐量大以及 I/O 设备利用率高。
Difficulties with Multiprogramming
-
Improper synchronization(同步)
- ensure a process waiting for an I/O device receives the signal
-
Failed mutual exclusion(互斥)
-
Nondeterminate program operation
- program should only depend on input to it
-
Deadlocks(死锁)
Time Sharing(分时系统)
解决了人机交互问题。
同时处理多个交互作业,多个用户分享处理器时间,该技术称为 分时。
所谓分时技术,是指把处理器的运行时间分成很短的时间片,按时间片轮流把处理器分配给各联机作业使用。一个时间片就是一段很短的时间。
分时操作系统是指多个用户通过自己的终端同时共享一台主机,这些终端连接在主机上,用户可以同时与主机进行交互操作而互不干扰,当用户在自己的终端上键入命令时,系统应能及时接收并及时处理该命令,再将结果返回用户。
而想要做到及时接收多个用户键入的命令或数据,只需在系统中配置一个多路卡(实现分时多路复用)即可。多路卡的作用是实现分时多路复用。
实现及时处理则需要采用两种方式:
- 作业直接进入内存。因为作业在磁盘上是不能运行的,所以作业应直接进入内存。
- 采用轮转运行方式。为避免一个作业长期独占处理机,引入了时间片的概念。系统规定每个作业每次只能运行一个时间片,然后就暂停该作业的运行,并立即调度下一个作业运行。如果在不长的时间内能使所有的作业都执行一个时间片的时间,便可以使每个用户都能及时地与自己的作业进行交互,从而可使用用户的请求得到及时响应。
分时系统的特征:
①多路性(同时性)。指允许多个终端用户同时使用一台计算机。
②独立性。指每个用户在各自的终端上进行操作,彼此之间互不干扰,给用户的感觉就像是他一人独占主机进行操作。
③及时性。指用户的请求在很短时间内获得响应。
④交互性。用户通过终端采用人机对话的方式直接控制程序运行,与同程序进行交互。
主要优点:用户请求可以被即时响应,解决了人机交互问题。允许多个用户同时使用一台计算机,并且用户对计算机的操作相互独立,感受不到别人的存在。
主要缺点:不能优先处理一些紧急任务。操作系统对各个用户/作业都是完全公平的,循环地为每个用户/作业服务一个时间片,不区分任务的紧急性。
多道批处理系统与分时系统比较
批处理多道程序设计 | 分时 | |
---|---|---|
主要目标 | 充分利用处理器 | 减小响应时间 |
操作系统指令源 | 作业控制语言命令 & 作业提供的命令 | 终端键入的命令 |
实时操作系统
为了能在某个时间限制内完成某些任务而不需要时间片排队,诞生了实时操作系统。实时操作系统最主要的特征是将时间作为关键参数。实时操作系统是指系统能及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致地运行。
在实时操作系统的控制下,计算机系统接收到外部信号后及时进行处理,并且要在严格的时限内处理完事件。实时操作系统的主要特点是及时性和可靠性。
主要优点:能够优先响应一些紧急任务,某些紧急任务不需时间片排队。
实时任务的类型:
- 周期性实时任务是指这样一类任务,外部设备周期性地发出激励信号给计算机,要求它按指定周期循环执行,以便周期性地控制某外部设备。
- 非周期性实时任务并无明显的周期性,但都必须联系着一个截止时间(最后期限)。它又分为:
- 开始截止时间,指某任务在某时间以前必须开始执行。
- 完成截止时间,指某任务在某时间以前必须完成。
- 硬实时任务(HRT)是指系统必须满足任务对截止时间的要求,否则可能出现难以预测的后果。如导弹控制系统等。
- 软实时任务(SRT)能接受偶尔违法时间规定的事情发生。如信息查询系统。
现代OS的基本类型
按硬件平台系统结构分类:
- 单机OS
- 并行OS
- 网络OS
- 分布式OS
单机OS的基本类型
按功能特征分类:
- Batch_Processing OS(批处理系统)
- Time_Sharing OS(分时系统)
- Real_Time OS(实时系统)
现代OS的两个基本特征
操作系统的基本特征包括 并发、共享、虚拟和异步。
- 任务共行
- 从宏观上看,任务共行是指系统中有多个任务同时运行
- 从微观上看,任务共行是指单处理机系统中的任务并发(Task Concurrency:即多个任务在单个处理机上交替运行)或多处理机系统中的任务并行(Task Parallelism:即多个任务在多个处理机上同时运行)。
- 资源共享
- 从宏观上看,资源共享是指多个任务可以 同时使用系统中的软硬件资源
- 从微观上看,资源共享是指多个任务可以交替互斥地使用系统中的某个资源。
并发:指两个或多个事件在 同一时间间隔内发生。这些事件宏观上是同时发生的,但微观上是交替发生的。
并行:并行是指两个或多个事件在 同一时刻同时发生。并行性需要有相关硬件的支持,如多流水线或多处理机硬件环境。操作系统的并发性是通过 分时得以实现的。因为在多道程序环境下,一段时间内,宏观上有多道程序在同时执行,而在每个时刻,单处理机环境下实际只能有一道程序执行,因此微观上这些程序仍是分时交替执行的。
操作系统就是伴随着“多道程序技术”而出现的。因此,操作系统和程序并发是一起诞生的。
在内存中的多个程序都分别建立一个进程,它们就可以并发执行,这样便能极大地提高系统资源的利用率,增加系统的吞吐量。
所谓进程,是指在系统中能独立运行并作为资源分配的独立单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。
单核CPU同一时刻只能执行一个程序,各个程序只能 并发地执行
多核CPU同一时刻可以同时执行多个程序,多个程序可以 并行地执行
并发和共享的关系:
并发性指计算机系统中同时存在着多个运行着的程序。
共享性是指系统中的资源可供内存中多个并发执行的进程共同使用。
并发和共享是多用户(多任务)OS的两个最基本的特征。它们是互为存在的条件
-
虚拟:虚拟是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物(后者)是用户感受到的。
-
时分复用技术,有虚拟处理机技术和虚拟设备技术。虚拟处理机技术是通过多道程序设计技术,采用让多道程序并发执行的方法,来分时使用一个处理器的。利用多道程序设计技术把一个物理上的CPU虚拟为多个逻辑上的CPU。时分复用技术能提高资源利用率的根本原因在于,它利用某设备为一用户服务的空闲时间,又转去为其他用户服务,使设备得到最充分的利用。
-
空分复用技术,有虚拟存储器技术。采用空分复用技术将一台机器的物理存储器变为虚拟存储器,以便从逻辑上扩充存储器的容量。空分复用技术是利用存储器的空闲空间分区域存放和运行其他的多道程序,以此来提高内存的利用率。
-
-
异步。在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。
任务管理模型
所谓 Task 是指,计算机系统在某个资源集合上所做的一次相对独立的计算过程。
- 在现代 OS 中,任务用线程和进程这两个基本概念共同表示;在传统 OS 中,任务仅仅用进程这一基本概念表示。
- 在现代 OS 中,任务管理模型用线程状态转换图表示;在传统 OS 中,任务管理模型用进程状态转换图表示。
资源管理模式
所谓 Resource 是指,由程序和数据组成的软件资源以及包含CPU、存储器、I/O设备等在内的硬件资源。
- 通常情况下,系统用竞争模式管理软件资源;为此,系统将为共享同一软件资源的多个任务提供互斥机制。
- 对于硬件资源,系统常常用分配模式加以管理。该模式可以描述为:
- 申请—分配一使用一释放—回收
1.4 OS架构
-> 需求分析 -> 系统设计 -> 编码实现 -> 产品测试 ->
- 软件体系结构设计
- 软件部件设计
一种常见的 OS 总体结构风格
- 大多数现代 OS 其总体结构包含两类子系统:一是用户接口子系统,二是基础平台子系统。其中,用户接口子系统提供计算机用户需求的用户命令,基础平台子系统提供应用软件需求的系统调用。
- 用户接口子系统与基础平台子系统之间的相互关系具有单向性。具体地说,用户接口子系统在实现各种用户命令时能够引用基础平台子系统所提供的各种系统调用,但基础平台子系统在实现各种系统调用时不会引用用户接口子系统所提供的各种用户命令。
OS 基础平台子系统结构风格
常见的基础平台子系统结构风格(一)
- Layered Structural Style(分层结构风格)
- Hierarchical Structural Style(分级结构风格)
- Modular Structural Style(分块结构风格)
分层结构风格的结构特征
- 使用分层结构风格的基础平台子系统结构包含若干layer(层);其中,每一层实现一组基本概念以及与其相关的基本属性。
- 层与层之间的相互关系:
- 所有各层的实现不依赖其以上各层所提供的概念及其属性,只依赖其直接下层所提供的概念及属性;
- 每一层均对其上各层隐藏其下各层的存在。
分级结构风格的结构特征
- 使用分级结构风格的基础平台子系统结构包含若干 level(级);其中,每一级实现一组基本概念以及与其相关的基本属性。
- 级与级之间的相互关系:
- 所有各级的实现不依赖其以上各级所提供的概念及属性,只依赖其以下各级所提供的概念及属性。
分块结构风格的结构特征
- 使用分块结构风格的基础平台子系统结构包含若干module(模块);其中,每一块实现一组基本概念以及与其相关的基本属性。
- 块与块之间的相互关系:
- 所有各块的实现均可以任意引用其它各块所提供的概念及属性。
分层、分级、分块结构风格的关系
- 分层结构风格是一种特殊的分级结构风格;
- 分级结构风格是一种特殊的分块结构风格。
分层、分块结构风格的比较
- 分层结构风格
- 有利于实现基础平台子系统的可维护性、可扩展性、可移植性、部件可重用性等非功能性需求;
- 不利于提高基础平台子系统的时间和空间效率;
- 构造一个纯粹的分层结构将非常困难。
- 分块结构风格
- 构造一个分块结构是一种切合实际的做法;
- 有利于生成高效、紧凑的基础平台子系统可执行代码;
- 不利于实现基础平台子系统的灵活性。
常见的基础平台子系统结构风格(二)
- Multi-Mode Structural Style(多模式结构风格)
- Single-Mode Structural Style(单模式结构风格)
基本概念:Mode(模式)
所谓模式,简单地说,就是程序在运行过程中使用的、由硬件体系结构提供的CPU特权模式。
多模式结构风格的结构特征
- 使用多模式结构风格的基础平台子系统结构包含多个模式模块;这些模式模块或者是一个应用软件或者是基础平台子系统的一部分。
- 在使用多模式结构风格的基础平台子系统结构中,不同的模式模块在不同的CPU特权模式下运行。
单模式结构风格的结构特征
- 使用单模式结构风格的基础平台子系统结构仅仅包含一个模式模块;该模式模块由应用软件和基础平台子系统共同组成。
- 在使用单模式结构风格的基础平台子系统结构中,应用软件和基础平台子系统在同一CPU特权模式下运行。
多模式结构风格与单模式结构风格的比较
- 多模式结构风格
- 有利于实现基础平台子系统的可靠性、安全性等非功能性需求;
- 会降低基础平台子系统的性能;
- 在较高级别CPU特权模式下调试程序是一件困难的事情。
- 单模式结构风格
- 不会增加基础平台子系统的开发难度;
- 不会影响基础平台子系统的性能;
- 不利于实现基础平台子系统的可靠性、安全性等非功能性需求。
双模式基础平台子系统结构风格
概念:若一个基础平台子系统使用了双模式结构风格,则称该基础平台子系统为双模式基础平台子系统。
结构特征(总体结构风格)
双模式基础平台子系统其总体结构包含两个模式模块;它们分别在两种不同的CPU特权模式下运行。
习惯上,人们把双模式基础平台子系统的这两个模式 模块分别称为核外子系统和核心子系统;把核外子系统所使用 的CPU特权模式称为User Mode ,把核心子系统所使用的CPU 特权模式称为 Kernel Mode。
Modes of Execution
- User mode
- Less-privileged mode
- User programs typically execute in this mode
- System mode,control mode,or kernel mode
- More-privileged mode
- Kernel of the operating system
Microkernels(微核)结构
微核结构设计思想:尽最大努力剔除核心子系统中的多余成份,并把它们移到核外子系统中实现,核心子系统只实现一些必要的简单的概念及其属性,从而保持核心子系统简洁高效。
- Small operating system core
- Contains only essential operating systems functions
- Many services traditionally included in the operating system are now external subsystems
- device drivers
- file systems
- virtual memory manager
- windowing system
- security services
现代操作系统一般将 OS 划分为若干层次,再将 OS 的不同功能分别设置在不同的层次中。通常将一些与硬件紧密相关的模块(如中断处理程序等)、各种常用设备的驱动程序以及运行效率较高的模块(如时钟管理、进程调度和许多模块所共用的一些基本操作),都安排在紧密硬件的软件层次中,将它们常驻内存,即通常被称为OS内核。
这种安排方式的目的在于两方面:一是便于对这些软件进行保护,防止遭受其他应用程序的破坏;二是可以提高OS的运行效率。
内核是操作系统最基本、最核心的部分。实现操作系统内核功能的那些程序就是内核程序。
一些与硬件关联较紧密的模块,如时钟管理、中断管理、设备驱动等处于操作系统的最底层。其次是运行频率较高的程序,如进程管理、存储器管理和设备管理等。这两部分内容构成了操作系统的内核。这部分内容的指令操作工作在核心态。
内核是计算机上配置的底层软件,是计算机功能的延伸。主要包括四个方面。
- 时钟管理:在计算机的各种部件中,时钟是最关键的设备。时钟的第一功能是计时,操作系统需要通过时钟管理,向用户提供标准的系统时间。另外,通过时钟中断的管理,可以实现进程的切换。系统管理的方方面面无不依赖于时钟。
- 中断机制:中断机制是操作系统各项操作的基础。如键盘或鼠标信息的输入、进程的管理和调度、系统功能的调用、设备驱动、文件访问等,无不依赖于中断机制。可以说,现代操作系统是靠中断驱动的软件。中断机制中,只有一小部分功能属于内核,它们负责保护和恢复中断现场的信息,转移控制权到相关的处理程序。
- 原语:原语具有的特点,处于操作系统的最底层,是最接近硬件的部分;这些程序的运行具有原子性,其操作只能一气呵成(主要从系统安全性和便于管理考虑);这些程序的运行时间都较短,而且调用频繁。定义原语的直接方法就是关闭中断(关中断),让其所有动作不可分割地完成后再打开中断(开中断)。在核心态下执行,常驻内存。
- 系统控制的数据结构及处理:系统中用来登记状态信息的数据结构很多,如作业控制块、进程控制块(PCB)、设备控制块、各类链表、消息队列、缓冲区、空闲区登记表、内存分配表等。为了实现有效的管理,系统需要一些基本的操作,常见的操作有一下三种:进程管理、存储器管理和设备管理。
操作系统内核功能
-
Process Management:进程创建和终止、调度和分派、状态转换、同步和通信、管理PCB
-
Memory Management:为进程分配地址空间、对换、段/页管理
-
I/O Management:缓存管理、为进程分配 I/O 通道和设备
-
Typical Function of an OS Kernel (支撑功能)
-
Interrupt handling(中断处理)
-
Timing(时钟管理)
-
Primitive(原语): Atomic Operation
-
Accounting(统计)
-
Monitoring(监测)
-
1.5 补充
1.5.1 操作系统的运行机制
计算机系统中,通常CPU执行两种不同性质程序:一种是操作系统内核程序;另一种是用户自编程序(即系统外层的应用程序,或简称“应用程序”)。我们程序员写的程序就是“应用程序”,而实现操作系统的代码就是“内核程序”,由很多内核程序组成了“操作系统内核”(或简称“内核”),内核是操作系统最重要最核心的部分,也是最接近硬件的部分甚至可以说,一个操作系统只要有内核就够了(eg:Docker—>仅需Linux内核),操作系统的功能未必都在内核中,如图形化用户界面 GUI。
CPU有两种状态,核心态(又称核心态、内核态)和用户态(目态)。
处于内核态时,说明此时正在运行的是内核程序,此时可以执行特权指令和非特权指令。
处于用户态时,说明此时正在运行的是应用程序,此时只能执行非特权指令。
所谓特权指令,是指计算机中不允许用户直接使用的指令,如I/O指令、置中断指令、内存清零指令、存取用于内存保护的寄存器、送程序状态字到程序状态字寄存器等的指令。
拓展:CPU 中有一个寄存器叫 程序状态字寄存器(PSW),其中有个二进制位,1表示“核心态”,0表示“用户态”。
内核态->用户态:执行一条特权指令——修改PSW的标志位为“用户态”,这个动作意味着操作系统将主动让出CPU使用权。
用户态->内核态:由“中断”引发,硬件自动完成变态过程,触发中断信号意味着操作系统将强行夺回CPU的使用权。
核心态指令实际上包括系统调用类指令和一些针对时钟、中断和原语的操作指令。
注意:
操作系统内核需要运行在内核态。
操作系统的非内核功能运行在用户态。
所以采用微内核的话,需要频繁地在核心态和用户态之间切换。
1.5.2 中断和异常的概念
中断也称外中断,指来自CPU执行指令以外事件的发生,如设备发出的 I/O 结束中断,表示设备输入/输出处理已经完成,希望处理机能够向设备发下一个输入/输出请求,同时让完成输入/输出后的程序继续运行。时钟中断表示一个固定的时间片已到,让处理机处理计时、启动定时运行的任务等。这一类中断通常是与当前指令执行无关的事件,即它们与当前处理机运行的程序无关。
异常(内中断、例外、陷入(trap)),指源自CPU执行指令内部的事件,如程序的非法操作码、地址越界、算术溢出、虚存系统的缺页及专门的陷入指令等引起的事件。对异常的处理一般要依赖于当前程序的运行现场,而且异常不能被屏蔽,一旦出现应立即处理。
在合适的情况下,操作系统内核会把CPU的使用权主动让给应用程序,即从核心态变为用户态。但当操作系统需要CPU的使用权时,则需要采用中断机制。“中断”是让操作系统内核夺回CPU使用权的唯一途径。
有时候应用程序想请求操作系统内核的服务,此时会执行一条特殊的指令——陷入指令,该指令会引发一个内部中断信号。执行“陷入指令”,意味着应用程序主动地将CPU控制权还给操作系统内核。“系统调用”就是通过陷入指令完成的。
中断机制的基本原理:
不同的中断信号,需要用不同的中断处理程序来处理。当CPU检测到中断信号后,会根据中断信号的类型去查询“中断向量表”,以此来找到相应的中断处理程序在内存中的存放位置。
二、进程和调度
摘要
基础:进程描述及控制
实现:互斥与同步
避免:死锁与饥饿
解决:几个经典问题
关于:进程通信
策略:进程调度
2.1 进程描述和控制
2.1.1 操作系统的主要需求
- 交错执行多个进程,以最大限度地提高处理器利用率,同时提供合理的响应时间
- 为进程分配资源
- 支持进程间通信和用户创建进程
2.1.2 程序的执行顺序
程序顺序执行的特征
程序执行有固定的时序
- 顺序性:指处理机严格地按照程序所规定的顺序执行,即每一操作必须在下一个操作开始之前结束。
- 封闭性:即程序运行时独占全机资源,资源的状态(除初始状态外)只有本程序才能改变它,程序一旦开始执行,其执行结果不受外界因素影响。
- 可再现性:指只要程序执行时的环境和初始条件相同,当程序重复执行时,不论它是从头到尾不停顿地执行,还是“走走停停”地执行,都可获得相同的结果。
程序并行执行的特征
- 间断性:程序在并发执行时,由于它们共享系统资源,以及为完成同一项任务而相互合作,致使在这些并发执行的程序之间形成了相互制约的关系。
- 失去封闭性(主要由共享资源引起):当系统中存在着多个可以并发执行的程序时,系统中的各种资源将为它们所共享,致使其中任一程序在运行时,其他环境都必然会受到其他程序的影响。
- 不可再现性:(并发程序可能对共享资源做不同的修改)程序在并发执行时,由于失去了封闭性,也将导致其又失去可再现性。
程序在并发执行时,由于失去了封闭性,其计算结果必将与并发程序的执行速度有关,从而使程序的执行失去了可再现性。换言之,程序经过多次执行后,虽然它们执行时的环境和初始条件相同,但得到的结果却各不相同。
程序并发执行条件(Bernstein条件)
- R(P1) ∩ W(P2) ∪ W(P1) ∩ R(P2) ∪ W(P1) ∩ W(P2) = {}
进程的几个定义
在多道程序环境下,程序的执行属于并发执行,此时它们将失去其封闭性,并具有间断性,以及其运行结果不可再现性的特征,所以,通常的程序是不能参与并发执行的。为了能使程序并发执行,并对并发执行的程序加以描述和控制,引入了“进程”的概念。引入进程的目的是为了使其进程实体能和其他进程实体并发执行,实现操作系统的并发性和共享性(最基本的两个特征)。
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码本身,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
-
也被称为 task
-
一个正在执行的程序
-
一个正在计算机上执行的程序实例
-
能分配给处理器并由处理器执行的实体
-
由一组执行的指令,一个当前状态和一组相关的系统资源表征的活动单元
-
进程是程序在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单位
-
进程是可并发执行的程序在一个数据集合上的运行过程
-
可以被跟踪
- 执行指令的顺序的队列
程序:是静态的,就是个存放在磁盘里的可执行文件。
进程:是动态的,是程序的一次执行过程。
同一个程序多次执行会对应多个进程。
2.1.3 进程的特点
- Dynamic(动态性) :进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。
- Concurrency(并发性) :指多个进程实体同时存于内存中,能在一段时间内运行。并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是是程序能与其他进程的程序并发执行,以提高资源利用率。
- Independent(独立性) :指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序,都不能作为一个独立的单位参与运行。
- Asynchronous(异步性):由于进程的相互制约,使得进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,所以在操作系统中必须配置相应的=进程同步机制。
- 结构性:每个进程都配置一个PCB对其进行描述。
进程的结构——PCB、程序段、数据段
为了使参与并发执行的每个程序(含数据)都能独立地运行,在操作系统中必须为之配置一个专门的数据结构,称为进程控制块(PCB)。当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的“身份证号”—— PID(Process ID,进程ID)。操作系统区分各个进程就是通过PID以及进程所属用户ID(UID)来区分的。所以操作系统要记录PID、UID,还要记录给进程分配了哪些资源(如:分配了多少内存、正在使用哪些I/O设备、正在使用哪些文件),还要记录进程的运行情况(如:CPU使用时间、磁盘使用情况、网络流量使用情况等)。这些信息都被保存在PCB中。操作系统需要对各个并发运行的进程进行管理,但凡管理时所需要的信息,都会被放在PCB中。操作系统就是利用PCB来描述进程的基本情况和活动过程,进而控制和管理进程。PCB是进程存在的唯一标志。
- 程序代码 Programs
- 数据集 Datas
- 进程控制块 PCB(Process Control Block)
由程序段、相关的数据段和PCB三部分便构成了进程实体(又称进程映像)。
进程映像是静态的,进程则是动态的
引入进程实体后,我们可以把传统操作系统中的进程定义为:“进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。”
要注意这里说的系统资源是指处理机、存储器和其他设备服务于某个进程的“时间”,例如把处理机资源理解为处理机的时间片才是准确的。因为进程是这些资源分配和调度的独立单位,即“时间片”分配的独立单位,这就决定了进程一定是一个动态的、过程性的概念。
2.1.4 进程的状态
由于多个进程在并发执行时共享系统资源,致使它们在运行过程中呈现间断性的运行规律,所以进程在其生命周期内可能具有多种状态。一般而言,每个进程至少应处于以下三种基本状态之一:
- 就绪态。进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即运行。系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。(其他资源✅ CPU❌)
- 执行态。进程正在处理机上运行。在单处理机环境下,每个时刻最多只有一个进程处于运行态。而在多处理机系统中,则有多个进程处于执行状态。(其他资源✅ CPU✅)
- 阻塞态。又称等待态或封锁态。指正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行时的状态,亦即进程的执行受到阻塞。此时引起进程调度,OS把处理机分配给另一个就绪进程,而让受阻进程处于暂停状态,这种暂停状态称为阻塞态。通常系统将处于阻塞态的进程也排成一个队列,称该队列为阻塞队列。实际上,在较大的系统中,为了减少队列操作的开销,提高系统效率,根据阻塞原因的不同,会设置多个阻塞队列。(其他资源❌ CPU❌)
区别就绪态和阻塞态:就绪态是指进程仅缺少处理机,只要获得处理机资源就立即运行;而阻塞态是指进程需要其他资源(除了处理机)或等待某一事件。之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪态的;而其他资源(如外设)的使用和分配或某一事件的发生(如 I/O 操作的完成)对应的时间相对来说很长,进程转换到等待态的次数也相对较少。
两状态模型
非运行态 ⇔ \Leftrightarrow ⇔ 运行态
- 并非所有进程只要 Not-running 就处于 ready (就绪) ,有的需要 blocked(阻塞)等待 I/O 完成
- Not-running 又可分为 ready 和 blocked 两种状态
五状态模型
为了满足进程控制块(PCB)对数据及操作的完整性要求以及增强管理的灵活性,通常在系统中又引入了两种常见的状态。
- 创建态。进程正在被创建,操作系统为进程分配资源、初始化 PCB。尚未转到就绪态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息;然后又系统为该进程分配运行时所必需的资源;最后把该进程转入就绪态。
- 结束态。进程正从系统中撤销,操作系统会回收进程拥有的资源、撤销 PCB。可能是进程正常结束或其他原因中断退出运行。进程需要结束运行时,系统首先必须将该进程置为结束态,然后进一步处理资源释放和回收等。
进程PCB中,会有一个变量 state 来表示进程的当前状态。如:1表示创建态、2表示就绪态、3表示运行态…为了对同一个状态下的各个进程进行统一的管理,操作系统会将各个进程的PCB组织起来。
- 运行态:占用处理机(单处理机环境中,某一时刻仅一个进程占用处理机)
- 就绪态:进程做好了准备,随时可以处于运行态
- 阻塞态:等待某事件发生才能执行,如等待I/O完成等
- 新建态:已创建 PCB 但还未加载到内存中的进程。(等待某事件发生才能执行,如等待I/O完成等)
- 退出态:因停止或取消,被OS从执行状态释放
- Null→New:新创建进程首先处于新状态
- New→Ready:OS接纳新状态进程为就绪进程
- Ready→Running:OS只能从就绪进程中选一个进程执行
- Running→Exit:执行状态的进程执行完毕,或被取消,则转换为退出状态
- Running→Ready:分时系统中,时间片用完,或优先级高的进程到来,将终止优先级低的进程的执行
- Running→Blocked:执行进程需要等待某事件发生。通常因进程需要的系统调用不能立即完成,而阻塞
- Blocked→Ready:当阻塞进程等待的事件发生,就转换为就绪状态
- Ready→Exit:某些系统允许父进程在任何情况下终止其子进程。若一个父进程终止,其子孙进程都必须终止。
- Blocked→Exit:同前
进程的挂起
(不释放CPU,可能释放内存,移到外存去)
Swapping (对换技术,交换技术):将内存中暂时不能运行的进程,或暂时不用的数据和程序,Swapping-out到外存,以腾出足够的内存空间,把已具备运行条件的进程,或进程所需要的数据和程序,Swapping-in内存。
原因:当所有进程都处于阻塞态时,处理器处于休闲状态。此时将某个进程的一部分或者全部移入磁盘,然后从挂起队列加载一个新进程,放入内存中运行。
- 处理器速度快于 I/O,因此所有进程都可能在等待 I/O
- 将这些进程交换到磁盘以释放更多内存
- 交换到磁盘后,阻塞状态变为暂停状态
单挂起状态的bug:
阻塞态:在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量将它唤醒。(释放CPU,不释放内存)
- 创建→活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创建的必要操作后,相应的系统进程将进程的状态转换为活动就绪状态。
- 创建→静止就绪:考虑到系统当前资源状况和性能的要求,不分配给新建进程所需资源,主要是内存,相应的系统将进程状态转为静止就绪状态,被安置在外存,不参与调度,此时进程创建工作尚未完成。
引起进程挂起的原因
事件 | 说明 |
---|---|
交换 | 操作系统需要释放足够的内存空间,以调入并执行处于就绪态的进程 |
其他OS原因 | 操作系统可能挂起后台进程或工具程序进程,或挂起可能会导致问题的进程 |
交互式用户请求 | 用户希望挂起一个程序的执行,以便进行调试或关联资源的使用 |
定时 | 进程可被周期性地执行(如记账或系统监视进程),并在等待下一个时间间隔时挂起 |
父进程请求 | 父进程可能会希望挂起后代进程的执行,以检查或修改挂起的进程,或协调不同后代进程之间的行为 |
被挂起进程的特征
- 不能立即执行
- 可能是等待某事件发生。若是,则阻塞条件独立于挂起条件,即使阻塞事件发生,该进程也不能执行
- 使之挂起的进程为:自身、其父进程、OS
- 只有挂起它的进程才能使之由挂起状态转换为其他状态
区分两个概念:
- ?进程是否等待事件,阻塞与否
- ?进程是否被换出内存,挂起与否
4种状态组合:
- Ready:进程在内存,准备执行
- Blocked:进程在内存,等待事件
- Ready,Suspend:进程在外存,只要调入内存即可执行
- Blocked,Suspend:进程在外存,等待事件
处理机可调度执行的进程有两种:
- 新创建的进程
- 或换入一个以前挂起的进程
- 通常为避免增加系统负载,系统会换入一个以前挂起的进程执行。
具有挂起状态的进程状态转换
- Blocked → Blocked,Suspend:OS 通常将阻塞进程换出,以腾出内存空间。
- Blocked,Suspend → Ready,Suspend:当 Blocked,Suspend 进程等待的事件发生时,可以将其转换为 Ready,Suspend 。
- Ready,Suspend → Ready:OS 需要调入一个进程执行时。
- Ready → Ready,Suspend:一般,OS 挂起阻塞进程。但有时也会挂起就绪进程,释放足够的内存空间。
- New → Ready,Suspend(New→Ready):新进程创建后,可以插入到 Ready 队列或 Ready,Suspend 队列。若无足够的内存分配给新进程,则需要 New → Ready,Suspend 。
- Blocked,Suspend → Blocked:当 Blocked,Suspend 队列中有一个进程的阻塞事件可能会很快发生,则可将一个 Blocked,Suspend 进程换入内存,变为 Blocked 。
- Running → Ready,Suspend:当执行进程的时间片用完时,会转换为 Ready 或,一个高优先级的 Blocked,Suspend 进程正好变为非阻塞状态,OS 可以将执行进程转换为 Ready,Suspend 状态。
- All → Exit:通常,Running → Exit。但某些 OS 中,父进程可以终止其子进程,使任何状态的进程都可转换为退出状态。
2.1.5进程的描述
OS如何感知进程、控制进程及其所用的系统资源?
操作系统控制计算机系统内部的事件,为处理器执行进程进行调度和分派,给进程分配资源,并响应用户程序的基本服务请求。因此,我们可把操作系统视为管理系统资源的实体。
图3.10显示了这一概念。在多道程序设计环境中,虚存中已有许多已创建进程(P,P2,…,Pn),每个进程在执行期间都需要访问某些系统资源,包括处理器、I/O 设备和内存。图中,进程 P 正在运行,它至少有一部分已在内存中,并控制了两个 I/O 设备:进程卫,也在内存中,但由于正在等待分配给 P 的 I/O 设备而被阻塞;进程 P,已被换出,因此处于挂起态。
操作系统的控制结构
在计算机系统中,对于每个资源和每个进程都设置了一个数据结构,用于表征某实体,我们称之为资源信息表或进程信息表,其中包含了资源或进程的标识、描述、状态等信息以及一批指针。OS管理的这些数据结构一般分为以下四类:内存表、设备表、文件表和用于进程管理的进程表,通常进程表又被称为进程控制块PCB。
- 操作系统掌握每个进程和资源的当前状态;
- 构造并维护其管理的每个实体的信息表。
内存表
用于跟踪内(实)存和外(虚)存。内存的某些部分为操作系统保留,剩余部分供进程使用,外存中保存的进程使用某种虚存或简单的交换机制。内存表必须包含如下信息:
- 分配给进程的内存。
- 分配给进程的外存。
- 内存块或虚存块的任何保护属性,如哪些进程可以访问某些共享内存区域。
- 管理虚存所需要的任何信息。
I/O 表(设备表)
- I/O 设备是可用的还是已分配的
- I/O 操作的状态
- 作为I/O传送的源与目标的内存单元
文件表
- 文件是否存在
- 文件在外存中的位置
- 当前状态和其他属性的信息
- 大部分信息(非全部信息)可能由文件管理系统维护和使用
进程表
- 进程位置
- 管理进程必要的属性
- 进程 ID
- 进程状态
- 在内存中的位置
进程控制结构
PCB作为进程实体的一部分,记录了操作系统所需的,用于描述进程的当前情况以及管理进程运行的全部信息,是操作系统中最重要的记录型数据结构。它使一个在多道程序环境下不能独立运行的程序(含数据)成为一个能独立运行的基本单位,一个能与其他进程并发执行的进程。
进程位置
- 进程包括要执行的程序集。
- 局部变量和全局变量的数据位置
- 任何已定义的常量。
- 堆栈。
- 进程控制块(PCB)。
- 属性集合
- 用于进程的中断和恢复
- 进程映像(Process image)
- 程序、数据、堆栈和属性的集合。
进程映像中的典型元素
项目 | 说明 |
---|---|
用户数据 | 用户空间中的可修改部分,包括程序数据、用户栈区域和可修改的程序 |
用户程序 | 待执行的程序 |
栈 | 每个进程有一个或多个后进先出(LIFO)栈,栈用于保存参数、过程调用地址和系统调用地址 |
进程控制块 | 操作系统感知进程、控制进程的数据结构 |
进程映像 = 程序 + 数据 + 栈 + 属性
进程控制块 Process Control Block
-
简称PCB:是操作系统控制和管理进程时所用的基本数据结构
-
作用
- PCB是相关进程存在于系统中的唯一标志;系统根据PCB而感知相关进程的存在。
- 能实现间断性运行方式。在多道程序环境下,程序是采用停停走走间断性的运行方式运行的。当进程因阻塞而暂停运行时,它必须保留自己运行时的CPU现场信息,再次被调度运行时,还需要恢复其CPU现场信息。在有了PCB后,系统就可将CPU现场信息保存在被中断进程的PCB中,供该进程再次被调度执行时恢复CPU现场时使用。
- 提供进程管理所需信息。
- 提供进程调度所需信息。
- 实现了其他进程的同步通信。
-
内容:通常情况下,PCB 包含 dentifiers(标识)、状态、控制、指针等多种信息。
- 进程标识符。进程标识符用于唯一地标识一个进程。其中包括外部标识符(创建者提供)和内部标识符(OS设置)。
- 处理机状态。也称为处理机的上下文。主要由处理机中各种寄存器中的内容组成,包括通用寄存器、指令计数器、程序状态字PSW、用户栈指针等。
- 进程调度信息。包括进程状态、进程优先级、事件和进程调度所需的其他信息。
- 进程控制信息。包括程序和数据的地址、进程同步和通信机制、资源清单和链接指针。
-
进程标识信息:存储在PCB中的数字标识符,
- 标识符:进程 ID,父进程 ID,用户 ID
-
进程状态信息(处理器状态信息):存储所有的程序状态字(PSW)
-
用户可见寄存器:用户可见寄存器是处理器在用户模式下执行机器语言时可以访问的寄存器。通常有8~32个此类寄存器,而在一些RISC实现中,这种寄存器会超过100个
-
控制和状态寄存器:用于控制处理器操作的各种处理器寄存器
- 程序计数器:包含下一条待取指令的地址
- 条件码:最近算数活逻辑运算的结果(如符号、零、进位、等于、溢出)
- 状态信息:包括中断运行/禁用标志、执行模式
-
栈指针:每个进程有一个或多个与之相关联的后进先出(LIFO)系统栈。栈用于保存参数和过程调用或系统调用的地址,栈指针指向栈顶
-
-
进程控制信息:操作系统协调各种活动进程的额外信息
-
调度和状态信息:这是操作系统执行调度功能所需的信息
- 进程状态:定义待调度执行的进程的准备情况(如运行态、就绪态、等待态、停止态)
- 优先级:描述进程调度优先级的一个或多个域。某些系统需要多个值(如默认、当前、最高许可)
- 调度相关信息:具体取决于所用的调度算法,如进程等待的时间总量和进程上次运行的执行时间总量
- 事件:进程在继续执行前等待的事件标识
-
数据结构:进程可以以队列、环或其他结构链接到其他进程。
-
进程间通信:各种标记、信号和信息可与两个无关进程间的通信关联。进程控制块中可维护某些或全部此类信息
-
进程特权:进程根据其可以访问的内存和可执行的指令类型来赋予特权。此外,特权也适用于系统实用程序和服务的使用
-
存储管理:该部分包括指向描述分配给该进程的虚存的段表和/或页表的指针
-
资源所有权和使用情况:指示进程控制的资源,如一个打开的文件。还可包含处理器或其他资源的使用历史,调度器需要这些信息
-
PCB 的作用
PCB 是操作系统中最重要的数据结构,包含操作系统所需进程的全部信息
PCB 集合定义了 OS 的状态
如何在发生错误和变化时,保护 PCB,具体表现为两个问题:
- 一个例程(如中断处理程序)中的错误可能会破坏进程控制块,进而破坏系统对受影响进程的管理能力
- 进程控制块结构或语义中的设计变化可能会影响到操作系统中的许多模块
Process Control Primitives(原语)
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。在操作系统中,一般把进程控制用的程序段称为原语,用原语来实现进程控制。原语的特点是执行期间不允许中断,它是一个不可分割的基本单位。
- Process Switch,进程切换
- Create and Terminate, 创建与终止
- Block and Wakeup, 阻塞与唤醒
- Suspend and Activate, 挂起与激活
原语是一种特殊的程序,它的执行具有原子性。
进程创建步骤
- 为新进程分配一个唯一的进程标识符。
- 为进程分配空间。
- 初始化进程控制块
- 设置正确的链接
- 创建活扩充其他数据结构
切换进程的时机(进程执行的中断机制)
- 中断:来自当前执行指令的外部,用于对异步外部事件的反应
- 时钟中断:操作系统确定当前运行进程的执行时间是否已超过最大允许时间段[时间片 time slice]
- I/O 中断:操作系统确定是否已发生 I/O 活动、
- 内存失效:处理器遇到一个引用不在内存的字的虚存地址时,操作系统就必须从外存中把包含这一引用的内存块(页或段)调入内存。
- 陷阱 trap:与当前执行指令相关,处理一个错误或一个异常条件
- 操作系统确定错误或异常条件是否致命。致命时,当前正运行进程置为退出态,并切换进程;不致命时,操作系统可能会尝试恢复程序,或简单地通知用户。
- 系统调用/管理程序调入 Supervisor call:显示请求,用于调用操作系统函数
- 例如执行一个 I/O 请求指令(如打开文件),该系统调用会转移到作为操作系统代码一部分的一个例程。
- 使用系统调用时会将用户进程设置为阻塞态。
进程模式切换
在中断阶段,处理器会根据出现的中断信号来检查中断是否出现。无中断出现时,处理器会继续取指阶段,并在当前进程中取当前程序的下一条指令;出现中断时,处理器会做如下工作:
- 将程序计数器置为中断处理程序的开始地址
- 从用户模式切换到内核模式,以便中断处理代码包含特权指令。
进程状态的变化
模式切换可在不改变运行态进程的状态的情况下出现。此时保存上下文并在以后恢复上下文仅需很少的开销。但是,若当前正运行进程将转换为另一状态(就绪、阻塞等),则操作系统必须使环境产生实质性的变化。完整的进程切换步骤如下:
- 保存处理器的上下文,包括程序计数器和其他寄存器。
- 更新当前处于运行态
进程切换与模式切换
-
进程切换,是作用于进程之间的一种操作。当分派程序收回当前进程的 CPU 并准备把它分派给某个就绪进程时,该操作将被引用。
- 引起进程切换的事件:当前进程时间片到、有更高优先级的进程到达、当前进程主动阻塞、当前进程终止。
- 切换原语:将运行环境信息存入 PCB;PCB 移入相应队列;选择另一个进程执行,并更新其 PCB ;根据 PCB 恢复新进程所需的运行环境。
-
模式切换,是进程内部所引用的一种操作。当进程映像所包含的程序引用核心子系统所提供的系统调用时,该操作将被引用。
进程切换是指处理机从一个进程的运行转到另一个进程上运行。
处理机模式切换时,处理机逻辑上可能还在同一进程中运行。若进程因中断或异常进入核心态运行,执行完后又回到用户态刚被中断的程序运行,则操作系统只需恢复进程进入内核时所保存的CPU现场,而无须改变当前进程的环境信息。但若要切换进程,当前运行进程改变了,则当前进程的环境信息也需要改变。运行环境信息称为进程的上下文;处理机状态信息称为处理机的上下文。
进程创建
允许一个进程创建另一个进程,此时创建者称为父进程,被创建的进程称为子进程。子进程可以继承父进程所拥有的资源。当子进程被撤销时,应将其从父进程那里获得的资源归还给父进程。此外,在撤销父进程时,必须同时撤销其所有的子进程。
-
创建进程的原因
事件 说明 新的批处理作业 磁带或磁盘中的批处理作业控制流通常会提供给操作系统。当操作系统准备接收新工作时,将读取下一个作业控制命令 交互登录 终端用户登录到系统 为提供服务而由操作系统创建 操作系统可以创建一个进程,代表用户程序执行一个功能,使用户无须等待(如控制打印的进程) 由现有进程派生 基于模块化的考虑或开发并行性,用户程序可以指示创建多个进程 -
创建步骤 creat() 原语
- 为进程分配一个唯一标识号ID: 主进程表中增加一个新的表项
- 为进程分配空间 : 用户地址空间、用户栈空间、PCB空间。若共享已有空间,则应建立相应的链接。
- 初始化 PCB:进程标识、处理机状态信息、进程状态
- 建立链接 :若调度队列是链表,则将新进程插入到就绪或(就绪,挂起) 链表
- 建立或扩展其他数据结构
进程创建完成后会进入就绪队列。
设备分配是通过在系统中设置相应的数据结构实现的,不需要创建进程,这是操作系统中I/O核心子系统的内容。
进程终止
-
导致进程终止的原因
事件 说明 正常完成 进程自行执行一个操作系统服务调用,表示它已经结束运行 超过时限 进程运行时间超过规定的时限。可以测量多种类型的时间,包括总运行时间(“挂钟时间”)、花费在执行上的时间,以及对于交互进程从上一次用户输入到当前时刻的时间总量 无可用内存 系统无法满足进程需要的内存空间 超出范围 进程试图访问不允许访问的内存单元 保护错误 进程试图使用不允许使用的资源或文件,或试图以一种不正确的方式使用,如往只读文件中写 算术错误 进程试图进行被禁止的计算,如除以零或存储大于硬件可以接纳的数字 时间超出 进程等待某一事件发生的时间超过了规定的最大值 I/O失败 在输入或输出期间发生错误,如找不到文件、在超过规定的最多努力次数后仍然读/写失败(如遇到磁带上的一个坏区时)或无效操作(如从行式打印机中读) 无效指令 进程试图执行一个不存在的指令(通常是由于转移到了数据区并企图执行数据) 特权指令 进程试图使用为操作系统保留的指令 数据误用 错误类型或未初始化的一块数据 操作员或操作系统干涉 由于某些原因,操作员或操作系统终止进程(如出现死锁时) 父进程终止 当一个父进程终止时,操作系统可能会自动终止该进程的所有子进程 父进程请求 父进程通常具有终止其任何子进程的权力 -
进程终止步骤 destroy() 原语
- 根据被终止进程的标识符ID,找到其PCB,读出该进程的状态;
- 若该进程为执行状态,则终止其执行,调度新进程执行;
- 若该进程有子孙进程,则立即终止其所有子孙进程;
- 将该进程的全部资源,或归还给其父进程,或归还给系统;
- 将被终止进程(的 PCB)从所在的队列中移出,等待其它程序来搜集信息。
先对其所占有的资源执行回收操作,然后再撤销PCB。
进程阻塞的原因
- 请求系统服务
- 启动某种操作:如I/O
- 新数据尚未到达
- 无新工作可做
进程阻塞是进程自身的一种主动行为(挂起是被动的),也因此只有处于核心态的进程(拥有CPU),才可能将其转为阻塞态。阻塞原语(Block原语)和唤醒原语(Wakeup原语)是一对作用恰好相反的原语,必须成对使用。
- 阻塞原语 block()
- 当出现阻塞事件,进程调用阻塞原语将自己阻塞。状态变为“阻塞状态”,并进入相应事件的阻塞队列
- 唤醒原语wakeup()
- 当阻塞进程期待的事件发生,有关进程调用唤醒原语,将等待该事件的进程唤醒。状态变为Ready,插入就绪队列
- 挂起原语suspend()
- 当出现挂起事件,系统利用挂起原语将指定进程或阻塞状态进程 挂起。进程从内存换到外存,状态改变:Ready → Ready,Suspend; Blocked → Blocked,Suspend,插入相应队列
- 激活原语active()
- 当激活事件发生,系统利用激活原语将指定进程激活。进程从外 存换入到内存,状态改变:Ready,Suspend → Ready ; Blocked,Suspend → Blocked ,插入相应队列
无论哪个进程控制原语,要做的无非三类事情:
- 更新PCB中的信息(修改进程状态,保存/恢复运行环境)
- 将PCB插入合适的队列
- 分配/回收资源
2.1.6 线程
OS 中引入进程的目的是为了使多个程序能并发执行,以提高资源利用率和系统吞吐量,在OS中引入线程,则是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性,增加了并发度。
进程与线程
进程的两个特点
- 资源所有权
- 调度/执行
为区分这两个特点,我们通常将分派的单位称为线程或轻量级进程(Light Weight Process, LWP),而将拥有资源所有权的单位称为进程(process)或任务(task)。
在多线程环境中,进程定义为资源分配单元和一个保护单元。与进程相关联的有:
- 容纳进程映像的虚拟地址空间
- 对处理器、其他进程(用于进程间通信)、文件和 I/O 资源(设备和通道)的受保护访问。一个进程中可能有一个或多个线程,每个线程都有:
- 一个线程执行状态(运行、就绪…)
- 未运行时保存的线程上下文;线程可视为在进程内运行的一个独立程序计数器。
- 一个执行栈
- 每个线程用于局部变量的一些静态存储空间。
- 与进程内其他线程共享的内存和资源的访问。
使用线程的好处
- 在已有进程中创建一个新线程的时间,远少于创建一个全新进程的时间。
- 终止线程要比终止进程所化的时间少。
- 同一进程内线程间切换的时间,要少于进程间切换的时间。
- 线程提高了不同程序间通信的效率。在多数操作系统中,独立进程间通的通信需要内核接入,以提供保护和通信所需的机制。但是,由于同一进程中的多个线程共享内存和文件,因此它们无需调用内核就可互相通信。
线程
- 在支持线程的操作系统中,调度和分派是在线程基础上完成的,因此大多数与执行相关的信息可以保存在线程级的数据结构中。
- 挂起操作会把一个进程的地址空间换出内存,而一个进程中的所有线程共享同一个地址空间,因此它们会同时被挂起。
- 类似地,进程终止时会使得进程中的所有线程都终止。
多线程
- 多线程是指操作系统在单个进程内支持多个并发执行路径的能力。
- MS-DOS 是支持单用户进程和单线程的操作系统例子。
- UNIX 支持多用户进程,但每个进程仅支持一个线程。
- Windows 2000, Solaris, Linux, Mach, and OS/2 支持一个进程有多个线程。
线程状态
- 线程的主要状态有:运行态、就绪态和阻塞态。
- 4 种与线程状态改变相关的基本操作
- 派生 Spawn:典型情况下,在派生一个新进程时,同时也会该进程派生一个线程。随后,进程中的线程可在同一进程中派生另一个线程,并为新线程提供指令指针和参数;新线程拥有自己的寄存器上下文和栈空间,并放在就绪队列中。
- 阻塞 Block:线程需要等待一个事件时会被阻塞(保存线程的用户寄存器、程序计数器和栈指针),处理器转而执行另一个就绪线程。
- 解除阻塞 Unblock:发生阻塞一个线程的事件时,会将该线程转移到就绪队列中。
- 结束 Finish:一个线程完成后,会释放其寄存器上下文和栈。
用户级线程 User-Level Threads
- 管理线程的所有工作都由应用程序完成
- 内核意识不到线程的存在
- 描述此类线程的数据结构以及控制此类线程的原语在核外子系统中实现。
内核级线程 Kernel-Level Threads
- W2K, Linux, and OS/2 are examples of this approach.
- 内核维护进程和线程的上下文信息。
- 调度是在线程的基础上完成的。
- 描述此类线程的数据结构以及控制此类线程的原语在核心子系统中实现。
组合/混合 Combined Approaches
- Solaris 是这种方法的一个例子。
- 线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。
- 同一个应用程序中的多个线程可在多个处理器上并行地运行,某个会引起阻塞的系统调用不会阻塞整个进程。
进程和线程间的关系
线程:进程 | 描述 | 实例系统 |
---|---|---|
1:1 | 执行的每个线程是唯一的进程,它有自己的地址空间和资源。 | 传统UNIX |
M:1 | 一个进程定义了一个地址空间和动态资源所有权。可以在该进程中创建和执行多个线程。 | Windows NT、Solaris、Linux、OS/2、OS/390、MACH等。 |
1:M | 一个线程可以从一个进程环境迁移到另一个进程环境。允许线程轻松地在不同系统中移动。 | Ra(Clouds)、Emerald |
M:N | 结合了 M:1 和 1:M 的属性 | TRIX |
系统实例分析
UNIX的进程管理
Linux的进程管理
Linux 中的进程或任务由一个 task_struct 数据结构表示。task_struct 数据结构包含以下各类信息:
- 状态:进程的执行状态(执行态、就绪态、挂起态、停止态、僵死态。这些状态将在下面进一步讲述)。
- 调度信息:Linux 调度进程所需要的信息。一个进程可能是普通的或实时的,并具有优先级。实时进程在普通进程前调度,在每类中使用相关的优先级。一个计数器会记录允许进程执行的时间量。
- 标识符,每个进程都有唯一的一个进程标识符,以及用户标识符和组标识符。组标识符用于给一组进程指定资源访问特权。
- 进程间通信:Linux 支持 UNIX SVR4 中的 IPC 机制。
- 链接:每个进程都有一个到其父进程的链接及到其兄弟进程(与它有相同的父进程)的链接,以及到所有子进程的链接。
- 时间和计时器:包括进程创建的时刻和进程所消耗的处理器时间总量。一个进程可能还有一个或多个间隔计时器,进程通过系统调用来定义间隔计时器,计时器期满时,会给进程发送一个信号。计时器可以只用一次或周期性地使用。
- 文件系统:包括指向被该进程打开的任何文件的指针和指向该进程当前日志与根目录的指针。
- 地址空间:定义分配给该进程的虚拟地址空间。
- 处理器专用上下文:构成该进程上下文的寄存器和栈信息。
图4.15给出了一个进程的执行状态,如下所示:
- 运行:这个状态值对应于两个状态。一个运行进程要么正在执行,要么准备执行。
- 可中断:这是一个阻塞态,此时进程正在等待一个事件(如一个 I/O 操作)的结束、一个可用的资源或另一个进程的信号。
- 不可中断:这是另一个阻塞态。它与可中断状态的区别是,在不可中断状态下,进程正在等待一个硬件条件,因此不会接收任何信号。
- 停止:进程被中止,并且只能由来自另一个进程的主动动作恢复。例如,一个正在被调试的进程可被置于停止态。
- 僵死:进程已被终止,但由于某些原因,在进程表中仍然有其任务结构。
2.2 进程调度概述
在多道程序设计系统中,内存中有多个进程。调度确定了哪个进程须等待、哪个进程能继续运行,因此会影响系统的性能。这一点可在图9.3中看出,该图给出了进程状态转换过程中所涉及的队列。本质上说,调度属于队列管理(managing queues)问题,用于在排队环境中减少延迟并优化性能。
调度类型
- 按 OS 的类型划分:
- 批处理调度、分时调度、实时调度、多处理机调度
- 按调度的层次划分:
- Long-term scheduling(长程调度)
- Medium-term scheduling(中程调度)
- Short-term scheduling(短程调度)
还有一种是 I/O 调度,决定可用 I/O 设备处理哪个进程挂起的 I/O 请求
长程调度
又称为高级调度、作业调度,它为被调度作业或用户程序创建进程、分配必要的系统资源,并将新创建的进程插入就绪队列,等待Short-term scheduling。
- 采用交换技术的系统将新创建的进程插入(就绪,挂起)队列,等待Medium-term scheduling 。
- 批处理系统中,作业进入系统后,先驻留在磁盘上(批处理队列中)。长程调度从该队列中选择作业,为之创建进程。
中程调度
又称为中级调度,它调度换出到磁盘的进程进入内存,准备执行
- 中程调度配合对换技术使用。
- 其目的是为了提高内存的利用率和系统吞吐量。
- 在多道程序度允许的情况下,从外存选择一个挂起 状态的进程调度到内存(换入)。
短程调度
- 又称为进程调度、低级调度,调度内存中的就绪进程执行。
- 也称为分派程序(dispatcher):决定就绪队列哪个进程将获得处理机。
- 执行得最为频繁。
- 导致当前进程阻塞或抢占当前运行进程的事件发生时,调用短程调度程序。这类事件包括:
- 时钟中断 Clock interrupts
- IO中断 I/O interrupts
- 操作系统调用 Operating system calls
- 信号 Signals(如信号量)
2.3 进程调度准则(目标)
- Response time(响应时间)
- 对一个交互进程来说,这指从提交一个请求到开始接收响应之间的时间间隔。通常情况下,进程在处理该请求的同时,会开始给用户产生一些输出。因此从用户的角度来看,相对于周转时间,这是一种更好的度量。该调度原则会试图实现较低的响应时间,并在可接受的响应时间范围内,使可交互的用户数量最大。
- 常用于评价分时系统的性能。
- Throughput(系统吞吐量)
- 单位时间内系统所完成的作业数
- 用于评价批处理系统的性能
- Processor efficiency(处理机效率)
- 这是处理器处于忙状态的时间百分比。
- 对昂贵的共享系统来说,这个规则很重要。
- 在单用户系统和其他一些系统如实时系统中,该规则与其他规则相比不太重要。
- Fairness(公平性,防止进程饥饿)
- 没有来自用户或其他系统的指导时,进程应被平等地对待,没有进程处于饥饿状态。
- Turnaround time(周转时间)
- 是指从作业提交给系统开始,到作业完成为止的这段时间间隔(也称为作业周转时间)
- 常用于评价批处理系统的性能
- Deadlines(截止时间)
- 是指某任务必须开始执行的最迟时间(Starting deadline ),或必须完成的最迟时间(Completion deadline )
- 常用于评价实时系统的性能
- Balancing Resource(资源平衡)
- 调度策略使系统中的所有资源都处于忙状态,优先调度较少使用紧缺资源的进程。
- 适用于长程调度和中程调度。
- Priorities 强制优先级
- 指定进程的优先级后,调度策略应优先选择高优先级的进程。
2.4 进程调度算法
决策模式(decision mode)说明选择函数开始执行的瞬间的处理方式,通常分为两类:
-
Nonpreemptive(非剥夺方式) --非抢占
-
在这种情况下,一旦进程处于运行状态,就会不断执行直到终止。进程要么因为等待UO,要么因为请求某些操作系统服务而阻塞自己。
-
主要用于批处理系统。
-
-
Preemptive(剥夺方式) --抢占
-
当前正运行进程可能被操作系统中断,此时转换为就绪态。
-
发生后把一个阻塞态进程置为就绪态时,或出现周期性的时间中断时,需要进行抢占决策。
-
与非抢占策略相比,抢占策略虽然会导致较大的开销,但能为所有进程提供较好的服务,因为它们避免了任何一个进程长时间独占处理器的情况。
-
使用有效的进程切换机制(尽可能获取这个比值的最小值为1.0,硬件的帮助),提供较大内存使大部分程序都在内存中,可使抢占的代价相对较低。
-
主要用于实时性要求较高的实时系统及性能要求较高的批处理系统和分时系统。
-
First-Come-First-Served(FCFS) 先来先服务
- 也称为先进先出(First-In-First-Out, FIFO)或严格排队方案。
- 每个进程就绪后,会加入就绪队列。
- 当前正运行的进程停止执行时,选择就绪队列中存在时间最长的进程运行。
- 缺点:归一化周转时间不协调。
- 相对于 I/O 密集型进程,它更有利于处理器密集型的进程。
- 一般,FCFS与其他调度算法混合使用。
- 系统可以按照不同的优先级维护多个就绪队列,每个队列内部按 照FCFS算法调度。
Round Robin(RR,轮转法,时间片法)
- 周期性地产生时钟中断,出现中断时,当前正运行的进程会放置到就绪队列中,然后基于FCFS策略选择下一个就绪作业运行。这种技术也称为时间片(time slicing),因为每个进程在被抢占前都会给定一片时间。
- 时间片的大小对计算机性能的影响:
- 若长度很短,则短作业会相对较快地通过系统。
- 另一方面,处理时钟中断、执行调度和分派函数都需要处理器开销。因此,应避免使用过短的时间片。
- 较好的想法是,时间片最好略大于一次典型亢互的时间。小于这一时间时,大多数进程至少需要两个时间片。
- 注意,当一个时间片比运行时间最长的进程还要长时,轮转法就会退化成FCFS。
- 存在的问题:未有效利用系统资源。Processor-bound 进程充分利用时间片,而 I/O- bound进程在两次I/O之间仅需很少的 CPU 时间,却需要等待一个时间片。
Virtual round robin(VRR)
- RR算法的改进:增加一个辅助队列,接收 I/O 阻塞完成的进程,调度优先于就绪队列,但占用的处理机时间小于就绪队列的时间片。
- 算法分析:VRR 算法比 RR 算法公平。
- RR算法常用于分时系统及事务处理系统。
Shortest Process Next (SPN,短进程优先)
- 非抢占策略,其原则是下次选择预计处理时间最短的进程。
- 短进程会越过长作业,跳到队列头。
- 改善了系统的性能,降低了系统的平均等待时间,提高了系统的 吞吐量
- 存在的问题:
- 很难准确确定进程的执行时间
- 对长进程不公平,可能造成长进程的饥饿
- 未考虑进程的紧迫程度,不适合于分时系统和事务处理系统
Shortest Remaining Time (SRT,剩余时间最短者优先)
- 是在 SPN 中增加了抢占机制的策略。调度程序总是选择预期剩余时间最短的进程。
- 只要假如就绪队列的新进程与当前正运行的进程相比,具有更短的剩余时间,调度程序就可抢占当前正在运行的进程。
- 与SPN算法一样,很难准确确定进程的剩余执行时间,且对长进程不公平
- 但是,它不像 FCFS 算法偏袒长进程,也不象 RR 算法会产生很多中断而增加系统负担。
- 由于短进程提前完成,故其平均周转时间比 SPN 短。
Highest Response Ratio Next(HRRN,响应比高者优先)
R = ω + s s R= \frac{ \omega + s } {s} R=sω+s
式中, R R R 为响应比, ω \omega ω 为等待处理器的时间,$s $ 为预计的服务时间。进程被立即调度时, R R R 等于归一化周转时间。注意, R R R 的最小值为 1.0,只有第一个进入系统的进程才能达到该值。
- 当前进程完成或被阻塞时,选择 R R R 值最大的就绪进程。
- 这种方法非常具有吸引力,因为它说明了进程的年龄。偏向短作业时(小分母产生大比值),长进程由于得不到服务,等待的时间会不断地增加,因此比值变大,最终在竞争中赢了短进程。类似于 SRT、SPN, 使用最高响应比 (HRRN) 策略时需要估计预计的服务时间。
算法分析:
- 若进程的等待时间相同,则要求服务时间短的进程优先(SPN);
- 若进程要求的服务时间相同,则等待时间长的进程优先(FCFS);
- 长进程随着等待时间增加,必然会被调度。
- 但是,很难准确确定进程要求的执行时间,且每次调度之前需 要计算响应比,增加系统开销。
Feedback (FB,反馈调度法)
- 设置多个就绪队列,各队的优先权不同(从上而下优先级逐级 降低);
- 各个队列中进程执行的时间片不同,优先权愈高时间片愈短;
- 新进程首先放入第一队列尾,按FCFS方法调度,若一个时间片 未完成,到下一队列排队等待;
- 仅当第一队列空时,调度程序才开始调度第二队列的进程,依 次类推。
算法分析:
- 有利于交互型作业 (常为短作业)
- 有利于短批处理作业
- 存在的问题: 当不断有新进程到来,则长进程可能饥饿。
系统实例分析
UNIX、LINUX系统调度实例
2.5 实时调度
2.5.1 实时系统与实时调度
系统的正确性不仅取决于计算的逻辑结果,而且取决于产生结果的时间。我们可通过定义实时进程或实时任务来定义实时系统。这些任务是实时任务,它们具有一定的紧急度。这类任务试图控制外部世界发生的事件,或对这些事件做出反应。由于这些事件是实时发生的,因而实时任务必须能够跟得上它所关注的事件。因此,通常会给某个特定任务指定一个最后期限,最后期限指定开始时间或结束时间。
实时任务
根据对截止时间的要求来划分
-
硬实时任务是指必须满足最后期限的任务,否则会给系统带来不可接受的破坏或致命的错误。
-
软实时任务也有一个与之相关的最后期限,且希望能满足这一期限的要求,但并不强制,即使超过了最后期限,调度和完成这个任务仍然是有意义的。
按任务执行时是否呈现周期性来划分
-
aperiodic,非周期任务有一个必须结束或开始的最后期限,或有一个关于开始时间和结束时间的约束条件。
-
periodic,周期任务而言,这一要求可描述成“每隔周期一次”或“每隔几个单位”。
实时系统
指能及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致地运行的计算机系统。
实时操作系统的需求表征
- 可确定性 Deterministic:操作系统获知有一次中断之前的延迟
- 它可以按照固定的、预先确定的时间或时间间隔执行操作。
- 关注的是操作系统获知有一次中断之前的延迟
- 在实时操作系统中,进程请求服务是用外部事件和时间安排来描述的。操作系统可确定性地满足请求的程度首先取决于它响应中断的速度,其次取决于系统是否具有足够的能力在要求的时间内处理所有的请求。
- 关于操作系统可确定性能力的一个非常有用的度量是,从高优先级设备中断至开始服务之间的延迟。
- 可响应性 Responsiveness:知道中断之后,操作系统为中断提供服务的时间。
- 初次处理中断并开始执行中断服务例程所需的时间量。若ISR的执行需要一次进程切换,则需要的延迟将比在当前进程上下文中执行ISR的延迟长。
- 执行ISR所需的时间总量,通常与硬件平台有关。
- 中断嵌套的影响。一个ISR会因另一个中断的到达而中断时,服务将被延迟。
- 用户控制 User control
- 把用户分成多个优先级组
- 是使用页面调度还是使用进程交换
- 哪个进程必须常驻内存
- 采用何种磁盘传输算法
- 不同优先级的进程各有哪些权限等
- 可靠性 Reliability
- 实时系统是实时响应和控制事件的,性能的损失或降低可能产生灾难性的后果
- 故障弱化操作 fail-soft operation
- 系统在故障时尽可能多地保存其性能和数据的能力。
- 实时系统的核心是短程任务调度程序。在设计这种调度程序时,公平性和最小平均响应时间并不是最重要的,最重要的是所有硬实时任务都能在最后期限内完成(或开始),且尽可能多的软实时任务也能在最后期限内完成(或开始)。
- 大多数当代实时操作系统都不能直接处理最后期限,而被设计为尽可能地对实时任务做出响应,使得在临近最后期限时,能迅速调度一个任务。
实时系统的应用
- 实时控制系统,指要求进行实时控制的系统。用于生产过程的控 制,实时采集现场数据,并对所采集的数据进行及时处理 。如 飞机的自动驾驶系统,以及导弹的制导系统等。
- 实时信息处理系统,指能对信息进行实时处理的系统。典型的实 时信息处理系统有:飞机订票系统、情报检索系统等
实时系统的特点
- 切换快 fast context switch
- 任务小 small size
- 能够对外部中断快速响应
- 多任务系统进程间通信工具,如 semaphores, signals and events
- 快速文件存储速度。files that accumulate data at a fast rate.
- 用户使用顺序文件 use of special
- 基于优先级的剥夺式调度 preemptive scheduling base on priority
- minimization of intervals during which interrupts are disabled
- delay task for fixed amount of time
- 报警和超时处理 special alarms and timeouts
实时调度
实时进程调度的剥夺方式
- 时间片轮转调度。 round robin preemptive scheduler
- 先来先服务,给时间片
- 实时进程来了之后,要排队
- 响应时间在秒级
- 广泛用于分时系统,也可用于一般的实时信息处理系统
- 不适合要求严格的实时控制系统
- 基于优先级的非剥夺调度法
- 为实时任务赋予较高的优先级,将它插入就绪队列队首,…
- 响应时间一般在数百毫秒至微秒范围。
- 多用于多道批处理系统,也可以用于要求不太严格的实时系统
- 基于优先级的剥夺调度法
- 当实时任务到达后,可以在时钟中断时,剥夺正在执行的低优先级进程的执行,调度执行高优先级的任务
- 响应时间较短,一般在几十毫秒或几毫秒
- 并不是所有时刻都能立刻剥夺,比如正在执行原语、临界区执行、
- 立刻剥夺调度法
- 要求操作系统具有快速响应外部事件的能力。一旦出现外部中断,只要当前任务未处于临界区,便立刻剥夺其执行,把处理机分配给请求中断的紧迫任务。
- 调度时延可以降低至100微妙,甚至更低。
实时调度的目标
- 硬实时任务,在其规定的截止时间内完成
- 尽可能使软实时任务也能在规定的截止时间内完成。
公平性和最短平均响应时间等要求已不再重要。
根据任务完不成可能带来的后果区分硬实时和软实时。
但是,大多数现代实时操作系统无法直接处理任务的截止时间,它们只能尽量提高响应速度,以尽快地调度任务。
2.5.2 实时调度算法
- 静态表驱动调度法 static table-driven scheduling
- 执行关于可行调度的静态分析。 determines at run time when a task begins execution.
- 分析的结果是一个调度,它确定在运行时一个任务何时需开始执行。
- 用于调度周期性的实时任务。
- 按照任务周期性的到达时间、执行时间、完成截至时间(ending deadline)、以及任务的优先级,制定调度表、调度实时任务。
- 最早截止时间优先(EDF)调度算法即属于此类。
- 此类算法不灵活,任何任务的调度申请改动都会引起调度表的修改。
- 静态优先级剥夺调度法 Static priority-driven preemptive approaches
- 此类算法多用于非实时多道程序系统。
- 优先级的确定方法很多,例如在分时系统中,可以对 I/O bound 和 processor bound 的进程赋予不同的优先级。
- 实时系统中一般根据对任务的限定时间赋予优先级,例如速度单调算法(RM)即为实时任务赋予静态优先级。
- 动态规划的调度法 Dynamic planning-based approaches
- 当实时任务到达以后,系统为新到达的任务和正在执行的任务动态创建一张调度表。
- 在当前执行进程不会错过其截止时间的条件下,如果也能使新到达任务在截止时间内完成,则立刻调度执行新任务。
- 动态尽力调度法 Dynamic best effort approaches
- 不执行可行分析。
- 系统试图满足所有的最后期限,并终止任何已经开始运行但错过最后期限的进程。
- 实现简单,广泛用于非周期性实时任务调度。
- 当任务到达时,系统根据其属性赋予优先级,优先级较高的先调度,例如最早截止时间优先调度算法就采用了这种方法,这种算法总是尽最大努力尽早调度紧迫任务,因此称为“最大努力调度算法”。
- 缺点在于,当任务完成,或截止时间到达时,很难知道该任务是否满足其约束时间。
限期调度
-
用到的信息
- 就绪时间。任务开始准备执行时的时间。
- 启动最后期限。任务必须开始的时间。
- 完成最后期限。任务必须完成的时间。
- 处理时间。从执行任务知道完成任务所需的时间。
- 资源需求。
- 优先级。
- 子任务结构。一个任务可以分解出强制子任务(mandatory subtask)和非强制子任务(optional subtask)。只有强制子任务拥有硬截止时间(hard deadline)。
-
先调度哪个任务?
- Scheduling tasks with the earliest deadline minimized the fraction of tasks that miss their deadlines. (最早截止时间)
-
采用哪种剥夺方式?
- When starting deadlines are specified, then a nonpreemtive scheduler makes sense.在执行完强制子任务或临界区后,阻塞自己。
- For a system with completion deadlines, a preemptive strategy is most appropriate.
-
最早截止时间优先,Eealiest Deadline,简称 ED
- 常用调度算法
- 若指定任务的 Starting deadlines,则采用 Nonpreemption,当某任务的开始截止时间到达时,正在执行的任务必须执行完其强制部分活临界区,释放 CPU,调度开始截止时间到的任务执行。 (严格地说,也是剥夺的)。
-
给定完成截止时间的周期性任务,Periodic tasks with completion deadlines
- 由于此类任务周期性的、可预测的,可采用静态表驱动之最早截止时间优先调度算法,使系统中的任务都能按要求完成。
- 举例:周期性任务 A 和 B ,指定了它们的完成截止时间,任务 A 每隔 20ms 完成一次,任务 B 每隔 50ms 完成一次。任务 A 每次需要执行 10ms,任务 B 每次需要执行 25ms。(表 10.3)
-
有自愿空闲时间的最早最后期限 Aperiodic tasks with starting deadlines
- 可以采用最早截止时间优先调度算法或允许 CPU 空闲的 EDF 调度算法。
- Earliest Deadline with Unforced Idle Times (允许 CPU 空闲的 EDF 调度算法),指优先调度最早截止时间的任务,并将它执行完毕才调度下一个任务。即使选定的任务未就绪,允许 CPU 空闲等待,也不能调度其他任务。尽管 CPU 的利用率不高,但这种调度算法可以保证系统中的任务都能按要求完成。
- 表 10.4
速度单调调度算法(Rate Monotonic Scheduling)
-
基于周期长短分配任务的优先级。
-
周期短的任务高优先级。
-
Period(任务周期),指一个任务到达至下一任务到达之间的时间范围。
-
Rate(任务速度),即周期(以秒计)的倒数,单位:赫兹。
-
任务周期的结束,表示任务的硬截止时间。任务的执行时间不应超过任务周期。
-
CPU 的利用率 = 任务执行时间 / 任务周期 。
-
在 RMS 调度算法中,如果以任务速度为参数,则优先级函数是一个单调递增的函数。(所以叫做速度单调算法)
调度实例
UNIX, Windows NT
2.6 并发:互斥与同步
Concurrency: Mutual Exclusion and Synchronization
并发性的两大问题:互斥和同步;死锁与饥饿。
互斥:是指散布在不同进程之间的若干程序片段,当某个进程执行其中的一个程序片段时,其他进程就不能运行它们之中的任一程序片段,只能等到该进程运行完之后才可以继续运行。
同步:是指散布在不同进程之间的若干程序片段,它们的运行必须严格按照一定的先后次序来运行,这种次序依赖于要完成的任务。比如数据的收发,必须发送方发送了接收方才能收。
2.6.1 并发控制
与并发相关的关键术语
- 原子操作:一个函数或动作由一个或多个指令的序列实现,对外是不可见的:也就是说,没有其他进程可以看到其中间状态或能中断此操作。要保证指令序列要么作为一个组来执行,要么都不执行,对系统状态没有可见的影响。原子性操作能保证井发进程的隔离。
- 临界区(critical section):进程中访问临界资源的代码段
- 死锁(deadlock):两个或两个以上的进程因每个进程都在等待其他进程做完某些事情而不能继续执行的情形。
- 活锁:两个或两个以上的进程为响应其他进程中的变化而持续改变自己的状态但不做有用的工作的情形。
- 互斥(mutual exclusion):当一个进程在临界区访问共享资源时,其他进程不能进入该临界区访问任何共享资源的情形。
- 竞争条件:多个线程或进程在读写一个共享数据时,结果依赖于它们执行的相对时间的情形。
- 饥饿(starvation):一个可运行进程尽管能继续执行,但被调度程序无限期地忽视,而不能被调度执行的情形。
- 临界资源(Critical Resource):一次仅允许一个进程访问的资源。(只能互斥访问的)。
并发的主要问题
- 进程间通信
- 资源共享或竞争
- 多进程同步
- 进程时间分配
并发的难点
- 多个进程读写全局资源。
- 操作系统很难对资源进行最优化分配。
- 定位程序设计错误非常困难。
例子:
(全局共享变量的控制问题)如果多个进程执行相同的代码:
void echo()
{
chin = getchar(); // 输入字符
chout = chin; // 赋值
putchar(chout); // 输出字符
}
这个打印字符的程序很容易发生数据的丢失,出现这种问题的原因是中断可能在进程的任何地方发生,解决方案是控制对共享资源的访问
操作系统关注的问题
- 紧密跟踪不同的进程,例如使用进程控制块。
- 为每个活动进程分配和释放各种资源。这些资源包括处理器时间、存储器、文件和 I/O 设备。
- 保护每个进程的数据和物理资源。
- 一个进程的功能和输出结果必须与执行速度无关(相对于其他并发进程的执行速度)。(速度可以理解为推进进度)。
2.6.2 并发进程
我们可以根据进程相互之间知道对方是否存在的程度,对进程间的交互方式进行分类。
-
进程之间相互不知道对方的存在
- 资源竞争
- 一个进程的结果与另一个进程的活动无关。进程的执行时间可能会受到影响。
- 互斥、死锁(可复用资源)、饥饿
-
进程间间接知道对方的存在
- 通过共享合作
- 一个进程的结果可能取决于从另一进程获得的信息。进程的执行时间可能会受到影响
- 互斥、死锁(可复用资源)、饥饿、数据一致性
-
进程直接知道对方的存在
- 通过通信合作
- 一个进程的结果可能取决于从另一个进程获得的信息。进程的执行时间可能会受到影响。
- 死锁(可消耗资源)、饥饿
死锁
例如,有两个进程 P1、P2,竞争两个资源 A、B。假设:
占用:P1(B)and P2(A)
申请:P1(A)and P2(B)
结果:P1、P2 永久等待(死锁)
进入区 → 临界区 → 退出区
进程间通过共享合作
- 写必须互斥
- 临界区用于保证数据完整性
进程间通过通信合作
-
由于传递消息的过程中进程间未共享任何对象,因而这类合作不需要互斥。
-
死锁:相互等待对方的通信。
-
饥饿:有三个进程,两个进程不断地交换信息,而第三个进程一直试图与其中一个通信,而被阻塞,因此处于饥饿状态。
进程间通过共享合作
进程/线程是计算机中的独立个体:异步性(并发性)
资源是计算机中的稀缺个体:独占性(不可复用性)
进程/线程协作完成任务(协作)
并发控制:进程/线程在推进时的相互制约关系
2.6.3 互斥条件与解决方案
- 必须强制实施互斥:在与相同资源或共享对象的临界区有关的所有进程中,一次只允许一个进程进入临界区。
- 一个在非临界区停止的进程不能干涉其他进程。
- 绝不允许出现需要访问临界区的进程被无限延迟的情况,即不会死锁或饥饿。
- 没有进程在临界区中时,任何需要进入临界区的进程必须能够立即进入。
- 对相关进程的执行速度和处理器的数量没有任何要求和限制。
- 一个进程驻留在临界区中的时间必须时有限的。
总结:空闲让进、忙则等待、有限等待、让权等待
2.6.4 同步互斥-硬件方法
中断禁用(屏蔽中断)
- 在单处理机器中,并发进程不能重叠,只能交替。
- 一个进程在调用一个系统服务或被中断前,将一直运行。
- 由于临界区不能被中断,故可以保证互斥。
- 由于处理器被限制于只能交替执行程序,因此执行的效率有明显的降低
- 这种方法不能用于多处理器器体系结构中。
while (true)
{
/* 禁用中断 */
/* 临界区 */
/* 启用中断 */
/* 其余部分 */
}
专用机器指令
多处理机:在多处理机配置中,几个处理器共享内存,处理器间的行为是对等的,处理器之间没有支持互斥的中断机制。在硬件级别上,对存储单元的访问排斥对相同单元的其他访问。基于这一点,处理器的设计者提出了一些机器指令,用于保证两个动作的原子性,即某一时刻只能有一个临界区代码访问临界资源。两种最常见的指令是:比较和交换指令、交换指令。
机器指令中间是没有中断点的。而 DMA 有断点,但不是中断点。
忙等待(busy waiting)或自旋等待(spin waiting):进程在得到临界区访问权之前,它只能继续执行测试变量的指令来获得访问权,除此之外不能做任何其他事情。
比较和交换指令
进程开始,bolt = 0 。当某一个进程 A 率先执行比较和交换指令时可以通过,通过之后便将 bolt 改为 1,而测试值是 0,因此其他进程执行比较和交换指令时不能通过,处于忙等待状态。当进程A执行完临界区之后又将 bolt 值改为0, 则将会有一个进程测试通过,往复执行,即可保证某一时刻只有一个临界区代码访问临界资源。
int compare_and_swap(int *word, int testval, int newval)
{
int oldval;
oldval= *word;
if (oldval == testval) *word = newval;
return oldval;
}
/* program mutualexclusion*/
const int n = /* 进程个数 */;
int bolt;
void P(int i)
{
while (true) {
while (compare_and_swap(bolt, 0, 1) == 1)
/* 不做任何事 */
/* 临界区 */
bolt = 0;
/* 其余部分 */
}
}
void main()
{
bolt = 0;
parbegin (P(1), P(2), ... , P(n));
}
exchange 指令
进程开始,bolt = 0 当某一个进程 A 率先执行交换指令时可以通过,通过之后便将 bolt 改为1,因此其他进程执行交换指令时 bolt = 1,通过交换将 keyi 置为 1,则将一直处于 while 循环中,无法执行下边临界区。当进程 A 执行完临界区之后又将 bolt 值改为 0, 则将会有一个进程测试通过,往复执行,即可保证某一时刻只有一个临界区代码访问临界资源。
void exchange(int *register, int *memory)
{
int temp;
temp = *memory;
*memory = *register;
*register = temp;
}
/* program mutualexclusion*/
const int n = /* 进程个数 */;
int bolt;
void P(int i)
{
while (true) {
int keyi = 1;
do exchange (keyi, bolt)
while (keyi !=0 )
/* 临界区 */
bolt = 0;
/* 其余部分 */
}
}
void main()
{
bolt = 0;
parbegin (P(1), P(2), ... , P(n));
}
互斥规程:构造 parbegin(P1, P2, Pn):阻塞主程序,初始化并行过程 P1, P2, … ,Pn; P1, P2, … ,Pn 过程全部终止之后,才恢复主程序的执行。
由于变量初始化的方式和交换算法的本质,下面的表达式恒成立:
b o l t + ∑ i k e y i = n bolt + \sum_{i} key_i = n bolt+i∑keyi=n
若 bolt = 0,则没有任何一个进程在它的临界区中;若 bolt = 1, 则只有一个进程在临界区中,即 key 的值等于 0 的那个进程。
机器指令方法的优缺点:
- 适用于单处理器或共享内存的多处理器上的任意数量的进程。
- 简单且易于证明。
- 可用于支持多个临界区,每个临界区可以用它自己的变量定义。
- 使用了忙等待:当一个进程正在等待进入临界区时,它会继续消耗处理器时间。
- 可能饥饿:当一个进程离开一个临界区且有多个进程正在等待时,选择哪个等待进程是任意的,因此某些进程可能会被无限地拒绝进入。
- 可能死锁:当某个进程通过专门指令时,在临界区中发生中断将有可能发生死锁。
2.6.5 同步互斥-信号量方法
信号量:用于进程间传递信号的一个整数值。在信号量上只可进行三种操作,即初始化、递减和增加,均为原子操作。其中,递减操作用于阻塞一个进程,递增操作用于解除一个进程的阻塞。信号量也被称为计数信号量或一般信号量。
基本原理:两个或多个进程可以通过简单的信号进行合作,可以强迫一个进程在某个位置停止,直到它接收到一个特定的信号。任何复杂的合作需求都可通过适当的信号结构得到满足。为了发信号,需要使用一个称为信号量的特殊变量。要通过信号量 s 传送信号,进程须执行原语 semSignal(s): 要通过信号量 s 接收信号,进程须执行原语 semWait(s): 若相应的信号仍未发送,则阻塞进程,直到发送完为止。
把信号量视为一个值为整数的变量,整数值上定义了三个操作:
- 一个信号量可以初始化成非负数。
- semWait 操作使信号量减 1。若值变成负数,则阻塞执行 semWait 的进程,否则进程继续执行。(申请资源且可能阻塞自己,注意这里是进阻塞队列,而不是忙等。P(s))
- semSignal 操作使信号量加 1。若值小于等于 0,则被 semWait 操作阻塞的进程解除阻塞。(释放资源并唤醒阻塞进程,唤醒条件:s ≤ 0 。V(s))
三个重要结论:
- 通常,在进程对信号量减 1 之前,无法提前知道该信号量是否会被阻塞。
- 当进程对一个信号量加 1 之后,会唤醒另一个进程,两个进程继续并发运行。而在一个单处理器系统中,同样无法知道哪个进程会立即继续运行。
- 向信号量发出信号后,不需要知道是否有另一个进程正在等待,被解除阻塞的进程数要么没有,要么是为 1。
通用信号量(General Semaphore):通用信号量是记录型,其中一个域为整型,另一个域为队列,其元素为等待该信号量的阻塞进程(FIFO)。semWait 和 semsignal 原语被假设是原子操作,不可被中断。信号量原语的定义:
struct semaphore
{
int count;
queueType queue;
};
void semWait(semaphore s)
{
s.count--;
if (s.count < 0){
/* 把当前进程插入队列 */
/* 阻塞当前进程 */
}
}
void semSignal(semaphore s)
{
s.count++;
if (s.count <= 0){
/* 把进程 P 从队列中移除 */
/* 把进程 P 插入就绪队列 */
}
}
二元信号量(binary semaphore)的值只能是 0 或 1。
struct binary_semaphore{
enum {zero, one} value;
queueType queue;
};
void semWaitB(binary_semaphore s)
{
if (s.value == one){
s.value = zero;
}else{
/* 把当前进程插入队列 */
/* 阻塞当前进程 */
}
}
void semSignalB(semaphore s)
{
if (s.queue is empty()){
s.value = one;
}else{
/* 把进程 P 从等待队列中移除 */
/* 把进程 P 插入就绪队列 */
}
}
- 二元信号量可以初始化为 0 或 1。
- semWaitB 操作检查信号的值。若值为 0,则进程执行 semWaitB 就会受阻。若值为 1,则将值改为 0,并继续执行该进程。
- semSignalB 操作检查是否有任何进程在该信号上受阻。若有进程受阻,则通过 semWaitB 操作,受阻的进程会被唤醒:若没有进程受阻,则值设置为 1。
2.6.6 同步互斥-信号量意义
信号量的类型:
- 信号量分为:互斥信号量和资源信号量。
- 互斥信号量用于申请或释放资源的使用权,常初始化为 1。
- 资源信号量用于申请或归还资源,可以初始化为大于 1 的正整数,表示系统中某类资源的可用个数。
- wait 操作用于申请资源(或使用权),进程执行 wait 原语时,可能阻塞自己。
使用信号量互斥
/* program mutualexclusion */
const int n = /* 进程数 */
semaphore s = 1;
void P(int i)
{
while (true){
semWait(s);
/* 临界区 */
semSignal(s);
/* 其余部分 */
}
}
void main()
{
parbegin (P(1), P(2), ... , P(n));
}
s.count 的值可以解释如下:
- s.count ≥ 0 :s.count 是可执行 semWait(s) 而不被阻塞的进程数(期间无 semSignal(s)执行)。这种情形允许信号量支持同步与互斥。
- s.count < 0 : s.count 的大小是阻塞在 s.queue 队列中的进程数。
- 当仅有两个并发进程共享临界资源时,互斥信号量仅能取值0、1、-1
- 1:表示无进程进入临界区
- 0:表示已有一个进程进入临界区
- -1:则表示已有一个进程正在等待进入临界区
- 当用 s 来实现 n 个进程的互斥时,s.count 的取值范围为 1 ~ -(n-1)
操作系统内核以系统调用形式提供 wait 和 signal 原语,应用程序
互斥锁(mutex):互斥是一个编程标志位,用来获取和释放一个对象。
强信号量(strong semaphore):被阻塞时间最久的进程最先从队列释放。
弱信号量(weak semaphore):没有规定进程从队列中移除顺序的信号量
信号量的两种可能实现
semWait(s)
{
while (compare_and_swap(s.flag, 0, 1) == 1)
/* 不做任何事 */
s.count--;
if (s.count < 0){
/* 该进程进入 s.queue 队列 */;
/* 阻塞该进程(还须将 s.flag 设置为 0) */;
}
s.flag = 0;
}
semSignal(s)
{
while(compare_and_swap(s.flag, 0, 1) == 1)
/* 不做任何事 */
s.count++;
if (s.count <= 0){
/* 从s.queue队列中移除进程 */
/* 进程 P 进入就绪队列 */
}
s.flag = 0;
}
semWait(s)
{
// 禁用中断;
s.count--;
if (s.count < 0){
/* 该进程进入 s.queue 队列 */
/* 阻塞该进程,并允许中断 */
}else{
// 允许中断;
}
}
semSignal(s)
{
// 禁用中断
s.count++;
if (s.count <= 0){
/* 从 s.queue 队列中移除进程 P */;
/* 进程 P 进入就绪队列 */;
}
// 允许中断;
}
2.6.7 生产者-消费者问题
(重点使有界的临界区问题)
生产者/消费者模型:
生产者:满则等待,空则填充
消费者:空则等待,有则获取
(只考虑多生产者单消费者的情况)
任务及要求:
- buffer 不能并行操作(互斥),即某时刻只允许一个实体(producer or cunsumer)访问 buffer。
- 控制 producer 和 consumer 同步读/写 buffer,即不能向满 buffer 写数据;不能在空 buffer 中读数据。
- 指针 in 和 out 初始化指向缓冲区的第一个存储单元。
- 生产者通过 in 指针向存储单元存放数据,一个存放一条数据,且 in 指针向后移一个位置。
- 消费者从缓冲区中逐条取走数据,一次取一条数据,相应的存储单元变为“空”。每取走一条数据,out 指针向后移一个存储单元位置。
生产者/消费者必须互斥:
- 生产者和消费者可能同时进入缓冲区,甚至可能同时读/写一个存储单元,将导致执行结果不确定。
- 这显然使不允许的。必须使生产者和消费者互斥进入缓冲区。即,某时刻只允许一个实体(生产者或消费者)访问缓冲区,生产者互斥消费者和其他生产者。
- 生产者不能向满缓冲区写数据,消费者也不能在空缓冲区中取数据,即生产者和消费者必须同步。
producer:
while(true) {
/* 生产v */
b[in] = v;
in++;
}
consumer:
while(true){
while(in <= out)
/* 不做任何事 */;
w = b[out];
out++;
/* 消费 w */;
}
使用二元信号量解决无限缓冲区生产者/消费者问题的正确方法:
/* program producerconsumer */
int n;
binary_semaphore s = 1, delay = 0;
void producer()
{
while (true){
producer();
semWaitB(s);
append();
n++;
if (n==1) semSignalB(delay);
semSignalB(s);
}
}
void consumer()
{
int m; /* 局部变量 */
semWaitB(delay);
while (true){
semWaitB(s);
take();
n--;
m = n;
semSignalB(s);
consume();
if (m == 0) semWaitB(delay);
}
}
void main()
{
n = 0;
parbegin (producer, consumer);
}
使用信号量解决无限缓冲区生产者/消费者问题的方法
/* program producerconsumer */
int n;
semaphore n = 0, s = 1;
void producer()
{
while (true){
producer();
semWait(s);
append();
semSignal(s);
semSignal(n);
}
}
void consumer()
{
while (true){
semWait(n);
semWait(s);
take();
semSignal(s);
consume();
}
}
void main()
{
parbegin (producer, consumer);
}
使用信号量解决有限缓冲区生产者/消费者问题的方法
/* program producerconsumer */
int n;
semaphore n = 0; /* 资源信号量n,数据单元,初始化为 0,表示初始的时候buffer 里面没有数据 */
semaphore s = 1; /* 互斥信号量s,初始化为1 */
semaphore e = sizeofbuffer; /* 资源信号量e,空存储单元的数量 */
void producer()
{
while (true){
producer();
semWait(e); // 表示要占用一个空存储单元
semWait(s);
append();
semSignal(s);
semSignal(n); // 表示数据单元多一个
}
}
void consumer()
{
while (true){
semWait(n);
semWait(s);
take();
semSignal(s);
semSignal(e);
consume();
}
}
void main()
{
parbegin (producer, consumer);
}
- wait(e) 与 wait(s) 不能交换:如果数据单元写满了,一个生产者先 wait(s),先把使用权拿到,进去发现,没法写,里面是满的,然后就阻塞了。此时消费者无法取,产生死锁。
- s 信号量对生产者和消费者都是公用的,表示互斥地使用用一个 buffer
- 进程应该先申请资源信号量,再申请互斥信号量,顺序不能颠倒。
- 对任何信号量的 wait 和 signal 操作必须配对。同一进程中的多对 wait 与 signal 语句只能嵌套,不能交叉。
- wait 和 signal 语句不能颠倒顺序,wait 语句一定先于 signal 语句。
2.6.8 读者写者问题
三个角色:一个共享的数据区; Reader: 只读取这个数据区的进程; Writer: 只往数据区中写数据的进程;
三个条件:多个Reader可同时读数据区; 一次只有一个Writer可以往数据区写; 数据区不允许同时读写。(“读-写” 互斥;“写-写” 互斥;“读-读” 允许)
可用于解决多个进程共享一个数据区(文件、内存区、一组寄存器等),其中若干读进程只能读数据,若干写进程只能写数据等实际问题。
方案:
- 读者优先(相对简单):指一旦有读者正在读数据,允许多个读者同时进入读数据,只有当全部读者退出,才允许写着进入写数据。(Writers are subject to starvation)
- 写者优先(相当复杂)
- 公平优先:写过程中,若其它读者、写者到来,则按到达顺序处理
读者优先
信号量设置
- wsem:互斥信号量,用于 Writers 间互斥,Writers 和 Readers 互斥
- readcount:统计同时读数据的 Readers 个数。
- mutex:对变量 readcount 互斥算术操作
- readcount 是一个互斥的资源。(每次登记读者,是一次一个登记的,删除也是一次一个地删除)。因此,mutex 是对 readcounnt 变量互斥的信号量。
- 不论是读者还是
int readcount = 0; semaphore mutex = 1, wsem = 1;
void reader()
{
while (1) {
P(mutex);
readcount++;
if (readcount == 1)
P(wsem); // 如果有写者在里面,先在这里阻塞等一等,只要第一个读者阻塞就好,后面的读者不用再进行阻塞
V(mutex);
READ;
P(mutex);
readcount--;
if (readcount == 0) // 表示最后一个退出的读者
V(wsem);
V(mutex);
}
}
void writer()
{
while (1) {
P(wsem); // 如果写者先来,阻塞读者
WRITE;
V(wsem);
}
}
写者优先
当至少有一个写者声明想写数据时,则不再允许新的读者进入读数据。例如:队列:(尾)WWRRW(头),让三个W进程能优先于R进程写数据。解决了写者饥饿问题,但 降低了并发程度,系统的并发性能较差 。
信号量设置:
wsem:互斥信号量,用于Writers间互斥,Reader互斥Writers
rsem:互斥信号量,当至少有一个写者申请写数据时互斥新的读者进入读数据。第一个写者受rsem影响,一旦有第一个写者,后续写者不受rsem其影响。但是读者需要在rsem上排队。
mwc:用于控制writecount互斥算术操作
mrc:用于控制readcount互斥算术操作
z: 对读者进行控制,防止在rsem上出现读进程的长队列,否则写进程不能跳过这个队列
int readcount, writecount;
semaphore mrc = l, mwc = 1, z = 1, wsem = 1, rsem = l;
void reader()
{
while (1) {
P(z);
P(rsem);
P(mrc);
readcount++;
if (readcount == 1)
P(wsem);
V(mrc);
V(rsem);
V(z);
READ;
P(mrc);
readcount--;
if (readcount == 0)
V(wsem);
V(mrc);
}
}
void writer()
{
while (1) {
P(mwc);
writecount++;
if (writecount == 1)
P(rsem);
V(mwc);
P(wsem);
WRITE;
V(wsem);
P(mwc);
writecount--;
if (writecount == 0)
V(rsem);
V(mwc);
}
}
公平优先
写过程中,若其它读者、写者到来,则按到达顺序处理
信号量设置:
- w:互斥信号量,用于 Writers 间互斥,Reader 互斥 Writers
- readcount:统计同时读数据的 Readers 个数
- mrc:对变量 readcount 互斥算术操作
- r:互斥信号量,确定 Writer 、Reader 请求顺序
在读者优先中,wsem 只对第一个读者起阻塞作用,后续读者不受其影响。为了保证按照到达顺序处理,故公平优先方式设置 wrsem , 读者/写者按到达顺序在 wrsem 上排队。
int readcount = 0;
semaphore mrc = 1, r = 1, w = 1;
READER
{
P(r);
P(mrc);
readcount++;
if (readcount == 1)
P(w);
V(mrc);
V(r);
Read();
P(mrc);
readcount--;
if (readcount == 0)
V(w);
V(mrc);
}
WRITER
{
P(r);
P(w);
Write();
V(w);
V(r);
}
2.6.9 管程方法
管程是一种程序设计语言结构,它提供的功能与信号量相同,但更易于控制。(面对对象方法)
管程是若干进程、局部于管程的数据,初始化语句(组)组成的软件模块,其主要特点:
- 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
- 一个进程通过调用管程的一个过程进入管程。
- 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
管程的互斥机制:
- 管程中的数据变量每次只能被一个进程访问。
- 通过放入 共享数据结构 从而实现访问该资源的进程的互斥
当调用管程的进程被阻塞时需要释放该管程供其他进程进入,当条件满足且管程可用则进程从阻塞点重新进入管程。
管程通过使用条件变量(condition variable)来支持同步,这些条件变量包含在管程中,并且只有在管程中才能被访问。有两个函数可以操作条件变量:
- cwait©: 调用进程的执行在条件 c 上阻塞,管程现在可被另一个进程使用。
- csignal©: 恢复执行在 cwait 之后因某些条件而被阻塞的进程。若有多个这样的进程,选择其中一个;若没有这样的进程,什么也不做。
管程的结构
- 入口: 等待进入管程的进程队列
- 管程等待区域: 条件等待队列 和 紧急队列
- 管程的数据区域: 局部数据 条件变量 过程(1,2,3,4…) 初始化代码
管程的工作流程
- 入口队列保证一次只能有一个进程可以进入, 试图进入管程的进程被阻塞加入等待管程可用的进程队列
- 当使用管程的进程执行cwait©把自己阻塞在条件c上,则加入等待条件改变重进进入管程的队列 (队列在管程内部)
管程解决有界缓冲区 生产者\消费者 问题
管程的2个条件变量:
- notfull :缓冲区至少有增加一个字符的空间时,notfull 为真
- notempty : 缓冲区至少还有一个字符时, notemprt 为真
伪代码:
/* program producerconsume */
moniter boundedbuffer;
char buffer[N]; //缓冲区N个数据项
int nextin,nextout; // 缓冲区指针
int count; // 缓冲区数据项个数
cond notfull,notempty; // 条件变量
// 初始化缓冲区
{
nextin = 0;
nextout = 0;
count = 0;
}
void append(char x) {
if (count == N) // 缓冲区满,防止溢出
cwait(notfull);
buffer[nextin] = x;
nextin = (nextin + 1) % N;
count++;
csignal(notempty); // 缓冲区添加数据后 可以供消费者使用
}
void take(char x) {
if (count == 0) //缓冲区为空 防止下溢
cwait(notempty);
x = buffer[nextout];
nextout = (nextout + 1) % N;
count--;
csignal(notfull); // 生产者可以继续生产
}
// main
void producer() {
char x;
while (true) {
produce(x);
append(x);
}
}
void consume() {
char x;
while (true) {
take(x);
consume(x);
}
}
void main() {
parbegin(producer,consumer);
}
优点:
- 管程有自己的互斥机制,通过控制条件变量完成进程的同步就行
- 使用信号量的话 互斥 和 同步 都要自己实现
2.6.10 消息传递
消息传递是一种 进程之间通信的方式,在这里则使用了消息传递来实现互斥;
实际功能由原语提供: 进程间消息传递的最小操作集
send(destination,message); // 发送一个消息给接收进程
receive(source,message); // 接收 Sender 发来的消息
同步
-
进程执行 send 原语2种可能
- 不被阻塞
- 发送的进程被阻塞 知道 消息被目标进程所接受
-
进程执行 receive 原语后的可能
- 直接受到消息 进程继续执行
- 等待消息:
- 该进程被阻塞直到消息到达
- 进程继续执行,放弃消息接受
- 阻塞 send, 阻塞 receive:都有可能阻塞等待,进程紧密同步(汇合)
- 无阻塞 send, 阻塞 receive: 发送方尽可能地持续发送信息,接收方如果没有收到信息,那么就阻塞等待信息;
- 无阻塞 send, 无阻塞 receive:发送方或者接收方都不要求阻塞等待
寻址
直接寻址
- send 原语包含一个接收方的ID,我能直接通过这个ID发送信息给接收方;
- receive 原语能够知道给我发送消息的是谁,能知道大概什么时候会收到来自谁的消息;
间接寻址
- 消息不从发出者直接到达接受者, 而是发送到一个共享的数据结构(消息队列),由临时保存消息的队列组成,通常称为信箱
- 常见关系:
- 1 对 1 :专用通信链接。
- 多对 1 :常见客户-服务器交互. 服务器由一个进程给其他许多进程提供服务,此时信箱常被称为端口
- 1 对 多 : 某进程广播一条消息
- 多 对 多:邮箱是可以有多个发送方与多个接收方;
- 传递的消息是有格式的
采用 Nonblocking send, blocking receive
- 多个进程共享 邮箱 mutex。若进程申请进入临界区,首先申请从 mutex 邮箱中接收一条消息。若邮箱空,则进程阻塞;若进程收到邮箱中的消息,则进入临界区,执行完毕退出,并将该消息放回 邮箱mutex。该消息 as a token 在进程间传递。
/* program mutualexclusion */
const int n = /* 进程数 */
void P(int i)
{
message msg;
while(true){
receive(box, msg);
/* 临界区 */
send(box, msg);
/* 其余部分 */
}
}
void main()
{
create mailbox(box);
send(box, null); // 初始化,向邮箱发送一条空消息
parbegin (P(1), P(2), ... , P(n));
}
利用 消息传递 解决有限缓冲区 生产者\消费者 问题
-
思路
- 通过 消息传递的间接寻址, 除了传递信号外还传递数据
- 设置两个邮箱:
- Mayconsume:Producer 存放数据,供 Consumer 取走(即 buffer 数据区)
- Mayproduce:存放空消息的 buffer 空间
- 生产者将产生的数据作为消息发到信箱 mayconsume,只要该信箱还有一条消息,消费者就可以开始消费
- 那么 mayconsume 就作为缓冲区,全局变量capacity确定缓冲区大小,
- 信箱 mayproduce 开始时填满空消息,且等于信箱的容量,每次生产使得 mayproduce 中的消息减少,mayconsume 中的消息增多
-
通过这2个信箱(消息队列),可以有多个生产者和消费者。
伪代码:
const int capacity = /*缓冲区容量*/;
message null = "空消息";
int i;
void producer() {
message pmsg;
while(true) {
receive (mayproduce, pmsg);
pmsg = produce();
send (mayconsume, pmsg);
}
}
void consumer() {
message cmsg;
while (true) {
receive (mayconsume, cmsg);
consume(cmsg);
send (mayproduce, null);
}
}
void main() {
create_mailbox(mayproduce); // 邮箱创建原语
create_mailbox(mayconsume);
for (int i=1; i<=capacity; ++i)
send (mayproduce,null); // mayproduce插入空消息
parbegin(producer,consumer);
}
2.7 死锁
内容概要:产生死锁与饥饿的原因、解决死锁的方法、死锁/同步的经典问题:哲学家进餐问题
2.7.1 死锁概念
死锁定义为一组相互竞争系统资源或进行通信的进程间的“永久”阻塞。当一组进程中的每个进程都在等待某个事件(典型情况下是等待释放所请求的资源),而仅有这组进程中被阻塞的其他进程才可触发该事件时,就称这组进程发生了死锁。因为没有事件能够被触发,故死锁是永久性的。与并发进程管理中的其他问题不同,死锁问题并无有效的通用解决方案。
产生原因:
- 资源不足导致的资源竞争:多个进程所共享的资源不足,引起它们对资源的竞争而产生死锁。
- 并发执行的顺序不当:进程运行过程中,请求和释放资源的顺序不当,而导致进程死锁。
资源分类
-
可重用资源
-
一次被应用的资源并不会消耗掉这一资源;
-
进程获得这一资源以后,释放掉并且还能被其他资源申请使用;
-
处理器,I/O通道,主存,赋存,文件,数据库以及信号量等等;
-
死锁可能产生在一个进程获取了一个可重用资源占用之后之后,又去申请其他资源;
-
例子:有一块内存资源,大小200K。并发执行时,200K就可能不够用,互相等待彼此释放内存资源,出现死锁。
-
-
可消耗资源
-
一个资源被一个进程创建,并在使用结束后销毁;
-
类似于中断,信号,信息,I/O缓冲区内的信息数据;
-
采用阻塞接受的消息传递可能发生死锁;这一类的死锁往往是由于设计失误而造成的,很难被发现,且潜伏期很长;
-
2.7.2 死锁条件
- 互斥。一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
- 占有且等待。当一个进程等待其他进程时,继续占有已分配的资源。
- 不剥夺。不能强行抢占进程已占有的资源。
- 环路等待。存在一个闭合的进程链,每个进程至少占有此链中下一个进程所需的一个资源。
前三个条件都只是死锁存在的必要条件而非充分条件。
2.7.3 死锁避免
处理死锁的方法有三种。一是采用某种策略消除条件中的某个条件的出现,来预防(prevent)死锁;二是基于资源分配的当前状态做动态选择来避免(avoid)死锁;三是试图检测(detect)死锁的存在并从死锁中恢复。
预防死锁(Deadlock Prevention)
- 间接方法,禁止前三个条件之一的发生:
- 互斥:是某些系统资源固有的属性,不能禁止
- 禁止“保持并等待”条件:要求进程一次性地申请其所需的全部资源。若系统中没有足够的资源可分配给它,则进程阻塞。
- 禁止“不剥夺”条件:1. 若一个进程占用了某些系统资源,又申请新的资源,则不能立即分配给它。必须让它首先释放出已占用资源,然后再重新申请;2. 若一个进程申请的资源被另一个进程占有,OS 可以剥夺低优先权进程的资源分配给高优先权的进程(要求此类可剥夺资源的状态易于保存和恢复,否则不能剥夺)
- 直接方法,禁止条件 4(环路等待)的发生
- 即禁止“环路等待”条件:可以将系统的所有资源按类型不同进行线性排队,并赋予不同的序号。进程对某类资源的申请只能 按照序号递增的方式进行。
显然,此方法是低效的,它将影响进程执行的速度,甚至阻碍资源的正常分配。
总结
资源分配策略:保守:预提交资源
- 一次性请求所有资源
- 优点:对执行一连串活动的进程非常有效、不需要抢占
- 缺点:低效、延迟进程的初始化、必须知道将来的资源请求
- 抢占
- 优点:用于状态易于保存和恢复的资源时非常方便
- 缺点:过于经常地、没必要地抢占
- 资源排序
- 优点:通过编译时检测是可以实施的、既然问题已在系统设计时解决,因此不需要在运行时计算
- 缺点:禁止增加的资源请求
避免死锁(Deadlock Avoidance)
预防死锁通过实施较强的限制条件实现,降低了系统性能。避免死锁的关键在于为进程分配资源之前,首先通过计算,判断此次分配是否会导致死锁,只有不会导致死锁的分配才可实行。
两种死锁避免方法:
- 若一个进程的请求会导致死锁,则不启动该进程。
- 若一个进程增加的资源请求会导致死锁,则不允许这一资源分配。
动态分配资源(进程对资源的申请在执行过程中,需要时在申请)。控制进程不进入不安全区(控制进程的执行次序,资源申请分配的时机)
资源分配拒绝策略
又称为银行家算法(banker algorithm)。首先需要定义状态和安全状态的概念。考虑一个系统,它有固定数量的进程和固定数量的资源,任何时候一个进程可能分配到零个或多个资源。系统的状态是当前给进程分配的资源情况,因此状态包含前面定义的两个向量 Resource 和 Available 及两个矩阵 Claim 和 Allocation。安全状态(safe state)指至少有一个资源分配序列不会导致死锁(即所有进程都能运行直到结束),不安全状态(unsafe state)指非安全的一个状态。
安全状态
- 指系统按某种顺序(进程的序列)如<P1, P2, …,Pn>为安全序列,来为每个进程分配其所需的资源,直至最大需求,使每个进程都可顺序完成,则称系统处于安全状态;
- 若系统不存在这样一个安全序列,则称系统处于 unsafe state。
安全状态 和 非安全状态
- 并非所有不安全状态都是死锁状态;
- 当系统进入不安全状态后,便可能进入死锁状态;
- 只要系统处于安全状态,则可以避免进入死锁状态;
总结
介于检测和预防中间
- 操作以便发现至少一条安全路径
- 优点:不需要抢占
- 缺点:必须知道将来的资源请求、进程不能被长时间阻塞
2.7.4 银行家算法
由来与概述
- 该算法可以用于银行发放一笔贷款前,预测该笔贷款是否会引起银行资金周转问题。
- 银行的资金就类似于计算机系统的资源,贷款业务就类似于计算机的资源分配。银行家算法能预测一笔贷款业务对银行是否是安全的,该算法也能预测一次资源分配对计算机系统是否是安全的。
- 为实现银行家算法,系统中必须设置若干的数据结构。
数据结构
- 可利用资源向量Available:是一个具有m个元素的数组,其中每一个元素代表一类可利用资源的数目,其初始值为系统中该类资源的最大可用数目。其值将随着该类资源的分配与回收而荣泰改变。Available[j] = k,表示系统中现有Rj类资源k个;
- 最大需求矩阵Max:是一个n*m的矩阵,定义了系统中n个进程中的每一个进程对m类资源的最大需求。Max(i, j) = k,表示进程i对Rj类资源的最大需求个数为k个。
* - 分配矩阵Allocation:是一个n*m的矩阵,定义了系统中每一类资源的数量。例如,Allocation(i,j) = k, 表示进程 i 当前对已分配Rj类资源的数目为k个。
- 需求矩阵Need:是一个n*m的矩阵,用以表示每一个进程尚需要的各类资源数目。例如,Need[i,j] = k,表示进程i还需要Rj类资源k个,方能完成其任务。
各类资源关系
Need[i, j] = Max[i ,j] - Allocation[i, j];
算法描述
2.7.5 银行家算法-安全性
- 设置两个工作向量:
- 设置一个数组Finish[n]。当Finish[i] = True( 0 <= i <= n, n为系统中进程数)时,表示进程Pi可以获得其所需的全部资源,而顺利执行完成。
- 同时需要设置一个临时变量Work,表示系统可提供给进程继续执行的资源集合。安全性算法刚开始执行时,Work := Available。
- 从进程集合中找到一个满足下列条件的进程:
Finish[i] = false; 并且 Need <= Work;
若找到满足该条件的进程,则执行步骤 3,否则执行步骤 4; - 当进程Pi获得资源后,将顺利执行直至完成,并释放其所拥有的全部资源,故应该执行:
Work := Work + Allcation;
Finish[i] := True;
GOTO 2; - 如果所有进程的 Finish[i] = True,则表示系统处于安全状态,否则系统处于不安全状态。
要求
- 对于每一个进程,我们都要声明它对每一类资源需求的总量;
- 进程之间相互独立,其执行顺序取决于系统安全,而非进程间的同步要求;
- 系统必须提供固定数量的资源供分配;
- 进程占用资源,则不能让其退出系统;
2.7.6 银行家算法-举例
2.7.7 死锁检测与解除
- 检测死锁不同于预防死锁,不限制资源访问方式和资源申请。
- OS周期性地执行死锁检测例程,检测系统中是否出现“环路等待”;
- 检测到死锁,则终端所有死锁进程;
- 对于某些进程,设置监测点,若死锁,退回至死锁进程的检测点,从检测点重新运行;死锁可能仍会发生
逐步的解锁进程,直至不再发生死锁;
逐步的剥夺资源,直至不在发生死锁;
解除资源的条件
- 目前为止消耗处理器时间最少的;
- 产出最少的进程;
- 剩余执行时间最长的;
- 得到的资源分配最少的;
- 最低优先级;
检测
非常自由:只要有可能,请求的资源都允许
- 周期性地调用以便测试死锁
- 优点:不会延迟进程的初始化、易于在线处理
- 缺点:固有的抢占丢失
2.7.8 哲学家进餐问题
描述
有5个哲学家,他们的生活方式时交替地进行思考和进餐。哲学家们公用一张圆桌,分别坐在周围的五张椅子上。圆周中间放有一大碗面条,每个哲学家分别由一个盘子和一支叉子。如果哲学家想吃面条,则必须拿到开启最近的左右两支叉子。进餐完毕后,放下叉子继续思考。
要求
设计一个合理的算法,使全部哲学家都能进餐(非同时)。算法必须避免死锁和饥饿,哲学家互斥共享叉子。
信号量解决方案
- 规定: 每位哲学家首先拿起左边的叉子,然后拿起右边的叉子
semaphore fork[5] = {1};
int i;
void philosopher (int i) {
while (true) {
think();
wait(fork[i]); //等待左边的叉子
wait(fork[(i+1) % 5]; // 等待右边的叉子
eat();
signal(fork[(i+1) % 5]); // 放回右边的叉子
signal(fork[i]; //放回左边的叉子 (增加信号量)
}
}
void mian() {
parbegin(philosopher(0),philosopher(1).....philosopher(4));
}
- 此方案会导致死锁: 这么写是有问题的。wait不能被中断,但是wait完,执行下一个wait语句中间是可以被中断的。也就是说,当所有哲学家都是在wait[fork[i]]后被中断,此时死锁出现。(所有哲学家同时坐下来,都拿起左边的叉子,都去伸手拿右边的叉子,却都没有拿到。)
对上面死锁危险的解决方案
- 再增加5把叉子,
- 哲学家学会只使用一把叉子吃面
- 增加服务员: 限制每次最多只能4个哲学家坐下来,那么至少有一个哲学家能够拿到2把叉子
增加服务员方案
- 一个信号量 room 表示 : 限制每次哲学家同时坐下的数量
semaphore fork[5] = {1};
semaphore room = 4;
int i;
void philosopher (int i) {
while (true) {
think(); // 哲学家正在思考
wait(room); // 第5位哲学家将被阻塞在 room 信号量队列
wait(fork[i]); // 取其左边的筷子
wait(fork[(i+1) % 5]); //取其右边的筷子
eat(); // 吃面条
signal(fork[(i+1) % 5]); // 放回右边的筷子
signal(fork[i]); // 放回左边的筷子
signal(room); // 唤醒阻塞在 room 信号量队列中的哲学家
}
}
void main() {
parbegin(philosopher(0),philosopher(1).....philosopher(4));
}
基于管程的解决方案
- 定义 5个管程的条件变量, 每把叉子对应一个条件变量,用来标识哲学家 等待 的叉子可用情况,
- 开一个bool数组记录每把叉子是否可用(true/false)
- 管程包含2个过程:
- get_forks : 表示取哲学家左右的叉子, 如果至少有1把不可以用,那么在条件变量队列中等待,使得其他哲学家进程进入管程
- release_forks : 标识2把叉子可用
- 与信号量方案类似,都是首先拿起左边的叉子,然后拿起右边的叉子
monitor dining_controller; // 声明管程
cron ForkReady[5]; // 管程的条件变量
bool fork[5] = {true}; // 标记叉子是否可用
void get_forks(int pid) { // pid既代表哲学家又代表叉子
int left = pid;
int right = (pid + 1) % 5;
if (!fork[left]) // 左边叉子被使用,则等待条件变量
cwait(ForkReady[left]);
fork[left] = false;//左边叉子现在可以归我们用了,再次标记为不能用
if (!fork[right])
cwait(ForkReady[right]);
fork[right] = false;
}
void release_forks(int pid) {
int left = pid;
int right= (pid + 1) % 5;
if (empty(ForkReady[left])) // 没有哲学家在该队列上等待,则该叉子可以使用
fork[left] = true;
else //如果还有等待,增加信号量
csignal(ForkReady[left]);
/* 释放右边的叉子 */
if (empty(ForkReady[right]))
fork[right] = true;
else
csignal(ForkReady[right]);
}
void philosopher(int i) {
while (true) {
think();
get_forks(i);
eat();
release_forks(i);
}
}
void main() {
parbegin(philosopher(0),philosopher(1).....philosopher(4));
}
三、内存管理/存储器管理
- 进程管理子系统操作
- 存储管理子系统
- I/O设备管理子系统
- 文件管理子系统
操作系统的设计理念往往涉及两个方面:1 正常运行;2 高效运行 。
补充:操作系统的初始化流程分析
- 加电或复位
- CS寄存器=FFFF[0];IP寄存器=0000[0] (CS寄存器:存放当前运行段的起始地址;IP寄存器:存放指令在代码段内的偏移量)
- CS: IP组合 揩向bios入口,作为处理器运行的-条指令 (IP组合:确定下一条执行指令的物理地址)
- BIOS 启动
- BIOS:主要提供CPU需要的启动指令。启动程序的运行过程:
上电自检 -> 监测并连接系统硬件 -> 从软盘/硬盘读入Boot Loader(硬盘的0面0道1扇区中)- BIOS 还提供一组中断,以便对硬件设备的访问。在 OS 未装入前,负责响应中断。
- Boot Loader:将系统启动代码读入内存
- 操作系统初始化
- 当 Boot Loader 将控制权交给OS的初始化代码以后,OS 开始其初始化工作,负责完成存储管理、设备管理、文件管理、进程管理的初始化
- 当OS的初始化工作完成以后,进入用户态,等待用户的操作
内存管理术语
- 页框:内存中固定长度的块
- 页:固定长度的数据块,存储在二级存储器中(如磁盘)。数据页可以临时复制到内存的页框中
- 段:变长数据块,存储在二级存储器中。整个段可以临时复制到内存的一个可用区域中(分段),或可以将一个段分为许多页,然后将每页单独复制到内存中(分段与分页相结合)
存储器管理的主要对象是内存(随机存储器)。对外存的管理与对内存的管理相类似,只是它们的用途不同,外存主要用来存放文件,对于外存的管理放在文件管理一章再介绍。
对于通用计算机而言,存储层次至少应具有三级:最高层为CPU寄存器,中间为主存,最底层是辅存。在较高档的计算机中,还可以根据具体的功能细分为寄存器、高速缓存、主存储器、磁盘缓存、固定磁盘、可移动存储介质等6层。
注:
- 在计算机系统的存储层次中,寄存器和主存储器又被称为可执行存储器。
- 主存储器简称 内存 或 主存。
- 寄存器具有与处理机相同的速度,故对寄存器的访问速度最快。
- 高速缓存主要用于备份主存中较常用的数据,以减少处理机对主存储器的访问次数,缓和了内存与处理机速度之间的矛盾。当 CPU 要访问一组特定的信息时,会首先检查它是否在高速缓存中。
- 磁盘的 I/O 速度远低于对主存的访问速度,为了缓和两者之间在速度上的不匹配,而设置了磁盘缓存,主要用于暂时存放频繁使用的一部分磁盘数据和信息,以减少访问磁盘的次数。但磁盘缓存和高速缓存不同,它本身并不是一种实际存在的寄存器,而是利用主存中的部分存储空间暂时存放从磁盘中读出或写入的信息。主存也可以看作是辅存的高速缓存。
内存管理和进程管理是操作系统的核心内容。本章围绕分页机制展开:通过分页管理方式在物理内存大小的基础上提高内存的利用率,再进一步引入请求分页管理方式,实现虚拟内存,使内存脱离物理大小的限制,从而提高处理器的利用率。
3.1 内存管理概念和简单内存管理技术
3.1.1 内存管理的基本原理和要求
内存管理是操作系统设计中最重要和最复杂的内容之一。操作系统对内存的划分和动态分配就是内存管理。有效的内存管理在多道程序设计中非常重要,它不仅可以方便用户使用存储器、提高内存利用率,还可以通过虚拟技术从逻辑上扩充存储器。内存是为了缓和CPU与磁盘之间的速度矛盾。程序执行前都必须存放在内存中才能被CPU处理。
操作系统在对内存进行管理时,需要负责以下内容:
- 操作系统负责内存空间的分配与回收
- 操作系统需要提供某种技术从逻辑上对内存空间进行扩充
- 操作系统需要提供地址转换功能,负责程序的逻辑地址与物理地址的转换
- 操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰。
1. 可执行程序的形成
创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:
- 编译。由编译程序将用户源代码编译成若干目标模块。(编译就是把高级语言翻译为机器语言)
- 链接。由链接程序将编译后形成的一组目标模块及所需的库函数链接在一起,形成一个完整的装入模块。
- 装入。由装入程序将装入模块装入内存运行。
逻辑地址空间和物理地址空间
编译后,每个目标模块都从0号单元开始编址,这称为该目标模块的相对地址(或逻辑地址)。当链接程序将各个模块链接成一个完整的可执行目标程序时,链接程序顺序依次按各个模块的相对地址构成统一的从0号单元开始编址的逻辑地址空间。用户程序和程序员只需知道逻辑地址,而内存管理的具体机制则是完全透明的,只有系统编程人员才会涉及内存管理的具体机制。不同进程可以由相同的逻辑地址映射到主存的不同位置。
物理地址空间是指内存中物理单元的集合,它是地址变换的最终地址,进程在运行时执行指令和访问数据,最后都要通过物理地址从主存中存取。当装入程序将可执行代码装入内存时,必须通过地址转换将逻辑地址转换成物理地址,这个过程称为地址重定位。
2. 程序的链接
1)静态链接
在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行程序,以后不再拆开。
2)装入时动态链接
将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的方式。
3)运行时动态链接
对某些目标模块的链接,是在程序执行中需要该目标模块时才进行的。采用这种方式的优点是,一是便于修改和更新;二是便于实现对目标模块的共享。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅能加快程序的装入过程,而且可节省大量的内存空间。
3. 程序的装入(装入模块装入内存)
1)绝对装入(静态装入)
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。绝对装入只适用于单道程序环境。程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。通常情况下都是编译或汇编时再转换为绝对地址。
2)可重定位装入(静态重定位)
在多道程序环境下,多个目标模块的起始地址(简称始址)通常都从 0 开始,程序中的其他地址都是相对于始址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。装入时对目标程序中指令和数据的修改过程称为重定位,地址变换通常是在装入时一次完成的(逻辑地址变换成物理地址),以后不再改变,所以又称静态重定位。
特点:一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则不能装入该作业。此外,作业一旦进入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间。
3)动态运行时装入(动态重定位)
编译、链接后的装入模块的地址都是从 0 开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。允许程序在内存中发生移动。
小结:
- 绝对装入(静态装入)是指编程阶段就把物理地址计算好。
- 可重定位装入(静态重定位)是指装入时把逻辑地址转换成物理地址,但装入后不能改变。
- 动态重定位是指执行时再决定装入的地址并装入,装入后有可能会换出,所以同一个模块在内存中的物理地址是可能改变的。
4. 内存保护
内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。内存保护可采用两种方法:
- 在 CPU 中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,分别和两个寄存器的值相比,判断有无越界。
- 采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。
实现内存保护需要重定位寄存器和界地址寄存器。重定位寄存器是用来“加”的,逻辑地址加上重定位寄存器中的值就能得到物理地址;界地址寄存器是用来“比”的,通过比较界地址寄存器中的值与逻辑地址的值来判断是否越界。
3.1.2 覆盖与交换
覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法。
1. 覆盖
覆盖的基本思想是将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存。内存中分为一个“固定区”和若干个“覆盖区”。需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)。不常用的段放在“覆盖区”,需要用到时调入内存,用不到时调出内存。
它的特点是,打破了必须将一个进程的全部信息装入主存后才能运行的限制,但要是同时运行程序的代码量大于主存时还是不能运行,此外,内存中能够更新的地方只有覆盖区的段,不在覆盖区中的段会常驻内存。
必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点:对用户不透明,增加了用户编程负担。覆盖技术只用于早期的操作系统中,现在已成为历史。
2. 交换
交换(对换)的基本思想是,把处于等待状态(或在 CPU 调度原则下被剥夺运行权利或是内存空间紧张时)的程序从内存移到辅存,把内存空间腾出来,这一过程称作换出;把准备好竞争 CPU 运行的程序从辅存移到内存,这一过程又称换入。中级调度采用的就是交换技术,决定将哪个处于挂起状态的进程重新调入内存。
暂时换出外存等待的进程状态为挂起状态(挂起态,suspend)。挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态。
- 具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的I/O速度比文件区的更快。
- 交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。
- 可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间…
注意:PCB 会常驻内存,不会被换出外存。因为PCB可以记录着进程被存放在外存的位置等信息。
交换技术主要在不同进程(或作业)之间进行,而覆盖则用于同一个程序或进程中。
3.1.3 连续分配管理方式
连续分配方式是指为一个用户程序分配一个连续的内存空间。连续分配方式可分为四类:单一连续分配、固定分区分配、动态分区分配以及动态可重定位分区分配算法。
1. 内部碎片和外部碎片
内部碎片:分配给某进程的内存区域中,有些内存没有用上。
外部碎片:是指内存中的某些空闲分区由于太小而难以利用。
2. 单一连续分配(无外部碎片,有内部碎片)
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间。这种方式不需要进行内存保护,因为内存中永远只有一道程序,因此肯定不会因为访问越界而干扰其他程序。
注意:上面的为低地址,下面的为高地址。
优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(eg:早期的 PC 操作系统 MS-DOS)。
缺点:只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低。
3. 固定分区分配(无外部碎片,有内部碎片)
固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干固定大小的区域,每个分区只装入一道作业。当有空闲分区时,便可再从外存的后备作业队列中选择适当大小的作业装入该分区,如此循环。
固定分区分配在划分分区时有两种不同的方法:分区大小相等和分区大小不等。
- 分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合(比如:钢铁厂有n个相同的炼钢炉,就可把内存分为n个大小相等的区域存放n个炼钢炉控制程序)
- 分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分(比如:划分多个小分区、适量中等分区、少量大分区)
为了便于内存分配,操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。
当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”。未找到合适分区时,则拒绝为该用户程序分配内存。
优点:实现简单,无外部碎片。
缺点:
- 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;
- 主存利用率低,当程序小于固定分区大小时,也占用一个完整的内存分区空间,这样分区内部就会存在空间浪费,从而产生内部碎片。
固定分区分配很少用于现在通用的操作系统中,但在某些用于控制多个相同对象的控制系统中扔发挥着一定的作用。
4. 动态分区分配(无内部碎片,有外部碎片)
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。系统用空闲分区表和空闲分区链这两种常用的数据结构来记录内存的使用情况。
空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息。
分区号 | 分区大小(MB) | 起始地址(M) | 状态 |
---|---|---|---|
1 | 20 | 8 | 空闲 |
2 | 10 | 32 | 空闲 |
3 | 4 | 60 | 空闲 |
- 空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息。
- 在动态分区存储管理方式中,主要的操作是分配内存和回收内存。
- 回收内存有三种情况,这三种情况理解起来相对比较简单,这里就不多赘述,具体看图。
- 在存储管理中,会产生碎片问题的存储管理方式是动态分区存储管理(指外部碎片)。
- 分配内存需要用到分区分配算法。动态分区分配算法有两种,一种是基于顺序搜索的动态分区分配算法,另一种是基于索引搜索的动态分区分配算法。
1)基于顺序搜索的动态分区分配算法(一定要掌握)
为了实现动态分区分配,通常是将系统中的空闲分区链接成一个链。所谓顺序搜索,是指依次搜索空闲分区链上的空闲分区,去寻找一个其大小能满足要求的分区。
1. 首次适应算法(First Fit)
- 算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
- 如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
- 优点:优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区。这为以后到达的大作业分配大的内存空间创造了条件。
- 缺点:低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,称为碎片(又称为“零头”)。而每次查找又都是从低址部分开始的,这无疑又会增加查找可用空闲分区时的开销。
2. 最佳适应算法(Best Fit)
- 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
- 如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
- 缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。
3. 最坏适应算法(Worst Fit)(又称最大适应算法)
- 算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
- 如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
- 缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
4. 邻近适应算法(Next Fit)(又称循环首次适应算法)
- 算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。
- 如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
总结:首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)。邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)。
综合来看,四种算法中,首次适应算法的效果反而更好。
2)基于索引搜索的动态分区分配算法(这部分作为了解)
基于顺序搜索的动态分区分配算法,比较适用于不太大的系统。当系统很大时,系统中的内存分区可能会很多,相应的空闲分区链就可能很长,这时采用顺序搜索分区方法可能会很慢。为了提高搜索空闲分区的速度,在大、中型系统中往往会采用基于索引搜索的动态分区分配算法。
1. 快速适应算法(Quick Fit)(又称分类搜索算法)
是按照空闲分区的容量大小进行分类的,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这样系统中就会存在多个空闲分区链表。而且还要在内存中设立一张管理索引表,其中的每个索引表项对应一种空闲分区类型。
- 优点:查找效率高。
- 缺点:为了有效合并分区,在分区归还主存时的算法复杂,系统开销较大。
2. 伙伴系统(Buddy System)
该算法规定,无论已分配分区或空闲分区,其大小均为 2 的 k 次幂(k 为整数,1<=k<=m)。通常 2m 是整个可分配内存的大小(也就是最大分区的大小)。则系统开始运行时,整个内存区是一个大小为 2m 的空闲分区,在系统运行过程中,由于不断地划分,将会形成若干个不连续的空闲分区,将这些空闲分区按分区的大小进行分类,将大小相同的空闲分区设立在一个空闲分区双向链中。
当分配内存时,会优先从需要分配的内存块链表上查找空闲内存块,当发现对应大小的内存块都已经被使用后,那么会从更大一级的内存块上分配一块内存,并且分成一半给我们使用,剩余的一半释放到对应大小的内存块链表上。反正就是 2i 要是不够分,就用 2i+1 分成两份,一份给我们用,一份放在链表上。回收时则反之。其时间性能比快速适应算法差,但由于它采用了索引搜索算法,比顺序搜索算法好,而其空间性能,由于对空闲分区进行合并,减少了小的空闲分区,提高了空闲分区的可使用率,故优于快速适应算法,比顺序搜索法略差。
- 整个可分配的分区大小 2u
- 需要的分区大小为 2u-1 < s < 2u 时,把整个块分配给该进程
- 如 s ≤ 2i-1 -1,将大小为 2i 的当前空闲分区划分成两个大小为 2i-1 - 1 的空闲分区
- 重复划分过程,直到 2i-1 < s ≤ 2i ,并把一个空闲分区分配给该进程
实现方法:
数据结构:空闲块按大小和起始位置组织称二维数组;初始状态时,只有一个大小为 2u 的空闲块;
分配过程:1. 由小到大在空闲块数组中找最小的可用空闲块;2. 如果空闲块过大,对可用空闲块进行二等分,一个放入空闲块列表,另一块继续比较,直到得到合适的可用空闲块。
释放过程:1. 把释放的块放入空闲块数组; 2. 合并满足合并条件的空闲块
合并条件:大小相同 2i ;地址相邻;起始地址小的块的地址必须是 2i+1 的倍数。看起来有点抽象,其实就是地址较小的块的起始地址必须是要合并的块的大小的两倍的倍数。
3. 哈希算法
利用哈希快速查找的优点,以及空闲分区在可利用空闲区表中的分布规律,建立哈希函数,构造一张以空闲分区大小为关键字的哈希表,该表的每一个表项记录了一个对应的空闲分区链表表头指针。当进行内存分配时,根据所需空闲分区大小,通过哈希函数计算,即得到在哈希表中的位置,从中得到相应的空闲分区链表,实现最佳分配策略。
5. 动态可重定位分区分配
连续分配方式的一个重要特点是,一个系统或用户程序必须被装入一片连续的内存空间中。当一台计算机运行了一段时间后,它的内存空间将会被分割成许多个小的分区,而缺乏大的空闲空间。即使这些分散的许多小分区的容量总和大于要装入的程序,但由于分区不相邻接,所以没办法将该程序装入内存。这些“碎片”不能满足进程的需求,若想把大作业装入,可采用的一种方法是:将内存中的所有作用进行移动,使它们全都相邻接。这样就可以把原来分散的多个空闲小分区拼接成一个大分区,就可将一个作业装入。这种方式称为”拼接“或”紧凑“。
当系统对内存进行”紧凑“操作时,不需要对程序做任何修改,只要用该程序在内存的新起始地址去置换原来的起始地址即可。而该过程中需要用到重定位寄存器,用来存放程序(数据)在内存中的起始地址。地址变换过程是在程序执行期间,随着对每条指令或数据的访问自动进行的,故称为动态重定位。
动态重定位分区分配算法与动态分区分配算法基本上相同,差别仅是在这种分配算法中,增加了紧凑的功能。
3.1.4 非连续分配管理方式(离散分配方式)
支持多道程序的两种连续分配方式:
- 固定分区分配:缺乏灵活性,会产生大量的内部碎片,内存的利用率很低;
- 动态分区分配:会产生很多外部碎片,虽然可以用”紧凑“技术来处理,但是”紧凑“的时间代价很高。考虑到连续分配方式的缺点,所以人们就在想如果可以将一个进程分散地装入到许多不相邻的分区中,便可充分地利用内存,而无需再进行”紧凑“操作。基于这一思想,产生了“非连续分配方式”,或者称为“离散分配方式”。
- 连续分配:为用户进程分配的必须是一个连续的内存空间。
- 非连续分配:为用户进程分配的可以是一些分散的内存空间。
当然,若采用非连续分配管理方式,需要额外的空间去存储分散区域的索引。然后根据分区的大小是否固定,分为分页存储管理方式和分段存储管理方式。分页存储管理方式又根据运行作业时是否要把作业的所有页面都装入内存才能运行,分为基本分页存储管理方式和请求分页存储管理方式(虚拟内存管理的内容)。
1. 基本分页存储管理
为了使内存的使用能尽量避免碎片的产生,引入了分页的思想:把主存空间划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,也以块为单位逐个申请主存中的块空间。
分页和固定分区技术的本质区别就是:块的大小相对分区要小很多,而且进程也按照块进行划分,进程运行时按块申请主存可用空间并执行。进程只会在为最后一个不完整的块申请一个内存块空间时,才产生主存碎片,所以尽管会产生内部碎片,但这种碎片相对于进程来说也是很小的,每个进程平均只产生半个块大小的内部碎片(也称页内碎片)。
1)分页存储的几个基本概念
分页存储
将内存空间分为一个个大小相等的分区(比如:每个分区 4KB),每个分区就是一个“页框”(页框=页帧=内存块=物理块=物理页面)。每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从 0 开始。(前面这些各自对应相同意思的名词都要记住,因为考试的时候每个名词都有可能出现)。
将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面” 。每个页面也有一个编号,即“页号”,页号也是从 0 开始。
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。各个页面不必连续存放,可以放到不相邻的各个页框中。(注:进程的最后一个页面可能没有一个页框那么大。也就是说,分页存储有可能产生内部碎片(页内碎片),因此页框不能太大,否则可能产生过大的内部碎片造成浪费)
页表
为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。
注:页表通常存在 PCB(进程控制块)中。
- 一个进程对应一张页表
- 进程的每个页面对应一个页表项
- 每个页表项由“页号”和“块号”组成
- 页表记录进程页面和实际存放的内存块之间的映射关系
- 每个页表项的长度是相同的
-
在这里我们必须思考,每个页表项占多少个字节呢?
由于页号是隐含的,因此每个页表项占 3 B 3B 3B,存储整个页表至少需要 3 × ( n + 1 ) B 3 \times (n+1)B 3×(n+1)B。注意:页表记录的只是内存块号,而不是内存块的起始地址, J J J 号内存块的起始地址 = J J J × \times × 内存块大小。
-
将进程地址空间分页之后,操作系统该如何实现逻辑地址到物理地址的转换?
特点:虽然进程的各个页面是离散存放的,但是页面内部是连续存放的。
如果要访问逻辑地址 A,则- 确定逻辑地址 A 对应的 “页号” P
- 找到 P 号页面在内存中的起始地址(需要查页表)
- 确定逻辑地址 A 的 “页内偏移量” W
- 逻辑地址 A 对应的物理地址 = P 号页面在内存中的起始地址 + 页内偏移量 W
-
而这时又出现了一个新问题:如何确定一个逻辑地址对应的页号、页内偏移量?
在计算机内部,地址是用二进制表示的,如果页面大小刚好是 2 的整数幂,则计算机硬件可以很快速的把逻辑地址拆分成(页号,页内偏移量)。
页号 = 逻辑地址 / 页面长度
页内偏移量 = 逻辑地址 % 页面长度
总结:页面大小 刚好是 2 的整数幂有什么好处?
- 逻辑地址的拆分更加迅速: 如果每个页面大小为 2 k B 2^{k}B 2kB,用二进制数表示逻辑地址,则末尾 K K K 位,即为页内偏移量,其余部分就是页号。因此,如果让每个页面的大小为 2 的整数幂,计算机硬件就可以很方便地得出一个逻辑地址对应的页号和页内偏移量,而无需进行除法运算,从而提升了运行速度。
- 物理地址的计算更加迅速: 根据逻辑地址得到页号,根据页号查询页表从而找到页面存放的内存块号,将二进制表示的内存块号和页内偏移量拼接起来,就可以得到最终的物理地址。
逻辑地址结构
地址结构包含两个部分:前一部分为页号,后一部分为页内偏移量
W
W
W。
如果有
K
K
K 位表示“页内偏移量”,则说明该系统中一个页面的大小是
2
K
2^K
2K个内存单元(重要:页面大小等价于 2 的页内偏移量位数次幂)
如果有
M
M
M 位表示“页号”,则说明在该系统中,一个进程最多允许有
2
M
2^M
2M 个页面
提示:有些奇葩题目中页面大小有可能不是 2 的整数次幂,这种情况还是得用最原始的方法计算:
页号 = 逻辑地址 / 页面长度
页内偏移量 = 逻辑地址 % 页面长度
内存单元:内存单元是计算机存储数据的最小单位,以字节计数。一个内存单元的大小是一字节,也就是 8 比特,是一串 8 比特的二进制数。
2)地址变换机构
地址变换机构的任务就是通过借助进程的页表将逻辑地址转换成内存中的物理地址。通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址 F 和页表长度 M。进程未执行时,页表的始址 和 页表长度 放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
注意:页面大小是2的整数幂
设页面大小为 L,逻辑地址 A 到物理地址 E 的变换过程如下:
- 计算页号 P 和页内偏移量 W( 如果用十进制数手算,则 P=A/L,W=A%L;但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)
- 比较页号 P 和页表长度(页表项的个数) M M M,若 P ≥ M P≥M P≥M,则产生越界中断,否则继续执行。(注意:页号是从 0 开始的,而页表长度至少是 1 ,因此 P = M P=M P=M 时也会越界)
- 页表中页号 P 对应的页表项地址 = 页表起始地址 F + 页号 P * 页表项长度,取出该页表项内容 b,即为内存块号。(注意区分页表项长度、页表长度、页面大小的区别。页表长度指的是这个页表中总共有几个页表项,即总共有几个页;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间)
- 计算 E = b × L + W E = b \times L + W E=b×L+W,用得到的物理地址 E 去访存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了)
这整个过程有两次访问内存:第一次是当页号合法时,要加入内存查页表;第二次是根据物理地址访问物理内存对应的内存单元。
在分页存储管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。因此,页式管理中地址是一维的(只要确定了页面大小,然后我们再给出逻辑地址就可以确定页号和页内偏移量)。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中,页内偏移量占多少位。
在实际应用中,通常使一个页框恰好能放入整数个页表项,为了方便查找页表项,页表一般是放在连续的内存块中。
结论:理论上,页表项长度为 3B 即可表示内存块号的范围,但是,为了方便页表的查询,常常会让一个页表项占更多的字节,使得每个页面恰好可以装得下整数个页表项。
3)具有快表的地址变换机构(基本地址变换机构的改进版本)
快表,又称联想寄存器(TLB, translation lookaside buffer ),是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。
引入快表后,地址变换过程:
- CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
- 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
- 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)
例:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时 1us,访问一次内存耗时 100us。若快表的命中率为 90%,那么访问一个逻辑地址的平均耗时是多少?
(1+100) * 0.9 + (1+100+100) * 0.1 = 111 us
有的系统支持快表和慢表同时查找,如果是这样,平均耗时应该是 (1+100) * 0.9 + (100+100) * 0.1 =110.9 us
若未采用快表机制,则访问一个逻辑地址需要 100+100 = 200us
显然,引入快表机制后,访问一个逻辑地址的速度快多了。
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。因为局部性原理,一般来说快表的命中率可以达到 90% 以上。
基本地址变换和具有快表的地址变换的区别:
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行:如果某个数据被访问过,不久之后该数据很可能再次被访问。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
上小节介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项。
地址变换过程 | 访问一个逻辑地址的访存次数 | |
---|---|---|
基本地址变换机构 | 1. 算页号、页内偏移量 2. 检查页号合法性 3. 查页表,找到页面存放的内存块号 4. 根据内存块号与页内偏移量得到物理地址 5. 访问目标内存单元 | 两次访存 |
具有快表的地址变换机构 | 1. 算页号、页内偏移量 2. 检查页号合法性 3. 查快表。若命中,即可知道页面存放的内存块号,可直接进行 5;若未命中则进行 4 4. 查页表,找到页面存放的内存块号,并且将页表项复制到快表中 5. 根据内存块号与页内偏移量得到物理地址 6. 访问目标内存单元 | 快表命中,只需一次访存 快表未命中,需要两次访存 |
TLB和普通Cache的区别一一TLB中只有页表项的副本,而普通Cache中可能会有其他各种数的泰~
4)两级页表
单级页表有两个问题,问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。问题二:进程在一段时间内只需要访问某几个页面就可以正常运行了,没有必要让整个页表常驻内存。针对这两个问题,产生了两级页表。
单级页表结构的逻辑地址结构:
31 … 12 | 11 … 0 |
---|---|
页号 | 页内偏移量 |
把原本的20位页号分割成一级页号(页目录号)和二级页号(页表索引)。因此有了页目录表(顶级页表、外层页表)和二级页表。
31 … 22 | 21 … 12 | 11 … 0 |
---|---|---|
一级页号(页目录号) | 二级页号(页表索引) | 页内偏移量 |
能够用较少的内存空间存放页表的唯一方法是,仅把当前需要的一批页表项调入内存,以后再根据需要陆续调入。在采用两级页表结构的情况下,对于正在运行的进程,必须将其页目录表调入内存,而对于页表则只需调入一页或几页。为了表征某页的页表是否已经调入内存,还应再页目录表项中增设一个状态位 S,其值若为0,表示该页表分页不在内存中,否则说明其分页已调入内存。
需要注意的几个细节:
- 若分为两级页表后,页表依然很长,则可以采用更多级页表,一般来说各级页表的大小不能超过一个页面。为了查询方便,顶级页表最多只能有一个页面。
例:某系统按字节编址,采用 40 位逻辑地址,页面大小为 4KB,页表项大小为 4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
页面大小 = 4KB =212B,按字节编址,因此页内偏移量为12位
页号 = 40 - 12 = 28 位
页面大小 = 212B,页表项大小 = 4B ,则每个页面可存放 212 / 4 = 210 个页表项
因此各级页表最多包含 210 个页表项,需要 10 位二进制位才能映射到 210 个页表项,因此每一级的页表对应页号应为10位。总共28位的页号至少要分为三级.
- 两级页表的访存次数分析(假设没有快表机构)
第一次访存:访问内存中的页目录表
第二次访存:访问内存中的二级页表
第三次访存:访问目标内存单元
总结:N 级页表访问一个逻辑地址需要 N + 1 次访存。多级页表解决了当逻辑地址空间过大时,页表的长度会大大增加的问题。而采用多级页表时,一次访问磁盘需要多次访问内存甚至磁盘,会大大增加一次访存的时间。
2. 基本分段存储管理方式
分页管理方式是从计算机的角度考虑设计的,目的是提高内存的利用率,提升计算机的性能。分页通过硬件机制实现,对用户完全透明。分段管理方式的提出则考虑了用户和程序员,把通常的程序分成若干个段,如主程序段、子程序段A、子程序B……、数据段及栈段等,每个段大多是一个相对独立的逻辑单位,方便于程序员的操作;也满足了方便编程、信息保护和共享、动态增长及动态链接等多方面的需要。
1)分段
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻(段内要求连续,段间不要求连续)。由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高。
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。
段号的位数决定了每个进程最多可以分几个段,段内地址位数决定了每个段的最大长度是多少。
2)段表
程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称“段表”。
- 每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度。
- 各个段表项的长度是相同的。例如:某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号16位, 段内地址16位),因此用16位即可表示最大段长。物理内存大小为 4GB(可用32位表示整个物理内存地址空间)。因此,可以让每个段表项占 16 + 32 = 48位,即 6B。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为 M,则 K号段对应的段表项存放的地址为 M + K*6。
段表寄存器:段表始址F、段表长度M(段表项的个数)
3)地址变换
经过编译程序编译后,形成等价的机器指令:“取出段号为 x,段内地址为xxx的内存单元种的内容,放到寄存器xx中”。(机器指令中的逻辑地址用二进制表示,CPU 执行指令时需要将逻辑地址变换为物理地址)。
- 根据逻辑地址得到段号 S、段内地址 W。(进程切换相关的内核程序负责恢复进程运行环境;进程切换时,段表会更新。)
- 第一次判断:判断段号是否越界。若 S ≥ M,则产生越界中断,否则继续执行。(注:段表长度至少是 1 ,而段号从 0 开始)
- 查询段表,找到对应的段表项,段表项的存放地址为 F + S * 段表项长度。
- 第二次判断。检查段内地址是否超过段长。若 W ≥ C,则产生越界中断,否则继续执行。(这个判断是跟分页管理方式最大的区别,因为每个段的大小是不相等的,所以需要多一次判断)
- 计算得到物理地址。
4)分段、分页管理的对比
页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。 段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
分段比分页更容易实现信息的共享和保护。不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的(比如,有一个代码段,只是简单的输出 “Hello World!”)。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)。
只需让各进程的段表项指向同一个段即可实现共享。
按照分页管理方式显然是不合理的:页面不是按逻辑模块划分的,很难实现共享
访问一个逻辑地址需要几次访存?
分页(单级页表):第一次访存——查内存中的页表,第二次访存——访问目标内存单元。总共两次访存。
分段:第一次访存——查内存中的段表,第二次访存——访问目标内存单元。总共两次访存。
与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。
3. 段页式管理方式
页式存储管理能有效地提高内存利用率,而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方法结合起来,便形成了段页式存储管理方式。
1)分页、分段管理方式中最大的优缺点
分页管理:内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片。缺点:不方便按照逻辑模块实现信息的共享和保护。
分段管理:很方便按照逻辑模块实现信息的共享和保护。缺点:如果段长过大,为其分配很大的连续空间会很不方便。另外,段式管理会产生外部碎片。(分段管理中产生的外部碎片也可以用“紧凑”来解决,知识需要付出较大的时间代价)
2)先分段再分页
将进程按逻辑模块分段,再将各段分页(先分段再分页,如每个页面4KB)。再将内存空间分为大小相同的内存块/页框/页帧/物理块,将各页面分别装入各内存块中。
3)段页式管理的逻辑地址结构
段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。段页式系统中的页号和页内偏移量就是把分段系统的逻辑地址的段内地址去进行拆分。段号的位数决定了每个进程最多可以分几个段,页号位数决定了每个段最大有多少页,页内偏移量决定了页面大小、内存块大小是多少。
如:若系统是按字节寻址的,则段号占 16 位,因此在该系统中,每个进程最多有 216 = 64K 个段,页号占 4 位,因此每个段最多有 24 = 16页,页内偏移量占 12 位,因此每个页面/每个内存块大小为 212 = 4096 = 4KB。
31 … 16 | 15 … 12 | 11 … 0 |
---|---|---|
段号 | 页号 | 页内偏移量 |
“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。因此段页式管理的地址结构是二维的。
每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。
每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。
在一个进程中,段表只有一个,而页表可能有多个。
4)地址变换
在段页式系统中,访问一个逻辑地址所需访存次数须三次访问内存,但是可以引入快表机构,以段号和页号为关键字查询快表,即可直接找到最终的目标页面存放地址,引入快表后只需一次访问内存。
- 根据逻辑地址得到段号 S、页号 P、页内偏移量 W。
- 判断段号是否越界。若 S ≥ M,则产生越界中断,否则继续执行。
- 查询段表,找到对应的段表项,段表项的存放地址为 F + S * 段表项长度。
- 第一次访存。检查页号是否越界,若 页号 ≥ 页表长度,则发生越界中断,否则继续执行。
- 第二次访存。根据页表存放块号、页号查询页表找到对应页表项。
- 根据内存块号、页内偏移量得到最终的物理地址。(也可引入快表机构,用段号和页号作为查询快表的关键字。若快表命中则仅需一次放存。)
- 第三次访存。访问目标内存单元。
3.1.5 补充
-
为什么要进行内存管理?
在单道批处理系统阶段,一个系统在一个时间段内只执行一个程序,内存的分配极其简单,即仅分配给当前运行的进程。引入多道程序的并发执行后,进程之间共享的不仅仅是处理机,还有主存储器。然而,共享主存会形成一些特殊的挑战。若不对内存进行管理,则容易导致内存数据的混乱,以至于限制进程的并发执行。因此。为了更好地支持多道程序并发执行,必须进行内存管理。 -
在虚拟内存管理中,地址变换机构将逻辑地址变换为物理地址,形成该逻辑地址的阶段是链接。(若题目是“完成该变换过程的阶段是”,则是装载)
编译后的程序需要经过链接才能装载,而链接后形成的目标程序中的地址也就是逻辑地址。以C语言为例:C程序经过预处理→编译→汇编→链接产生了可执行文件,其中链接的前一步是产生可重定位的二进制目标文件。C语言采用源文件独立编译的方法,如程序main.c,file1.c,file2.c,file1.h,file2.h在链接的前一步生成了main.o,file1.o,file2.o,这些目标模块的逻辑地址都从0开始,但只是相对于该模块的逻辑地址。链接器将这三个文件、libc和其库文件链接成一个可执行文件。链接阶段主要完成重定位,形成整个程序的完整逻辑地址空间。例如,file1.o的逻辑地址为0 ~ 1023,main.o的逻辑地址为0 ~ 1023,假设链接时将file1.o链接在main.o之后,则链接之后file1.o对应的逻辑地址应为1024 ~ 2047,整个过程如下图:
-
在使用交换技术时,若一个进程正在I/O操作,则不能交换内存,否则其I/O数据区将被新换入的进程占用,导致错误。不过可以在操作系统中开辟I/O缓冲区,将数据从外设输入或将数据输出到外设的I/O活动在系统缓冲区中进行,这时的系统缓冲区与外设I/O,进程交换都不受限制。
-
在存储管理中,采用覆盖与交换技术的目的是为了解决主存空间不足的问题,但不是在物理上扩充主存,只是将暂时不用的部分换成内存,以节省空间,从而在逻辑上扩充主存。覆盖技术是早期在单一连续存储管理中使用的扩大存储容量的一种技术,它同样可用于固定分区分配的存储管理。
-
分区分配内存管理方式的主要保护措施是界地址保护。每个进程都拥有自己独立的进程空间,若一个进程在运行时所产生的地址在其地址空间之外,则发生地址越界,因此需要进行界地址保护,即当程序要访问某个内存单元时,由硬件检查是否允许,若允许则执行,否则产生地址越界中断。内存保护需要由操作系统和硬件机构合作完成,以保证进程空间不被非法访问。
-
固定分区方式中,作业装入后位置不再改变,可以采用静态重定位。
-
采用分页或分段管理后,提供给用户的物理空间是不能确定的。因为页表和段表都存储在内存中,系统提供给用户的物理地址空间为总空间大小减去页表或段表的长度。由于页表和段表的长度不能确定,所以提供给用户的物理地址空间大小也不能确定。
-
内存分页管理是在硬件和操作系统层面实现的,对用户、编译系统、连接装配程序等上层是不可见的。所以分页系统中的页面是为操作系统所感知的。
-
页式存储管理中,页表的始地址存放在寄存器中。
-
对重定位存储管理方式,应在整个系统中设置一个重定位寄存器。
-
采用段式存储管理时,一个程序如何分段时在用户编程时决定的。
-
可重入程序是通过减少对换数量方法来改善系统性能的,可重入程序主要是通过共享来使用同一块存储空间的,或通过动态链接的方式将所需的程序段映射到相关进程中去,其最大的优点是减少了对程序段的调入/调出,因此减少了对换数量。
-
程序的动态链接与程序的逻辑结构有关,分段存储管理将程序按照逻辑段进行划分,因此有利于其动态链接。其他的内存管理方式与程序的逻辑结构无关。
-
操作系统中实现分区存储管理的代价最小,是满足多道程序设计的最简单的存储管理方案,特别适合嵌入式等微型设备。而实现分页、分段和段页式存储管理需要特定的数据结构支持,如页表、段表等,为了提高性能,还需要硬件提供快存和地址加法器等,代价高。
-
对外存对换区的管理以提供换入、换出速度为主要目标。
-
在页式存储管理中,当页面大时,用于管理页面的页表就少,但是页内碎片会比较大;当页面小时,用于管理页面的页表就大,但是页内碎片小。所以可以通过适当的计算获得较佳的页面大小和较小的系统开销。而当页面大小确定后,所划分的页面大小都必须相等大小。
-
存储管理的目的有两个:一个是方便用户,二是提高内存利用率。
-
对主存储器的访问,以字节或字为单位。
-
在分页存储管理中,逻辑地址分配时按页为单位进行分配的,而主存的分配即物理地址的分配,是以内存块为单位分配的。(要注意是对主存储器的访问还是分配)
-
分页存储管理中,作业地址空间是一维的,即单一的线性地址空间,程序员只需要一个记忆符来表示地址。在分段存储分配管理中,段之间是独立的,而段长不定长,而页长是固定的,因此作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。简言之,确定一个地址需要几个参数,作业地址空间就是几维的。
-
段页式存储管理汲取了页式管理和段式管理的长处,其实现原理结合了页式和段式管理的基本思想,即用分段方法来分配和管理用户地址空间,用分页方法来管理物理存储空间。但它的开销要比段式和页式管理的开销大。
-
一道关于二级页表的题
关于多级页表有两个需要注意的点:
①在多级页表的分页存储管理方式中,一个页表最大只能占一个块。
②有几个二级页表,在一级页表中就有几个页表项。
题解为:
注:本节的内容要多做题才能比较好地理解。
3.2 虚拟内存管理
3.2.1 虚拟内存的基本概念
1. 传统存储管理方式的特征和缺点
传统存储管理方式主要有两个特征:
- 一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:1. 作业很大时,不能全部装入内存,导致大作业无法运行;2. 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。
- 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源。
由上述可知,许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间,而一些需要运行的作业而无法装入运行,显然浪费了宝贵的内存资源。
2. 局部性原理
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)
快表机构就是将近期常访问的页表项副本放到更高速的联想寄存器中。
而要应对局部性原理可以采用高速缓冲技术的思想:将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速存储器中。快表、页高速缓存及虚拟内存技术从广义上讲,都属于高速缓存技术。
3. 虚拟内存的定义和特征
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存(换入),然后继续执行程序。若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存(换出)。在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟存储器。之所以将其称为虚拟存储器,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入、请求调入和置换功能后(对用户完全透明),给用户的感觉是好像存在一个比实际物理内存大得多的存储器。
易混知识点:
虚拟内存的最大容量是由计算机的地址结构(CPU寻址范围)确定的
虚拟内存的实际容量 = min(内存和外存容量之和,CPU寻址范围)
虚拟内存有以下三个主要特征:
- 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
- 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
4. 虚拟内存技术的实现
虚拟内存技术允许将一个作业分多次调入内存。采用连续分配方式时,会使相当一部分内存空间都处于暂时或“永久”的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。
不管是采用虚拟内存的哪种方式,都需要有一定的硬件支持,一般需要的支持有以下几个方面:
- 一定容量的内存和外存。
- 页表机制(或段表机制),作为主要的数据结构。
- 中断机构,当用户程序要访问的部分尚未调入内存时,则产生中断。
- 地址变换机构,逻辑地址到物理地址的变换。
3.2.2 请求分页管理方式
请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。(可以与基本分页存储管理对比学习)。请求分页存储管理方式和基本分页存储管理方式的区别是,前者采用虚拟技术,因此开始运行时,不必将作业一次性全部装入内存,而后者不是。
在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
在请求分页系统中,只要求将当前需要的一部分页面装入内存,便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存中时,再通过请求调页功能,将缺失页面从外存调入内存;同时还可通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。
为了实现请求分页,系统必须提供一定的硬件支持。除了需要一定容量的内存及外存的计算机系统,还需要有页表机制、缺页中断机构和地址变换机构。
1. 页表机制
与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存(要注意这里,当页面没有被修改时,就不用重新写回外存了)。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
相比基本分页管理,请求分页存储管理的页表增加了四个字段。
- 状态位(合法位)。用于指示该页是否已调入内存,供程序访问时参考。
- 访问字段。用于记录本页在一段时间内被访问的次数,或记录本页最近已有多长时间未被访问,供置换算法换出页面时参考。
- 修改位。标识该页在调入内存后是否被修改过。
- 外存地址。用于指出该页在外存上的地址,通常是物理块号,供调入该页时参考。
2. 缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断中的”故障“,即可能被系统修复的异常。它同样也要经历保护CPU环境、分析中断原因、转入缺页中断处理程序、恢复CPU环境等几个步骤。但与一般的中断相比,它有以下两个明显的区别:
- 在指令执行期间产生和处理中断信号。而不像通常一样,等一条指令执行完再去检查是否有中断请求到达。缺页中断在指令执行期间,若发现所要访问的指令或数据不在内存时,便立即产生和处理缺页中断信号,以便能及时将所缺的页面调入内存。
- 一条指令在执行期间,可能产生多次缺页中断。(如:copy A to B,即将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)
中断的分类:
内中断(内部异常)-- 信号来源: CPU 内部
- 陷阱、陷入(trap):有意而为之的异常,如系统调用
- 故障(fault):由错误条件引起的,可能被故障处理程序修复,如缺页中断
- 终止(abort):不可恢复的致命错误造成的结果,终止处理程序不再将控制返回给引发终止的应用程序,如整数除0。
外中断:信号来自于 CPU 外部,比如 I/O 中断请求、人工干预
3. 地址变换机构
请求分页系统中的地址变换机构是在分页系统地址变换机构的基础上,为实现虚拟内存,又增加了某些功能而形成的。
- 快表中有的页面一定是在内存中的。若某个页面被换出外存,则快表中的相应表项也要删除,否则可能访问错误的页面。
- 找到对应页表项后,若对应页面未调入内存,则产生缺页中断之后由操作系统的缺页中断处理程序进行处理
补充细节:(序号和上图的对应)
- 只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。
- 和普通的中断处理一样,缺页中断处理依然需要保留CPU现场。
- 需要用某种“页面置换算法”来决定一个换出页面。
- 换入/换出页面都需要启动慢速的I/O操作,可见,如果换入/换出太频繁,会有很大的开销。
- 页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。
故在具有快表机构的请求分页系统中,访问一个逻辑地址时,若发生缺页,则地址变换步骤是:
查快表(未命中)——查慢表(发现未调入内存)——调页(调入的页面对应的表项会直接加入快表)——查快表(命中)——访问目标内存单元
访问内存的有效时间,有三种情况:
-
被访问页在内存中,且其对应的页表项在快表中。此时不存在缺页中断情况,内存的有效访问时间(EAT)分为查找快表的时间(λ)和访问实际物理地址所需的时间(t):$ EAT=λ+t$
-
被访问页在内存中,且其对应的页表项不在快表中。此时不存在缺页中断情况,但需要两次访问内存,一次读取页表,一次读取数据,另外还需要更新快表。所以,这种情况内存的有效访问时间可分为查找快表的时间、查找页表的时间、修改快表的时间和访问实际物理地址的时间: E A T = λ + t + λ + t EAT=λ+t+λ+t EAT=λ+t+λ+t
-
被访问页不在内存中。此时需要进行缺页中断处理,所以这种情况的内存的有效访问时间可分为查找快表的时间、查找页表的时间、处理缺页中断的时间、更新快表的时间和访问实际物理地址的时间:假设缺页中断处理时间为ε,则 $EAT=λ+t+ε+λ+t=ε+2(λ+t) $
上面的三种情况都没有考虑快表的命中率(α)和缺页率(f)等因素,因此,加入这两个因素后,内存的有效访问时间的计算公式应为
E A T = λ + α × t + ( 1 − α ) × [ t + f × ( ε + λ + t ) + ( 1 − f ) × ( λ + t ) ] EAT=λ + α \times t + (1-α) \times [ t + f \times (ε + λ + t) + (1 - f) \times (λ + t) ] EAT=λ+α×t+(1−α)×[t+f×(ε+λ+t)+(1−f)×(λ+t)]
3.2.3 页面置换算法(决定应该换入哪页、换出哪页)
请求分页存储管理与基本分页存储管理的主要区别:在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。此时换出外存并把所要访问的信息调入内存时需要用到的算法称为页面置换算法。置换算法的好坏将直接影响到系统的性能。页面的换入、换出需要磁盘I/O,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率。
(注:在做置换算法的题时记得先把是否缺页的表画出来)
1. 最佳置换算法(OPT)
每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。 但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
2. 先进先出置换算法(FIFO)
- 每次选择淘汰的页面是最早进入内存的页面。
- 实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块。
Belady 异常:当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有 FIFO 算法会产生 Belady 异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差。
3. 最近最久未使用置换算法(LRU)
每次淘汰的页面是最近最久未使用的页面,实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间 t。当需要淘汰一个页面时,选择现有页面中 t 值最大的,即最近最久未使用的页面。
该算法的实现需要专门的硬件(寄存器和栈)支持,虽然算法性能好,但是实现困难,开销大。LRU 是堆栈类的算法,理论上可以证明,堆栈类算法不可能出现 Belady 异常。FIFO 算法基于队列实现,不是堆栈类算法。
4. 时钟置换算法(CLOCK)
最佳置换算法性能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。
时钟置换算法是一种性能和开销较均衡的算法,因为算法要循环扫描缓冲区,像时钟的指针一样在转动,所以又称 CLOCK算法,或最近未用算法(NRU,Not Recently Used)。简单的 CLOCK 算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为 1。当需要淘汰一个页面时,只需检查页的访问位。如果是 0,就选择该页换出;如果是 1,则将它置为 0,暂不换出,继续检查下一个页面;若第一轮扫描中所有页面都是 1,则将这些页面的访问位依次置为 0 后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的 CLOCK 算法选择一个淘汰页面最多会经过两轮扫描)
5. 改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免 I/O 操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位 = 1,表示页面被修改过。为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。
- 第一轮:淘汰(0,0)
- 第二轮:淘汰(0,1),并将扫描过的页面访问位都置为0
- 第三轮:淘汰(0,0)
- 第四轮:淘汰(0,1)
算法规则:将所有可能被置换的页面排成一个循环队列
3.2.4 页面分配策略
1. 驻留集
驻留集:指请求分页存储管理中给进程分配的物理块的集合。在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。若驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少;驻留集太大,又会导致多道程序并发度下降,资源利用率降低(CPU 或 I/O 设备等资源有可能出现空闲的情况)。所以应该选择一个合适的驻留集大小。
在请求分页系统中,可采用两种内存分配策略,即固定和可变分配策略。
- 固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变。
- 可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变。
在进行置换时,也可采用两种策略,即全局置换和局部置换。
-
局部置换:发生缺页时只能选进程自己的物理块进行置换。
-
全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。
由于固定分配的驻留集大小是不变的,但全局置换时,物理块数量必然会改变,所以不可能是固定分配全局置换。于是,只能组合出以下三种适用的策略:
2. 固定分配局部置换
系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。这种策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理。若太少,会频繁出现缺页中断,降低了系统的吞吐量。若太多,又必然使内存中驻留的进程数目减少,进而可能造成CPU空闲或其他资源空闲的情况,而且在实现进程对换时,会花费更多的时间。(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)
3. 可变分配全局置换
刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定(系统会锁定一些页面,这些页面中的内容不能置换出外存(如:重要的内核数据可以设为“锁定”))的页面换出外存,再将该物理块分配给缺页的进程。采用这种策略时,只要某进程发生缺页,都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加。
4. 可变分配局部置换
刚开始会为每个进程分配一定数量的物理块。当某进程发生缺页时,只允许从该进程自己的物理块中选出一个进行换出外存。如果进程在运行中频繁地缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块。
区别:
- 可变分配全局置换:只要缺页就给分配新物理块。
- 可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块。
为使进程能够正常运行,必须事先将要执行的那部分程序和数据所在的页面调入内存,现在的问题是:何时调入页面和从何处调入页面。
5. 何时调入页面
- 预调页策略:根据局部性原理(主要指空间局部性,即:如果当前访问了某个内存单元,在之后很有可能会接着访问与其相邻的那些内存单元),一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入(运行前调入), 由程序员指出应该先调入哪些部分。
- 请求调页策略:进程在运行期间发现缺页时才将所缺页面调入内存(运行时调入)。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘 I/O 操作,因此 I/O 开销较大。
一般情况下,两种调页策略会同时使用。
6. 从何处调入页面
请求分页系统中的外存分为两部分:用于存放文件的文件区和用于存放对换页面的对换区。对换区通常采用连续分配方式,而文件区采用离散分配方式,因此对换区的磁盘 I/O 速度比文件区的更快。
- 系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
- 系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。
UNIX 方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。
3.2.5 抖动
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)。而为进程分配的物理块太少,会使进程发生抖动现象。为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率。所以为了研究为应该为每个进程分配多少个物理块,Denning 提出了进程“工作集”的概念。
3.2.6 工作集
驻留集:指请求分页存储管理中给进程分配的内存块的集合。
工作集:指在某段时间间隔里,进程实际访问页面的集合。
工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为5,经过一段时间的监测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。
拓展:基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法——选择一个不在工作集中的页面进行淘汰。
3.2.7 地址翻译
3.2.8 补充
-
虚拟内存(虚存)空间的大小由什么因素决定?
虚存的容量要满足以下两个条件:- 虚存的实际容量 ≤ 内存容量和外存容量之和,这是硬件的硬性条件规定的,若虚存的实际容量超过了这个容量,则没有相应的空间来供虚存使用。
- 虚存的最大容量 ≤ 计算机的地址位数能容纳的最大容量。假设地址是32位的,按字节编址,一个地址代表1B存储空间,则虚存的最大容量 ≤ 4GB(232B)。这是因为若虚存的最大容量超过4GB,则32位的地址将无法访问全部虚存,也就是说4GB以后的空间被浪费了,相当于没有一样,没有任何意义。
- 实际虚存的容量是取决条件①和②的交集,即两个条件都要满足,仅满足一个条件是不行的。
-
虚拟内存是怎么解决问题的?会带来什么问题?
虚拟内存使用外存上的空间来扩充内存空间,通过一定的换入/换出,使得整个系统在逻辑上能够使用一个远远超出其物理内存大小的内存容量。因为虚拟内存技术调换页面时需要访问外存,会导致平均访存时间增加,若使用了不合适的替换算法,则会大大降低系统性能。 -
进程在执行中发生了缺页中断,经操作系统处理后,应让其执行被中断的那一条指令。缺页中断调入新页面,肯定要修改页表项和分配页框,同时内存没有页面,需要从外存读入,也会发生磁盘I/O。
-
虚拟存储技术是补充内存逻辑空间的技术。并未实际扩充内存和外存,而是采用相关技术相对地扩充主存。
虚拟存储器的最大容量是由计算机的地址结构决定的,与主存容量和外存容量没有必然的联系。虽然从实际使用来说,虚拟存储器能使得进程的可用内存扩大道内外存容量之和,但进程内存寻址仍由计算机的地址结构决定,这就决定了虚拟存储器理论上的最大容量。比如,64位系统环境下,虚拟内存技术使得进程可用内存空间达264B,但外存显然是达不到这个大小的。虚拟存储技术基于程序的局部性原理。局部性越好,虚拟存储系统越能更好地发挥作用。
虚拟存储扩充内存的基本方法是将一些页或段从内存中调入、调出,而调入、调出的基本手段是覆盖与交换。 -
请求分页存储管理中,若采用FIFO页面淘汰算法,可能会产生当驻留集增大时,页故障数不减反增的Belady异常。然而,还有另外一种情况。例如,当页面序列为 1,2,3,1,2,3时,页帧数增加,缺页中断会减少。则当可供分配的页帧数增加时,缺页中断的次数可能增加也可能减少。
-
导致LRU算法实现起来耗费高的原因是需要硬件的特殊支持。LRU算法需要对所有页最近一次被访问的时间进行记录,查找时间最久的进行替换,这涉及到排序,而排序需要硬件的支持。
-
在虚拟存储器系统的页表项中,决定是否会发生页故障的是合法位(状态位),合法位信息是用来显示本页面是否在内存中的。
-
在页面置换策略中,所有策略都可能引起抖动。
-
请求分页存储管理的主要特点是扩充了内存。基于局部性原理,运行一个进程时不需要让整个进程进入内存。
-
在请求分页存储管理的页表中增加了若干项信息,其中修改位和访问位是供置换算法参考。
-
产生内存抖动的主要原因是页面置换算法不合理。
-
在进程运行时,若其工作集页面都在主存储器内(不是虚拟存储器,因为虚拟内存还有硬盘的那部分),则能够使该进程有效地运行,否则会出现频繁的页面调入/调出现象。
-
覆盖技术与虚拟存储技术有何本质上的不同?交换技术与虚拟存储技术中使用的调入/调出技术有何不同之处?
解答:
1)覆盖技术与虚拟存储技术最本质的不同在于,覆盖程序段的最大长度要受内存容量大小的限制,而虚拟存储器中程序的最大长度不受内存容量的限制,只受计算机地址结构的限制。
另外,覆盖技术中覆盖段由程序员设计,且要求覆盖段中的各个覆盖区具有相对独立性,不存在直接联系或相互交叉访问;而虚拟存储技术对用户的程序段没有这种要求。
2) 交换技术就是把暂时不用的某个程序及数据从内存移到外存中,以便腾出必要的内存空间,或把指定的程序或数据从外存读到内存中的一种内存扩充技术。
交换技术与虚存中使用的调入/调出技术的主要相同点是,都要在内存与外存之间交换信息。
交换技术与虚存中使用的调入/调出技术的主要区别是:交换技术调入/调出整个过程中,一个进程的大小要受内存容量大小的限制;而虚存中使用的调入/调出技术在内存和外存之间来回传递的是页面或分段,而不受整个进程,从而使得进程的地址映射具有更大的灵活性,且允许进程的大小比可用的内存空间大。 -
在页式虚存管理系统中,假定驻留集为 m 个页帧(初始所有页帧均为空),在长为 p 的引用串中具有 n 个不同页号(n>m),对于FIFO、LRU两种页面置换算法,试给出页故障数的上限和下限。
发生页故障的原因是,当前访问的页不在主存,需要将该项调入主存。此时不管主存中是否己满(已满则先调出一页),都要发生一次页故障,即无论怎样安排,n个不同的页号在首次进入主存时必须要发生一次页故障,总共发生n次,这是页故障数的下限。虽然不同的页号数为n小于等于总长度p (访问串可能会有一些页重复出现),但驻留集m <n,所以可能会有某些页进入主存后又被调出主存,当再次访问时又发生一次页故障的现象, 即有些页可能会出现多次页故障。最差的情况是每访问一个页号时,该页都不在主存中,这样共发生p次故障。
因此,对于FIFO、LRU置换算法,页故障数的上限均为p,下限均为n。 -
页框号是什么?在置换掉失效页面时页框号会变吗?
页框号指向的是内存的实际物理地址的页号。在进程启动时,分配了哪些内存块应该是已经确定的(即工作集多大,但也可以是使用全局置换策略变化的)。所以在页面失效时,虚拟页号可能会失效而从快表中删除,但是新的页号替换了那个旧页就继承了它的页框号。 -
根据数组的随机存取特点,数组在虚拟地址空间中所占的区域必须连续,由于数组不止占用一页,相邻逻辑页在物理上不一定相邻,因此数组在物理地址空间中所占的区域可以不连续。
-
在请求分页式存储管理中,地址重定位是在指令执行时进行的。
四、文件管理
4.1 文件管理基础
4.1.1 文件的概念
1. 数据项、记录和文件
基于文件系统的概念,可以把数据组成分为数据项、记录和文件三级。
- 数据项
在文件系统中,数据项是最低级的数据组成形式,可把它分成以下两种类型:
1)基本数据项。这是用于描述一个对象的某种属性的字符集,是数据组织中可以命名的最小逻辑数据单位,又称为字段。例如,用于描述一个学生的基本数据项有:学号、姓名、年龄、所在班级等。
2)组合数据项。是由若干个基本数据项组成的,简称组项。例如工资是个组项,它可由基本工资、工龄工资和奖励工资等基本项所组成。 - 记录
记录时一组相关数据项的集合,用于描述一个对象在某方面的属性。如一个学生的记录可以是学号、姓名、年龄以及所在班级等数据项组成的。而每个记录都需要有一个关键字来唯一标识。如学号就可以唯一标识一条记录。 - 文件
文件是指由创建者所定义的、具有文件名的一组相关元素的集合,可分为有结构文件和无结构文件两种。在有结构文件中,文件由一组相似的记录组成,报考某学校的所有考生的报考信息记录,又称为记录式文件;而无结构文件则被视为一个字符流,比如一个二进制文件或字符文件,又称流式文件。
文件、记录和数据项之间的层次关系:
2. 文件属性
- 文件名:由创建文件的用户决定文件名,主要是为了方便用户找到文件,同一目录下不允许有重名文件。
- 标识符:一个系统内的各文件标识符唯一,对用户来说毫无可读性,因此标识符只是操作系统用于区分各个文件的一种内部名称。
- 类型:指明文件的类型。
- 位置:文件存放的路径(让用户使用)、在外存中的地址(操作系统使用,对用户不可见)。
- 大小:指明文件大小。
- 保护信息:对文件进行保护的访问控制信息。
- 时间、日期和用户标识:文件创建、上次修改和上次访问的相关消息,用于保护和跟踪文件的使用。
所有文件的信息都在保存目录结构中,而目录结构保持在外存上。文件信息在需要时才调入内存。通常,目录条目包括文件名称及其唯一的标识符,而标识符定位其他属性的信息。
所谓的“目录”,其实就是我们熟悉的“文件夹”。目录其实也是一种特殊的有结构文件(由记录组成)。
3. 文件系统的接口
为方便用户的使用,文件系统以接口的形式提供了一组对文件和记录操作的方法和手段。通常是下面两种类型的接口:
- 命令接口,是指作为用户与文件系统直接交互的接口,用户可通过键盘终端键入命令取得文件系统的服务。
- 程序接口,是指作为用户程序与文件系统的接口,用户程序可通过系统调用取得文件系统的服务,例如,用于创建文件的系统调用 Create,用于打开一个文件的系统调用 Open 等。
4. 文件的基本操作
- 创建文件。进行 Create 系统调用时,需要提供的参数:所需的外存空间大小、文件存放路径、文件名。 创建文件有两个必要步骤:一是在外存中找到文件所需的空间(结合空闲链表法、位示图、成组链接法等管理策略,找到空闲空间);二是根据文件存放路径的信息找到该目录对应的目录文件,在目录中创建该文件对应的目录项。目录项中包含了文件名称、在文件系统中的位置及其他可能的信息。
- 删除文件。进行 Delete 系统调用时,需要提供的几主要参数:文件存放路径、文件名。步骤:1. 根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的目录项。2.根据该目录项记录的文件在外存的存放位置、文件大小等信息,回收文件占用的磁盘块。(回收磁盘块时,根据空闲表法、空闲链表法、位图法等管理策略的不同,需要做不同的处理)3.从目录表中删除文件对应的目录项。
- 读文件。进程使用 read 系统调用 完成写操作。需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可),还需要指明要读入多少数据(如:读入1KB)、指明读入的数据要放在内存中的什么位置。操作系统在处理 read 系统调用时,会从读指针指向的外存中,将用户指定大小的数据读入用户指定的内存区域中。
- 写文件。进程使用 write 系统调用 完成写操作,需指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可),还需要指明要写出多少数据(如:写出1KB)、写回外存的数据放在内存中的什么位置。操作系统在处理 write 系统调用时,会从用户指定的内存区域中,将指定大小的数据写回写指针指向的外存。
- 设置文件的读/写位置。前面所述的文件读/写操作,都只提供了对文件顺序存取的手段,即每次都是从文件的始端进行读或写;设置文件读/写位置的操作,通过设置文件读/写指针的位置,以便读/写文件时不再每次都从其始端,而是从所设置的位置开始操作,因此可以改顺序存取为随机存取。
这些基本操作可以组合起来执行其他文件操作。如一个文件的复制是由创建新文件、从旧文件读出并写入新文件中,这些基本操作来完成。
5. 文件的打开与关闭
在很多操作系统中,在对文件进行操作之前,要求用户先使用 open 系统调用 “打开文件”,需要提供的几个主要参数:1.文件存放路径;2.文件名;3.要对文件的操作类型。操作系统在处理 open系统调用 时,主要做了几件事:1. 根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的的目录项,并检查该用户是否有指定的操作权限。2. 将目录项复制到内存中的“打开文件表”中。并将对应表目的编号返回给用户。之后用户使用打开文件表的编号来指明要操作的文件。
打开文件时并不会把文件数据直接读入内存。”索引号“也称”文件描述符“。
用户进程的打开文件表:编号、文件名、读写指针、访问权限、系统表索引号。
读写指针记录了该进程对文件的读写操作进行到的位置。
系统的打开文件表(整个系统只有一张):编号(对应用户的系统表索引号)、文件名、…、外存地址、打开计数器。
打开计数器记录了此时有多少个进程打开了此文件。
当进程关闭文件时,会把自己的”打开文件表“中对应的目录项删掉。系统的打开文件表把打开计数器相应的减一。
4.1.2 文件的逻辑结构
所谓的“逻辑结构”,就是指在用户看来,文件内部的数据应该是如何组织起来的。而“物理结构”指的是在操作系统看来,文件的数据是如何存放在外存中的。
在系统中的所有文件都存在着以下两种形式的文件结构:
- 文件的逻辑结构。这是从用户观点出发所观察到的文件组织形式,即文件是由一系列的逻辑记录组成的,是用户可以直接处理的数据及其结构,它独立于文件的物理特性,又称为文件组织。用户所看到的文件就是逻辑文件。
- 文件的物理结构,又称为文件的存储结构。这是指系统将文件存储在外存上所形成的一种存储组织形式,是用户不能看见的。文件的物理结构不仅与存储介质的存储性能有关,而且与所采用的外存分配方式有关。无论是文件的逻辑结构,还是其物理结构,都会影响对文件的检索速度。
按逻辑结构,文件可划分为无结构文件和有结构文件两种。
1. 无结构文件(流式文件)
文件内部的数据就是一系列二进制流或字符流组成。又称“流式文件”。其文件的长度是以字节为单位的。如:Windows 操作系统中的 .txt 文件。
2. 有结构文件(记录式文件)
由一组相似的记录组成,又称“记录式文件”。每条记录由若干个数据项组成。如:数据库表文件。一般来说,每条记录有一个数据项可作为关键字(作为识别不同记录的ID)。
根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录两种。
有结构文件按记录的组织形式可以分为以下几种:
1)顺序文件
顺序文件:文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储或链式存储。
顺序存储:逻辑上相邻的记录物理上也相邻(类似于顺序表)
链式存储:逻辑上相邻的记录物理上不一定相邻(类似于链表)
在顺序文件中的记录,可以按照各种不同的顺序进行排序。一般地,可分为两种情况:
- 串结构。在串结构文件中的记录,通常是按存入时间的先后进行排序的,各记录之间的顺序与关键字无关。在对串结构文件进行检索时,每次都必须从头开始,逐个记录地查找,直到找到指定的记录或查完所有的记录为止。显然,对串结构文件进行检索是比较费时的。
- 顺序结构。由用户指定一个字段作为关键字,文件中的所有记录按照这个关键字进行排序。
已经知道了文件的起始地址(也就是第一个记录存放的位置),如何进行记录寻址:
- 链式存储:无论是定长/可变长记录,都无法实现随机存取,每次只能从第一个记录开始依次往后查找。
- 顺序存储。
- 可变长记录:无法实现随机存取,每次只能从第一个记录开始依次往后查找。
- 定长记录
- 可实现随机存取,记录长度为 L,则第ⅰ个记录存放的相对位置是 i*L
- 若采用串结构,无法快速找到某关键字对应的记录
- 若采用顺序结构,可以快速找到某关键字对应的记录(如折半查找)
注:一般来说,考试题目中所说的“顺序文件”指的是物理上顺序存储的顺序文件。之后提到的顺序文件也默认如此。 可见,顺序文件的缺点是增加/删除一个记录比较困难(如果是串结构则相对简单,不用按照关键字的顺序来)。为了解决这个问题,可以为顺序文件配置一个运行记录文件或称为事务文件,把试图增加、删除或修改的信息记录与其中,规定每隔一段时间,将运行记录文件与原来的主文件加以合并,产生一个按关键字排序的新文件。
顺序文件的最佳应用场合是在对文件中的记录进行批量存储时(即每次要读或写一大批记录)。所有逻辑文件中顺序文件的存储效率是最高的。但在交互应用的场合,如果用户(程序)要求查找或修改单个记录,系统需要在文件的记录中逐个地查找,此时,顺序文件所表现出来的性能就可能很差。
当对于一个含有N个记录的顺序文件,如果采用顺序查找法,查找到一个指定的记录,平均需要查找N/2次。
2)索引文件
变长记录文件只能顺序查找,系统开销较大。为此,可以建立一张索引表以加快检索速度,索引表本身是定长记录的顺序文件,因此可以快速找到第 i 个记录对应的索引项。索引表是按关键字进行排序的。
可将关键字作为索引号内容,若按关键字顺序排列,则还可以支持按照关键字折半查找。每当要增加/删除一个记录时,需要对索引表进行修改。由于索引文件有很快的检索速度,因此主要用于对信息处理的及时性要求比较高的场合。
对索引文件存取时,必须先查找索引表。索引项只包含每条记录的长度和在逻辑文件中的起始地址。因为每条记录都要有一个索引项,因此提高了存储代价。
另外,可以用不同的数据项建立多个索引表。如:学生信息表中,可用关键字“学号”建立一张索引表。也可用“姓名”建立一张索引表。这样就可以根据“姓名”快速地检索文件了。(Eg:SQL 就支持根据某个数据项建立索引的功能)
优点:它将一个需要顺序查找的文件改造称一个可随机查找的文件,极大地提高了对文件的查找速度。同时,利用索引文件插入和删除记录也非常方便。
缺点:除了有主文件外,还须配置一张索引表,而且每个记录都要有一个索引项,因此增加了存储开销。每个记录对应一个索引表项,因此索引表可能会很大。比如:文件的每个记录平均只占 8B,而每个索引表项占32个字节,那么索引表都要比文件内容本身大4倍,这样对存储空间的利用率就太低了。
3)索引顺序文件
索引顺序文件是索引文件和顺序文件思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是:并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。索引顺序文件的索引项也不需要按关键字顺序排列,这样可以极大地方便新表项的插入。
若一个顺序文件有10000个记录,则根据关键字检索文件,只能从头开始顺序查找(这里指的并不是定长记录、顺序结构的顺序文件),平均须查找 5000 个记录。
若采用索引顺序文件结构,可把 10000 个记录分为 √10000 = 100 组,每组 100 个记录。则需要先顺序查找 索引表找到分组(共100个分组,因此索引表长度为 100,平均需要查 50 次),找到分组后,再在分组中顺序查找记录(每个分组100 个记录,因此平均需要查 50 次)。可见,采用索引顺序文件结构后,平均查找次数减少为 50+50 = 100 次。
同理,若文件共有 106个记录,则可分为 1000 个分组,每个分组 1000 个记录。根据关键字检索一个记录平均需要查找 500+500 = 1000 次。这个查找次数依然很多,如何解决呢?
4)多级索引顺序结构
为了进一步提高检索效率,可以为顺序文件建立多级索引表。例如,对于一个含 106 个记录的文件,可先为该文件建立一张低级索引表,每 100 个记录为一组,故低级索引表中共有 10000 个表项(即10000个定长记录),再把这 10000 个定长记录分组,每组 100 个,为其建立顶级索引表,故顶级索引表中共有 100 个表项。
4.1.3 目录结构
文件目录也是一种数据结构,用于标识系统中的文件及其物理地址,供检索时使用。对目录管理的要求如下:
- 实现按名存取。目录在用户(应用程序)所需要的文件名和文件之间提供一种映射。
- 提高对目录的检索速度。
- 文件共享。
- 允许文件重名。
目录本身就是一种结构文件,由一条条记录组成。每条记录对应一个文件。
1. 文件控制块和索引控制块
文件控制块
文件控制块(FCB)是用来存放控制文件需要的各种信息的数据结构,以实现“按名存取”。FCB的有序集合称为文件目录,一个FCB就是一个文件目录项。为了创建一个新文件,系统将分配一个FCB并存放在文件目录中,成为目录项。
FCB主要包含以下信息:
- 基本信息,如文件名、文件的物理位置、文件的逻辑结构、文件的物理结构等。
- 存储控制信息,如文件存取权限等。
- 使用信息,如文件建立时间、修改时间等。
索引结点(FCB的改进)
文件目录通常是存放在磁盘上的,当文件很多时,文件目录可能要占用大量的盘块。在查找目录的过程中,需要依次将目录文件的每个盘块中的目录调入内存,然后根据用户所给定的文件名与目录项中的文件名逐一比对,直到找到指定文件为止。
而在检索目录文件的过程中,只用到了文件名,仅当找到一个目录项(即其中的文件名与指定要查找的文件名相匹配)时,才需从给目录项中读出该文件的物理地址。而其他一些对该文件进行描述的信息在检索目录时一概不用。显然,这些信息在检索目录时不需调入内存。为此,在有的系统中,如UNIX系统,便采用了把文件名与文件描述信息分开的方法。使文件描述信息单独形成一个称为索引结点的数据结构。
这样目录项就只需要包含文件名、索引结点指针。
当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中的存放位置,根据“存放位置”即可找到文件。
存放在外存中的索引结点称为“磁盘索引结点”,当索引结点放入内存后称为“内存索引结点”。
相比之下内存索引结点中需要增加一些信息,比如:文件是否被修改、此时有几个进程正在访问该文件等。
2. 目录结构
目录可以执行的操作:
- 搜索:当用户要使用一个文件时,系统要根据文件名搜索目录,找到该文件对应的目录项。
- 创建文件:创建一个新文件时,需要在其所属的目录中增加一个目录项。
- 删除文件:当删除一个文件时,需要在目录中删除相应的目录项。
- 显示目录:用户可以请求显示目录的内容,如显示该目录中的所有文件及相应属性。
- 修改目录:某些文件属性保存在目录中,因此这些属性变化时需要修改相应的目录项(如:文件重命名)。
1)单极目录结构
早期操作系统并不支持多级目录,整个系统中只建立一张目录表,每个文件占一个目录项。 单级目录实现了“按名存取”,但是不允许文件重名。
在创建一个文件时,需要先检查目录表中有没有重名文件,确定不重名后才能允许建立文件,并将新文件对应的目录项插入目录表中。而重名问题在多道程序环境下却又是难以避免的,即使是在单用户环境下,当文件数超过数百个是,也难以记忆。所以单级目录结构不适用于多用户操作系统。
2)两级目录结构
早期的多用户操作系统,采用两级目录结构。分为主文件目录(MFD,Master File Directory)和用户文件目录(UFD,User Flie Directory)。
该结构虽然能有效地将多个用户隔开,在各用户之间完全无关时,这种隔离式一个优点。但当多个用户之间要相互合作去完成一个任务,且一用户又需去访问其他用户的文件时,这种隔离便成为一个缺点,因为这种隔离会使诸用户之间不便于共享文件。
3)树形目录结构
用户(或用户进程)要访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。各级目录之间用“/”隔开。从根目录出发的路径称为绝对路径。例如:自拍.jpg 的绝对路径是 “/照片/2015-08/自拍.jpg”
系统根据绝对路径一层一层地找到下一级目录。刚开始从外存读入根目录的目录表;找到“照片”目录的存放位置后,从外存读入对应的目录表;再找到“2015-08”目录的存放位置,再从外存读入对应目录表;最后才找到文件“自拍.jpg”的存放位置。整个过程需要3次读磁盘I/O操作。
很多时候,用户会连续访问同一目录内的多个文件(比如:接连查看“2015-08”目录内的多个照片文件),显然,每次都从根目录开始查找,是很低效的。因此可以设置一个“当前目录”。
例如,此时已经打开了“照片”的目录文件,也就是说,这张目录表已调入内存,那么可以把它设置为“当前目录”。当用户想要访问某个文件时,可以使用从当前目录出发的“相对路径” 。 在 Linux 中,“.”表示当前目录,因此如果“照片”是当前目录,则”自拍.jpg”的相对路径为:“./2015-08/自拍.jpg”。从当前路径出发,只需要查询内存中的“照片”目录表,即可知道”2015-08”目录表的存放位置,从外存调入该目录,即可知道“自拍.jpg”存放的位置了。
可见,引入“当前目录”和“相对路径”后,磁盘I/O的次数减少了。这就提升了访问文件的效率。
把从当前目录开始直到数据文件为止所构成的路径名成为相对路径名。
把从树根开始的路径名称为绝对路径名。
树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。但是,树形结构不便于实现文件的共享。为此,提出了“无环图目录结构”。
4)无环图目录结构
可以用不同的文件名指向同一个文件,甚至可以指向同一个目录(共享同一目录下的所有内容)。
需要为每个共享结点设置一个共享计数器,用于记录此时有多少个地方在共享该结点。用户提出删除结点的请求时,只是删除该用户的FCB、并使共享计数器减1,并不会直接删除共享结点。
只有共享计数器减为 0 时,才删除结点。
注意:共享文件不同于复制文件。在共享文件中,由于各用户指向的是同一个文件,因此只要其中一个用户修改了文件数据,那么所有用户都可以看到文件数据的变化。
4.1.4 文件共享
操作系统为用户提供文件共享功能,可以让多个用户共享地使用同一个文件。
注意:多个用户共享同一个文件,意味着系统中只有“一份”文件数据。并且只要某个用户修改了该文件的数据,其他用户也可以看到文件数据的变化。
如果是多个用户都“复制”了同一个文件,那么系统中会有“好几份”文件数据。其中一个用户修改了自己的那份文件数据,对其他用户的文件数据并没有影响。
1. 基于索引结点的共享方式(硬链接)
索引结点,是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中。这样目录项就只需要包含文件名、索引结点指针。
索引结点中设置一个链接计数变量 count,用于表示链接到本索引结点上的用户目录项数。
- 若 count = 2,说明此时有两个用户目录项链接到该索引结点上,或者说是有两个用户在共享此文件。
若某个用户决定“删除”该文件,则只是要把用户目录中与该文件对应的目录项删除,且索引结点的 count 值减 1。 - 若 count > 0,说明还有别的用户要使用该文件,暂时不能把文件数据删除,否则会导致指针悬空。
- 当 count = 0 时系统负责删除文件。
2. 基于符号链的共享方式(软链接)
在利用符号链方式实现文件共享时,只有文件的拥有者才拥有指向其索引结点的指针。而共享该文件的其他用户只有该文件的路径名,并不拥有指向其索引结点的指针。这样,也就不会发生在文件主删除一个共享文件后留下一个悬空指针的情况。
当文件的拥有者把一个共享文件删除后,其他用户通过符号链去访问它时,会出现访问失败,于是将符号链删除,此时不会产生任何影响。当然,利用符号链实现文件共享仍然存在问题。例如,一个文件采用符号链方式共享,当文件拥有者将其删除,而在共享的其他用户用其符号链接访问该文件之前,又有人在同一路径下创建了另一个具有同样名称的文件,则该符号链将仍然有效,但访问的文件己经改变,从而导致错误。
在符号链的共享方式中,当其他用户读共享文件时,需要根据文件路径名逐个地查找目录,直至找到该文件的索引结点。因此,每次访问时,都可能要多次地读盘,使得访问文件的开销变大并增加了启动磁盘的频率。此外,符号链的索引结点也要耗费一定的磁盘空间。
可以这样说:文件共享,“软”“硬” 兼施。硬链接就是多个指针指向一个索引结点,保证只要还有一个指针指向索引结点,索引结点就不能删除;软链接就是把到达共享文件的路径记录下,当要访问文件时,根据路径寻找文件。可以想象。硬链接的查找速度要比软链接的快。
4.1.5 文件保护
文件保护通过口令保护、加密保护和访问控制等方式实现。其中口令保护和加密保护是为了防止用户文件被他人存取或窃取,而访问控制则用于控制用户对文件的访问方式。
1. 口令保护
优点:保存口令的空间开销不多,验证口令的时间开销也很小。
缺点:正确的“口令”存放在系统内部,不够安全。
2. 加密保护
优点:保密性强,不需要在系统中存储“密码”。
缺点:编码/译码,或者说加密/解密要花费一定时间。
3. 访问控制
在每个文件的FCB(或索引结点)中增加一个访问控制列表(Access-Control List, ACL),该表中记录了各个用户可以对该文件执行哪些操作。
注意:如果对某个目录进行了访问权限的控制,那也要对目录下的所有文件进行相同的访问权限控制。
优点:实现灵活,可以实现复杂的文件保护功能。
4.1.6 补充
- 设置当前目录的主要目的是加快文件的检索速度。
- 文件的读/写速度取决于磁盘的性能。
- 从系统角度看,文件系统负责对文件的存储空间进行组织、分配,负责文件的存储并对存入文件进行保护、检索。从用户角度看,文件系统根据一定的格式将用户的文件存放到文件存储器中适当的位置,当用户需要使用文件时,系统根据用户所给的文件名能够从文件存储器中找到所需要的文件。即从用户角度看,操作系统引入文件系统的目的是实现对文件的按名存取。
- 一个文件对应一个FCB,而一个文件目录项就是一个FCB。文件系统在创建一个文件时,就是在为它建立一个文件目录项。
- 打开文件操作的主要工作是把指定文件的目录复制到内存指定的区域(将该文件的FCB存入内存的活跃文件目录表,而不是将文件内容复制到主存)。找到指定文件目录是打开文件之前的操作。
- 一个文件被用户进程首次打开即被执行了open操作,会把文件的FCB调入内存中,而不会把文件内容读到内存中,只有进程希望获取文件内容时才会读入文件内容。
- 文件的逻辑结果是为了方便用户而设计的。物理结构是取决于存储介质特性和操作系统的管理方式。
- 索引文件由逻辑文件和索引表组成。逻辑索引的目的是加快文件数据的定位,是从用户角度出发的,而物理索引的主要目的是管理不连续的物理块,是从系统管理的角度出发的。
- 删除文件时,与此文件关联的目录项和文件控制块需要随着文件一同删除,同时释放文件关联的内存缓冲区。
- 目录文件是FCB的集合,一个目录中既可能有子目录,又可能有数据文件,因此目录文件中存放的是子目录和数据文件的信息。
- 文件系统采用多级目录结构的目的是解决命名冲突。采用多级目录结构后,符合了多层次管理的需要,提高了文件查找的速度,还允许用户建立同名文件。
- 对一个文件的访问,常由用户访问权限和文件属性共同限制。
- 加密保护和访问控制两种机制相比,访问控制机制必须由系统实现。访问控制机制的安全性较差,但灵活性相对较高。
- 为了对文件系统中的文件进行安全管理,任何一个用户在进入系统时必须进行注册,这一级安全管理是系统级。系统级安全管理包括注册和登录。
- 不同的文件系统存放的方法是不一样的。一个文件存放在磁带中时,通常采用连续存放方法,文件在硬盘上一般不采用连续存放方法。
- 防止系统故障造成系统内故障可以采用备份的方法保护文件,而存取控制矩阵的方法用于多用户之间的存取权限保护。
- 可以提高文件访问速度的方法有:为文件分配连续的簇、采用磁盘高速缓存、提前读和延迟写。提前读是指在读当前盘块时,将下一个可能要访问的盘块数据读入缓冲区,以便需要时直接从缓冲区中读取,提高了文件的访问速度。延迟写是指先将数据写入缓冲区,并置上“延迟写”标志,以备不久之后访问,当缓冲区需要再次被分配出去时,才将缓冲区数据写入磁盘,减少了访问磁盘的次数,提高了文件的访问速度。
- 当多个进程共享一个文件时,各进程的用户打开文件表中关于该文件的表项内容是不同的,比如读写指针和访问权限等。
4.2 文件系统实现
4.2.1 文件实现——文件分配方式
对磁盘非空闲块的管理就是文件的分配方式;对磁盘空闲块的管理就是文件存储空间管理。
文件分配方式(文件的物理结构)有三种方式:连续分配、链式分配(隐式链接、显示链接)、索引分配。
1. 文件块、磁盘块
类似于内存分页,磁盘中的存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中,磁盘块的大小与内存块、页面的大小相同。内存与磁盘之间的数据交换(即读/写操作、磁盘/O)都是以“块”为单位进行的。即每次读入一块,或每次写出一块。
在内存管理中,进程的逻辑地址空间被分为一个一个页面。同样的,在外存管理中,为了方便对文件数据的管理,文件的逻辑地址空间也被分为了一个一个的文件“块”。
于是文件的逻辑地址也可以表示为(逻辑块号,块内地址)的形式。
操作系统为文件分配存储空间都是以块为单位的。用户通过逻辑地址来操作自己的文件,操作系统要负责实现从逻辑地址到物理地址的映射。
2. 连续分配
连续分配方法要求每个文件在磁盘上占有一组连续的块。通常,它们都位于一条磁道上,在进行读/写操作时,不必移动磁头。在采用连续组织方式时,可把逻辑文件中的记录顺序地存储到邻接的各物理盘块中,这样所形成的文件结构称为顺序文件结构,此时的物理文件称为顺序文件。
物理块号 = 起始块号 + 逻辑块号。
优点:支持顺序访问和直接访问(即随机访问);连续分配的文件在顺序访问时速度最快,由连续分配所装入的文件,其所占用的盘块可能是位于一条或几条相邻的磁道上,磁头的移动距离最少,因此,这种对文件访问的速度是几种存储空间分配方式中最快的一种。
缺点:不方便文件拓展,一旦需要增加,就需要大量移动盘块;存储空间利用率低,会产生磁盘碎片,可以用紧凑来处理碎片,但需要耗费很大的时间代价;且很难确定一个文件需要的空间大小,因而只适用于长度固定的文件。
3. 链接分配
链接分配采取离散分配的方式,消除了外部碎片,因此提高了磁盘空间的利用率。分为隐式链接和显式链接两种。
1)隐式链接
隐式链接——除文件的最后一个盘块之外,每个盘块中都存有指向下一个盘块的指针。文件目录包括文件第一块的指针和最后一块的指针。
优点:很方便文件拓展,不会有碎片问题,外存利用率高。
缺点:只支持顺序访问,不支持随机访问,查找效率低,指向下一个盘块的指针也需要耗费少量的存储空间。
为了提高检索速度和减小指针所占用的存储空间,可以将几个盘块组成一个簇。比如,一个簇可包含4个盘块,在进行盘块分配时,是以簇为单位进行的。在链接文件中的每个元素也是以簇为单位。这样将会成倍地减少查找指定块的时间,而且也可减少指针所占用的存储空间,但却增大了内部碎片,而且这种改进也是非常有限的。
注:考试题目中遇到未指明隐式/显式的“链接分配”,默认指的是隐式链接的链接分配。
2)显式链接
指把用于链接文件各物理块的指针,从每个物理块的块末尾中提取出来,显式地存放在内存的一种链接表中。该表在整个磁盘中仅设置一张,称为**文件分配表(FAT,File Allocation Table)**。开机时文件分配表放入内存,并常驻内存。
一个磁盘仅设置一张FAT。 开机时,将FAT读入内存,并常驻内存。
优点:很方便文件拓展,不会有碎片问题,外存利用率高,并且支持随机访问。相比于隐式链接来说,地址转换时不需要访问磁盘,因此文件的访问效率更高。
缺点:文件分配表的需要占用一定的存储空间。
4. 索引分配
事实上,在打开某个文件时,只需把该文件占用的盘块的编号调入内存即可,完全没有必要将整个 FAT 调入内存。为此,应将每个文件所对应的盘块号集中地放在一起,在访问到某个文件时,将该文件所对应的盘块号一起调入内存。索引分配方法就是基于这种想法所形成的一种分配方法。
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表——建立逻辑页面到物理页之间的映射关系)。索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块。
若每个磁盘块1KB,一个索引表项4B,则一个磁盘块只能存放 256 个索引项。如果一个文件的大小超过了256块,那么一个磁盘块是装不下文件的整张索引表的,如何解决这个问题?
可以采用以下机制了解决这个问题。
1)链接方案
如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放。
缺点:若文件很大,索引表很长,就需要将很多个索引块链接起来。想要找到 i 号索引块,必须先依次读入 0~i-1 号索引块,这就导致磁盘I/O次数过多,查找效率低下。
2)多层索引
多层索引:建立多层索引(原理类似于多级页表)。使第一层索引块指向第二层的索引块。还可根据文件大小的要求再建立第三层、第四层索引块。
若采用多层索引,则各层索引表大小不能超过一个磁盘块。
假设磁盘块大小为1KB,一个索引表项占4B,则一个磁盘块只能存放 256 个索引项。
若某文件采用两层索引,则该文件的最大长度可以到
256 * 256 * 1KB = 65,536 KB = 64MB
可根据逻辑块号算出应该查找索引表中的哪个表项。
如:要访问 1026 号逻辑块,则
1026/256 = 4,1026%256 = 2
因此可以先将一级索引表调入内存(第一次),查询 4 号表项,将其对应的二级索引表调入内存(第二次),再查询二级索引表的2号表项即可知道 1026 号逻辑块存放的磁盘块号了。访问目标数据块,需要3次磁盘I/O。(第三次是将对应的数据块调入内存)
若采用三层索引,则文件的最大长度为 256 * 256 * 256 * 1KB = 16GB
类似的,访问目标数据块,需要四次磁盘I/O。
采用 K 层索引结构,且顶级索引表未调入内存,则访问一个数据块只需要 K + 1 次读磁盘操作。
多级索引的主要优点是:大大加快了对大型文件的查找速度。其主要缺点是在访问一个盘块时,其所需启动磁盘的次数随着索引级数的增加而增多,即使是对于小文件,也是如此。实际情况是,通常总是以中、小文件居多,而大文件是较少的。因此可见,如果在文件系统中仅采用了多级索引组织方式,并不能获得理想的效果。
3)混合索引(常考)
混合索引:多种索引分配方式的结合。例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)。
优点:对于小文件来说,访问一个数据块所需的读磁盘次数更少。
超级超级超级重要考点:1. 要会根据多层索引、混合索引的结构计算出文件的最大长度(Key:各级索引表最大不能超过一个块);2. 要能自己分析访问某个数据块所需要的读磁盘次数(Key:FCB中会存有指向顶级索引块的指针,因此可以根据FCB读入顶级索引块。每次读入下一级的索引块都需要一次读磁盘操作。另外,要注意题目条件——顶级索引块是否已调入内存)
5. 逻辑结构 Vs 物理结构
1)C语言创建无结构文件
FILE *fp = fopen("test.txt", "w"); // 打开文件
if( fp == NULL ){
printf("打开文件失败!");
exit(0);
}
// 写入 1w 个Hello world
for (int i = 0; i < 10000; i++)
fputs("Hello world!", fp);
fclose(fp);
在用户视角看,整个文件占用一片连续的逻辑地址空间。用户用逻辑地址访问文件。从操作系统视角看,需要把这些字符按照磁盘块带下去进行拆分,操作系统将(逻辑块号,块内偏移量)转换为(物理块号,块内偏移量)。
连续分配:逻辑上相邻的块物理上也相邻。
链接分配:逻辑上相邻的块在物理上用链接指针表示先后关系。
索引分配:操作系统为每个文件维护一张索引表,其中记录了 逻辑块号 -> 物理块号 的映射关系。
2)C语言创建顺序文件
文件内部各条记录的逻辑结构(链式存储、顺序存储):由创建文件的用户自己设计的。
文件整体用什么物理结构(连续分配、链接分配或索引分配):由操作系统决定。
3)索引文件采用索引分配
索引文件:从用户视角(逻辑结构上)来看,整个文件依然是连续存放的。如:前 1MB 存放索引项,后续部分存放记录。
逻辑结构:操作系统把这些数据按照磁盘块大小进行拆分,然后根据操作系统自己建立的索引分配,把数据块放到各个磁盘块中。实现了逻辑结构到物理结构的转换。
索引文件的索引表:用户自己建立的,映射:关键字 -> 记录存放的逻辑地址 (这种映射是用户自己建立的)
索引分配的索引表:操作系统建立的,映射:逻辑块号 -> 物理块号。
总结:逻辑结构就是用户的视角看到的样子,整个文件占用连续的逻辑地址空间,文件内部的信息组织完全由用户自己决定,操作系统并不关心。物理结构是由操作系统决定文件采用什么物理结构存储,操作系统负责将逻辑地址转变为(逻辑块号,块内偏移量)的形式,并负责实现逻辑块号到物理块号的映射。
4.2.2 文件实现——文件存储空间管理
1. 存储空间的划分与初始化
存储空间的划分:将物理磁盘划分为一个个文件卷(逻辑卷、逻辑盘)。有的系统支持超大型文件,可支持由多个物理磁盘组成一个文件卷。
存储空间的初始化:将各个文件卷划分为目录区和文件区。在一个文件卷中,文件数据信息的空间(文件区)和存放文件控制信息 FCB 的空间(目录区)是分离的。目录区只要存放文件目录信息(FCB)、用于磁盘存储空间管理的信息;文件区用于存放文件数据。
2. 文件存储器空间管理
文件存储设备分成许多大小相同的物理块,并以块为单位交换信息,因此,文件存储设备的管理实质上是对空闲块的组织和管理,它包括空闲块的组织、分配与回收等问题。
1)空闲表法
空闲表法属于连续分配方式。系统为外存上的所有空闲区建立一张空闲盘块表。
如何分配磁盘块:与内存管理中的动态分区分配很类似,为一个文件分配连续的存储空间。同样可采用首次适应、最佳适应、最坏适应等算法来决定要为文件分配哪个区间。
如何回收磁盘块:与内存管理中的动态分区分配很类似,当回收某个存储区时需要有四种情况——①回收区的前后都没有相邻空闲区;②回收区的前后都是空闲区;③回收区前面是空闲区;④回收区后面是空闲区。总之,回收时需要注意表项的合并问题。
2)空闲链表法
- 空闲盘块链
操作系统保存着链头、链尾指针。 空闲盘块中存储着下一个空闲盘块的指针。
如何分配:若某文件申请 K 个盘块,则从链头开始依次摘下 K 个盘块分配,并修改空闲链的链头指针。
如何回收:回收的盘块依次挂到链尾,并修改空闲链的链尾指针。
这种方法的优点是分配和回收一个盘块的过程非常简单,适用于离散分配的物理结构。但在为一个大小超过一个盘块的文件分配盘块时,需要重复多次操作。 - 空闲盘区链
操作系统保存着链头、链尾指针。连续的空闲盘块组成一个空闲盘区,空闲盘区中的第一个盘块内记录了盘区的长度、下一个盘区的指针。
如何分配:若某文件申请 K 个盘块,则可以采用首次适应、最佳适应等算法,从链头开始检索,按照算法规则找到一个大小符合要求的空闲盘区,分配给文件。若没有合适的连续空闲块,也可以将不同盘区的盘块同时分配给一个文件,注意分配后可能要修改相应的链指针、盘区大小等数据。
如何回收:若回收区和某个空闲盘区相邻,则需要将回收区合并到空闲盘区中。若回收区没有和任何空闲区相邻,将回收区作为单独的一个空闲盘区挂到链尾。
离散分配、连续分配都适用。为一个文件分配多个盘块时效率更高。
3. 位示图法
每个二进制位对应一个盘块。
如何分配:若文件需要K个块,1. 顺序扫描位示图,找到K个相邻或不相邻的“0”;2. 根据字号、位号算出对应的盘块号,将相应盘块分配给文件;3. 将相应位设置为“1”。
如何回收:1. 根据回收的盘块号计算出对应的字号、位号;2. 将相应二进制位设为“0”
这种方法的主要优点是从位示图中很容易找到一个或一组相邻接的空闲盘块。由于位示图很小,占用空间少,因而可将它保存在内存中,进而使在每次进行盘区分配时,无需首先将盘区分配表读入内存中,从而节省了许多磁盘的启动操作。连续分配、离散分配都适用。
4. 成组链接法
空闲表法、空闲链表法不适用于大型文件系统,因为空闲表或空闲链表可能过大。UNIX系统中采用了成组链接法对磁盘空闲块进行管理。
文件卷的目录区中专门用一个磁盘块作为“超级块”,当系统启动时需要将超级块读入内存。并且要保证内存与外存中的“超级块”数据一致。
其大致思想是:把顺序的n个空闲扇区地址保存在第一个空闲扇区内,其后一个空闲扇区内则保存另一顺序空闲扇区的地址,如此继续,直到所有空闲扇区均予以链接。
4.2.3 文件系统层次结构
用一个例子来辅助记忆文件系统的层次结构:
假设某用户请求删除文件 “D:/工作目录/学生信息.xlsx” 的最后100条记录。
- 用户需要通过操作系统提供的接口发出上述请求——用户接口。
- 由于用户提供的是文件的存放路径,因此需要操作系统一层一层地查找目录,找到对应的目录项——文件目录系统。
- 不同的用户对文件有不同的操作权限,因此为了保证安全,需要检查用户是否有访问权限——存取控制模块(存取控制验证层)。
- 验证了用户的访问权限之后,需要把用户提供的“记录号”转变为对应的逻辑地址——逻辑文件系统与文件信息缓冲区。
- 知道了目标记录对应的逻辑地址后,还需要转换成实际的物理地址——物理文件系统。
- 要删除这条记录,必定要对磁盘设备发出请求——设备管理程序模块。
- 删除这些记录后,会有一些盘块空闲,因此要将这些空闲盘块回收——辅助分配模块。
4.2.4 补充
-
文件的物理结构包括连续、链式、索引三种,其中链式结构不能实现随机访问,连续结构不易于扩展。因此随机访问且易于扩展是索引结构的特性。
-
为支持CD-ROM中视频文件的快速随机播放,播放性能最好的文件数据块组织(要确保最短的查询时间)是连续结构。
-
在磁盘上,最容易导致存储碎片发生的物理文件结构是顺序存放(连续分配)。顺序文件需要占用连续的磁盘空间,所以会产生难以分配的碎片。
-
关于目录检索,通常不采用散列法,散列法是通过类似于哈希函数通过关键字进行检索,而哈希函数需要冗余的空间来避免冲突,所以会导致文件目录很大。一般是采用顺序检索法,只要路径名的一个分量未找到,就应停止查找。
-
通常用户可以根据需要来确定文件的逻辑结构,而文件的物理结构是由操作系统的设计者根据文件存储器发特性来确定,一旦确定,就操作系统管理。
-
文件系统为每个文件创建一张索引表,存放文件数据块的磁盘存放位置。打开文件表仅存放已打开文件信息的表,将指明文件的属性从外存复制到内存,在使用该文件时直接返回索引。而位图和空闲盘块链表是磁盘管理方法。
-
随机存取是索引文件速度快,顺序存取时顺序存取文件速度快。
-
顺序文件进行检索时,首先从FCB中读出文件的第一个盘块号;而对索引文件进行检索时,应先从FCB中读出文件索引块的开始地址。
-
索引文件支持变长的文件,同时可以随机访问文件的指定数据块;链接分配不支持随机访问,需要依靠指针依次访问;连续分配的文件长度固定,不支持可变文件长度(连续分配的文件长度虽然也可变,但是需要大量移动数据,代价较大,相比之下不太合适)。
-
①连续分配方式的优点是可以随机访问(磁盘),访问速度快;缺点是要求有连续的存储空间,容易产生碎片,降低磁盘空间的利用率,并且不利于文件的增长扩充。
②链接分配方式的优点是不要求连续的存储空间,能更有效地利用磁盘空间,并且有利于扩充文件;缺点是只适合顺序访问,不适合随机访问;另外,链接指针占用一定的空间,降低了存储效率,可靠性也差。
③索引分配方式的优点是既支持顺序访问又支持随机访问,查找效率高,便于文件删除;缺点是索引表会占用一定的存储空间。
4.3 磁盘组织与管理
4.3.1 磁盘的结构
1. 磁盘、磁道、扇区
磁盘的表面由一些磁性物质组成,可以用这些磁性物质来记录二进制数据。磁盘的盘面被划分成一个个磁道。一个“圈”就是一个磁道。一个磁道又被划分成一个个扇区,每个扇区就是一个“磁盘块”。各个扇区存放的数据量相同(如 1KB)。最内侧磁道上的扇区面积最小,因此数据密度最大。相邻磁道及相邻扇区间通过一定的间隙分隔开,以避免精度错误。
2. 如何在磁盘中读/写数据
需要把“磁头”移动到想要读/写的扇区所在的磁道。磁盘会转起来,让目标扇区从磁头下面划过,才能完成对扇区的读/写操作。在读/写操作期间,磁头固定,磁盘在下面高速旋转。
3. 盘面、柱面
一个盘片可能会有两个盘面。每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有的磁头只能“共进退”。所有盘面中想对位置相同的磁道组成柱面。
4. 磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。在“文件的物理结构”小节中,我们经常提到文件数据存放在外存中的几号块,这个块号就可以转换成(柱面号,盘面号,扇区号)的地址形式。
可根据该地址读取一个“块”
- 根据“柱面号”移动磁臂,让磁头指向指定柱面;
- 激活指定盘面对应的磁头;
- 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇 区的读/写。
5. 磁盘的分类
- 活动头磁盘:磁头可以移动。磁臂可以来回伸缩来带动磁头定位磁道。
- 固定头磁盘:磁头不可以移动。这种磁盘中每个磁道有一个磁头。
- 可换盘磁盘:盘片可以更换。
- 固定盘磁盘:盘片不可更换。
4.3.2 磁盘调度算法
用户访问文件,需要操作系统的服务,文件实际上存储在磁盘中,操作系统接收用户的命令后,经过一系列的检验访问权限和寻址过程后,最终都会到达磁盘,控制磁盘把相应的数据信息读出或修改。当有多个请求同时到达时,操作系统就要决定先为哪个请求服务,这就是磁盘调度算法要解决的问题。
1. 一次磁盘读/写操作需要的时间
1)寻道时间
寻找时间/寻道时间 T s T_s Ts :在读/写数据前,将磁头移动到指定磁道所花的时间。
-
启动磁头臂是需要时间的。假设耗时为 s;
-
移动磁头也是需要时间的。假设磁头匀速移动,每跨越一个磁道耗时为 m,总共需要跨越 n 条磁道。则:
寻道时间 T s = s + m × n T_s = s + m \times n Ts=s+m×n
操作系统的磁盘调度算法会直接影响寻道时间。而磁盘调度算法影响的指标是移动磁头所需时间。
2)延迟时间
延迟时间 T R T_R TR :通过旋转磁盘,使磁头定位到目标扇区所需要的时间。设磁盘转速为 r(单位:转/秒,或 转/分),则平均所需的延迟时间 T R = 1 / 2 r T_R = 1/2r TR=1/2r 。 1/r 就是转一圈需要的时间。找到目标扇区平均需要转半圈,因此再乘以 1/2 。磁盘的典型转速为 5400转/分,或 7200转/分。
3)传输时间
传输时间 T t T_t Tt :从磁盘读出或向磁盘写入数据所经历的时间,假设磁盘转速为 r,此次读/写的字节数为 b,每个磁道上的字节数为 N。则传输时间 T t = ( 1 / r ) × ( b / N ) = b / ( r N ) T_t = (1/r) \times (b/N)=b/(rN) Tt=(1/r)×(b/N)=b/(rN)
延迟时间和传输时间都与磁盘转速相关,且为线性相关。而转速是硬件的故有属性,因此操作系统也无法优化延迟时间和传输时间。总的平均存取时间 T a = T s + 1 / 2 r + b / ( r N ) T_a = T_s + 1/2r + b/(rN) Ta=Ts+1/2r+b/(rN)。 虽然这里给出了总平均存取时间的公式,但是这个平均值是没有太大实际意义的,因为在实际的磁盘I/O操作中,存取时间与磁盘调度算法密切相关。调度算法直接决定寻道时间,从而决定总的存取时间。
三个时间中,一般来说,寻道时间因为要移动磁臂,所以占用时间最长。
目前常用的磁盘调度算法有以下几种。
2. 先来先服务算法(FCFS)
根据进程请求访问磁盘的先后顺序进行调度。
优点:公平;如果请求访问的磁道比较集中的话,算法性能还算过的去。
缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。
3. 最短寻找时间优先(SSTF)
SSTF 算法会优先处理的磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短,但是并不能保证总的寻道时间最短。(其实就是贪心算法的思想,只是选择眼前最优,但是总体未必最优)
优点:性能较好,平均寻道时间短。
缺点:可能产生“饥饿”现象。
4. 扫描算法(SCAN)
SSTF 算法会产生饥饿的原因在于:磁头有可能在一个小区域内来回来去地移动。为了防止这个问题,可以规定,只有磁头移动到最外侧磁道的时候才能往内移动,移动到最内侧磁道的时候才能往外移动。这就是扫描算法(SCAN)的思想。由于磁头移动的方式很像电梯,因此也叫电梯算法。
优点:性能较好,平均寻道时间较短,不会产生饥饿现象。
缺点:①只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。
②SCAN算法对于各个位置磁道的响应频率不平均(如:假设此时磁头正在往右移动,且刚处理过90号磁道,那么下次处理90号磁道的请求就需要等磁头移动很长一段距离;而响应了184号磁道的请求之后,很快又可以再次响应 184 号磁道的请求了)
5. LOOK 调度算法
扫描算法(SCAN)中,只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。LOOK 调度算法就是为了解决这个问题,如果在磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向。(边移动边观察,因此叫 LOOK)
优点:比起 SCAN 算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短。
6. 循环扫描算法(C-SCAN)
SCAN算法对于各个位置磁道的响应频率不平均,而 C-SCAN 算法就是为了解决这个问题。规定只有磁头朝某个特定方向移动时才处理磁道访问请求,而返回时直接快速移动至起始端而不处理任何请求。
优点:比起SCAN 来,对于各个位置磁道的响应频率很平均。
缺点:只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了;并且,磁头返回时其实只需要返回到18号磁道即可,不需要返 回到最边缘的磁道。另外,比起SCAN算法来,平均寻道时间更长。
7. C-LOOK 调度算法
C-SCAN 算法的主要缺点是只有到达最边上的磁道时才能改变磁头移动方向,并且磁头返回时不一定需要返回到最边缘的磁道上。C-LOOK 算法就是为了解决这个问题。如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
优点:比起 C-SCAN 算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短。
若题目中无特别说明, 则SCAN 就是 LOOK, C-SCAN 就是C-LOOK。
4.3.3 减少延迟时间的方法
一次磁盘读/写操作需要的时间:1. 寻道时间:启动磁臂、移动磁头所花的时间;2. 延迟时间:将目标扇区转到磁头下面所花的时间;3. 传输时间:读/写 数据花费的时间。
结论:磁头读入一个扇区数据后需要一小段时间处理,如果逻辑上相邻的扇区在物理上也相邻,则读入几个连续的逻辑扇区,可能需要很长的“延迟时间”。
为了减少延迟时间,有以下方法:
1. 交替编号
若采用交替编号的策略,即让逻辑上相邻的扇区在物理上有一定的间隔,可以使读取连续的逻辑扇区所需要的延迟时间更小。
2. 扩展:磁盘地址结构的设计
磁盘的物理地址是(柱面号,盘面号,扇区号)。
假设某磁盘有 8 个柱面/磁道(假设最内侧柱面/磁道号为 0),4个盘面,8个扇区。则可用 3 个二进制位表示柱面,2 个二进制位表示盘面,3 个二进制位表示扇区。
若物理地址结构是(盘面号,柱面号,扇区号),且需要连续读取物理地址(00, 000, 000)~(00, 001, 111)的扇区:(00, 000, 000)~(00, 000, 111)转两圈可读完(一个磁道需要转两圈才能读完,因为读取连续的盘块需要延迟时间)。之后再读取物理地址相邻的区域,即(00, 001, 000)~(00, 001, 111),需要启动磁头臂,将磁头移动到下一个磁道。
注:这种方法需要移动磁头臂。
若物理地址结构是(柱面号,盘面号,扇区号),且需要连续读取物理地址(000, 00 000)~(001, 00, 111)的扇区:(000, 00, 000)~(000, 00, 111)由盘面 0 的磁头读入数据之后再读取物理地址相邻的区域,即(000, 01, 000)~(000, 01, 111)由于柱面号/磁盘号相同,只是盘面号不同,因此不需要移动磁头臂。只需要激活相邻盘面的磁头即可。
读取地址连续的磁盘块时,采用(柱面号,盘面号,扇区号)的地址结构可以减少磁头移动消耗的时间。
存储一个文件时,当一个磁道存储不下时,剩下部分时存在同一个盘面的不同磁道好,还是存在同一个柱面上的不同盘面好?
寻道时间对于一次磁盘访问的影响是最大的,若存在同一个盘面的不同磁道,则磁臂势必要移动,这样会大大增加文件的访问时间,而存在同一个柱面上的不同盘面就不需要移动磁道,所以一般情况下存在同一个柱面上的不同盘面更好。
3. 错位命名
在不采用错位命名法的情况下:
若相邻的盘面相对位置相同处扇区编号相同。所有盘面都是一起连轴转的。读取完磁盘块(000, 00, 111)之后,需要短暂的时间处理,而盘面又再不停地转动,因此当(000, 01, 000)第一次划过 1 号盘面的磁头下方时,并不能读取数据,只能再等该扇区再次划过磁头。
在采用错位命名法的情况下:
由于采用错位命名法,因此读取完磁盘块(000, 00, 111)之后,还有一段时间处理,当(000, 01, 000)第一次划过 1 号盘面的磁头下方时,就可以直接读取数据。从而减少了延迟时间。
4.3.4 磁盘的管理
1. 磁盘初始化
- 进行低级格式化(物理格式化),将磁盘的各个磁道划分为扇区。一个扇区通常可分为 头、数据区域(如 512B 大小)、尾 三个部分组成。管理扇区所需要的各个数据结构一般存放在头、尾两个部分,包括扇区校验码(如奇偶校验、CRC 循环冗余校验码等,校验码用于校验扇区中的数据是否发生错误)
- 将磁盘分区,每个分区由若干柱面组成(即分为我们熟悉的 C盘、D盘、E盘)
- 进行逻辑格式化,创建文件系统。包括创建文件系统的根目录、初始化存储空间管理所用的数据结构(如 位示图、空闲分区表)
2. 引导块
计算机启动时需要运行一个初始化程序(自举程序),它初始化CPU、寄存器、设备控制器和内存等,接着启动操作系统。
当初始化程序放在 ROM(只读存储器,出厂时写入,并且以后不能再修改)中时,会存在没办法更新初始化程序的问题。 完整的自举程序存放在磁盘的启动块(即引导块/启动分区)上,启动块位于磁盘的固定位置。ROM 中只存放很小的“自举装入程序”,开机时计算机先运行“自举装入程序”,通过执行该程序就可找到引导块,并将完整的“自举程序”读入内存,完成初始化。拥有启动分区的磁盘称为 启动磁盘 或 系统磁盘(C:盘)。
3. 坏块
坏了、无法正常使用的扇区就是“坏块”。这属于硬件故障,操作系统是无法修复的。应该将坏块标记出来,以免错误地使用到它。对于简单的磁盘,可以在逻辑格式化时(建立文件系统时)对整个磁盘进行坏块检查,标明哪些扇区是坏扇区,比如:在 FAT 表上标明。(在这种方式中,坏块对操作系统不透明)
对于复杂的磁盘,磁盘控制器(磁盘设备内部的一个硬件部件)会维护一个坏块链表。在磁盘出厂前进行低级格式化(物理格式化)时就将坏块链进行初始化。会保留一些“备用扇区”,用于替换坏块。这种方案称为扇区备用。且这种处理方式中,坏块对操作系统透明。
对坏块的处理实质上就是用某种机制,使系统不去使用坏块。
4.3.4 补充
- 磁盘调度的目的是缩短寻道时间。
- 磁盘上的文件以块为单位读/写。因为文件以块为单位存放于磁盘中,文件的读写也以块为单位。
- 磁盘调度中,对读/写时间影响最大的是寻道时间,寻道过程为机械运动,时间较长,影响较大。
- 扇区数据的处理时间主要影响传输时间。延迟时间的大小与磁盘调度算法无关;与文件的物理结构有关,而且取决于磁盘空闲空间的分配程序。
- 先来先服务算法根据磁盘请求的时间先后进行调度,因而可能随时改变词条方向。而电梯调度、循环扫面算法均限制磁头的方向。
五、设备管理
I/O系统管理的主要对象是I/O设备和相应的设备控制器。其主要的任务是,完成用户提出的I/O请求,提高I/O速率,以及提高设备的利用率,并能为更高层的进程方便地使用这些设备提高手段。
5.1 I/O 管理概述
5.1.1 I/O 设备
1. 什么是 I/O 设备
- “I/O” 就是 “输入/输出”(Input/Output)。
- I/O 设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件。
- UNIX 系统将外部设备抽象为一种特殊的文件,用户可以使用与文件操作相同的方式对外部设备进行操作。
- Write 操作:向外部设备写入数据
- Read 操作:从外部设备读入数据
2. I/O 设备的分类——按使用特性
- 人机交互类外部设备:鼠标、键盘、打印机等,用于人机交互。数据传输速慢。
- 存储设备:移动硬盘、光盘等,用于数据存储。数据传输速度快。
- 网络通信设备:调制解调器等,用于网络通信。传输速度介于上诉两者之间。
3. I/O 设备的分类——按传输速率分类
- 低速设备:鼠标、键盘等,传输速率为每秒几个到几百字节
- 中速设备:如激光打印机等,传输速率为每秒数千至上万个字节
- 高速设备:如磁盘等,传输速率为每秒数千字节至千兆字节的设备
字节多路通道用做连接低速或中速的I/O设备。
4. I/O 设备的分类——按信息交换的单位分类
- 块设备:如磁盘等,数据传输的基本单位是“块”。传输速率较高,可寻址,即对它可随即地读/写任一块。
- 字符设备:鼠标、键盘等,数据传输的基本单位是字符。传输速率较慢,不可寻址,在输入/输出时常采用中断驱动方式。
5.1.2 I/O 控制器
I/O 设备由机械部件和电子部件组成。(电子部件:I/O 控制器、设备控制器)
1. I/O 设备的机械部件
I/O 设备的机械部件主要用来执行具体I/O操作。 如我们看得见摸得着的鼠标/键盘的按钮;显示器的LED屏;移动硬盘的磁臂、磁盘盘面。
2. I/O 设备的电子部件(I/O控制器)
I/O 设备的电子部件通常是一块插入主板扩充槽的印刷电路板。
CPU 无法直接控制 I/O 设备的机械部件,因此 I/O 设备还要有一个电子部件作为 CPU 和 I/O 设备机械部件之间的“中介”,用于实现 CPU 对设备的控制。这个电子部件就是 I/O控制器 ,又称设备控制器。CPU 可控制 I/O 控制器,又由 I/O 控制器来控制设备的机械部件。
I/O 控制器的功能:
- 接受和识别 CPU 发出的命令。如 CPU 发来的 read/write 命令,I/O 控制器中会有相应的控制寄存器来存放命令和参数。
- 向 CPU 报告设备的状态。I/O 控制器中会有相应的状态寄存器,用于记录 I/O 设备的当前状态。如:1 表示空闲,0 表示忙碌。
- 数据交换。I/O 控制器中会设置相应的数据寄存器。输出时,数据寄存器用于暂存 CPU 发来的数据,之后再由控制器传送设备。输入时,数据寄存器用于暂存设备发来的数据,之后 CPU 从数据寄存器中取走数据。
- 地址识别。类似于内存的地址,为了区分设备控制器中的各个寄存器,也需要给各个寄存器设置一个特定的“地址”。I/O 控制器通过 CPU 提供的“地址”来判断 CPU 要读/写的是哪个寄存器。
3. I/O 控制器的组成
由于 I/O 控制器位于 CPU 和设备之间,它既要与 CPU 通信,又要与设备通信,还应具有按照 CPU 所发来的命令去控制设备工作的功能,因此,现有的大多数控制器都是由设备控制器与处理机的接口、设备控制器与设备的接口和 I/O 逻辑三部分组成。
由于 I/O 设备的速率较低,而 CPU 和内存的速率却很高,故在控制器中必须设置一个数据缓冲区。在输出时,用此缓冲区暂存在主机高速传来的数据,然后才以与 I/O 设备所匹配的速率将缓冲器中的数据传送给 I/O 设备。在输入时,缓冲区则用于暂存从 I/O 设备送来的数据,待接收到一批数据后,再将缓冲区中的数据高速地传送给主机。
值得注意的小细节:
- 一个 I/O 控制器可能会对应多个设备;
- 数据寄存器、控制寄存器、状态寄存器可能有多个(如:每个控制/状态寄存器对应一个具体的设备),且这些寄存器都要有相应的地址,才能方便 CPU 操作。有的计算机会让这些寄存器占用内存地址的一部分,称为内存映像 I/O ;另一些计算机则采用 I/O 专用地址,即寄存器独立编址。
4. 内存映像 I/O v.s. 寄存器独立编址
5.1.3 I/O 控制方式
在 I/O 控制方式的整个发展过程中,始终贯穿着这样一条宗旨,即尽量减少主机对 I/O 控制的干预,把主机从繁杂的 I/O 控制事务中解脱出来,以便更多地去完成数据处理任务。
设备管理的主要任务之一是控制设备和内存或处理机之间的数据传送。外围设备和内存之间的输入/输出控制方式有4种。
需要注意的问题:1. 完成一次读/写操作的流程;2. CPU 干预的频率; 3. 数据传送的单位;4. 数据的流向;5. 主要缺点和主要优点。
1. 程序直接控制方式
读操作是通过CPU发出读指令,然后从I/O设备把数据读入到内存中就绪,依次执行:
①CPU向控制器发出读指令。于是设备启动,并且状态寄存器设为1(未就绪)
②轮询检查控制器的状态(其实就是在不断地执行程序的循环,若状态位一直是1, 说明设备还没准备好要输入的数据,于是 CPU 会不断地轮询)
③输入设备准备好数据后将数据传给控制器,并报告自身状态
④控制器将输入的数据放到数据寄存器中,并将状态改为 0(已就绪)
⑤CPU发现设备已就绪,即可将数据寄存器中的内容读入CPU的寄存器中,再把CPU寄存器中的内容放入内存
⑥若还要继续读入数据,则 CPU 继续发出读指令
- CPU 干预的频率
很频繁,I/O 操作开始之前、完成之后需要 CPU 介入,并且在等待 I/O 完成的过程中 CPU 需要不断地轮询检查。 - 数据传送的单位
每次读/写一个字。 - 数据的流向
读操作(数据输入):I/O 设备 -> CPU(指的是CPU的寄存器)-> 内存
写操作(数据输出):内存 -> CPU -> I/O设备
每个字的读/写都需要CPU的帮助 - 主要缺点和主要优点
优点:实现简单。在读/写指令之后,加上实现循环检查的一系列指令即可(因此才称为“程序直接控制方式”)
缺点:CPU 和 I/O 设备只能串行工作,CPU 需要一直轮询检查,长期处于“忙等”状态 ,CPU 利用率低。
2. 中断驱动方式
引入中断机制。由于 I/O 设备速度很慢,因此在 CPU 发出读/写命令后,可将等待 I/O 的进程阻塞,先切换到别的进程执行。当 I/O 完成后,控制器会向 CPU 发出一个中断信号,CPU 检测到中断信号后,会保存当前进程的运行环境信息,转去执行中断处理程序处理该中断。处理中断的过程中,CPU 从 I/O 控制器读一个字的数据传送到 CPU 寄存器,再写入主存。接着,CPU 恢复等待 I/O 的进程(或其他进程)的运行环境,然后继续执行。
- CPU 会在每个指令周期的末尾检查中断;
- 中断处理过程中需要保存、恢复进程的运行环境,这个过程是需要一定时间开销的。可见,如果中断发生的频率太高,也会降低系统性能。
- CPU 干预的频率
每次 I/O 操作开始之前、完成之后需要 CPU 介入。
等待 I/O 完成的过程中 CPU 可以切换到别的进程执行。 - 数据传送的单位
每次读/写一个字 - 数据的流向
读操作(数据输入):I/O 设备 -> CPU -> 内存
写操作(数据输出):内存 -> CPU -> I/O 设备 - 主要缺点和主要优点
- 优点:与“程序直接控制方式”相比,在“中断驱动方式”中,I/O 控制器会通过中断信号主动报告 I/O 已完成,CPU 不再需要不停地轮询。CPU 和 I/O 设备可并行工作,CPU利用率得到明显提升。
- 缺点:每个字在 I/O 设备与内存之间的传输,都需要经过 CPU。而频繁的中断处理会消耗较多的 CPU 时间。
3. DMA 方式
与“中断驱动方式”相比,DMA 方式( Direct Memory Access,直接存储器存取。主要用于块设备的 I/O 控制)有这样几个改进:
- 数据的传送单位是“块”。不再是一个字、一个字的传送;
- 数据的流向是从设备直接放入内存,或者从内存直接到设备。不再需要CPU作为“快递小哥”。
- 仅在传送一个或多个数据块的开始和结束时,才需要 CPU 干预。
DMA控制器的组成:
- DR(Data Register,数据寄存器):暂存从设备到内存,或从内存到设备的数据。
- MAR (Memory Address Register,内存地址寄存器):在输入时,MAR表示数据应放到内存中的什么 位置;输出时MAR表示要输出的数据放在内存中的什么位置。
- DC (Data Counter,数据计数器):表示剩余要读/写的字节数。
- CR(Command Register,命令/状态寄存器):用于存放 CPU 发来的 I/O 命令,或设备的状态信息。
控制逻辑
命令/状态寄存器控制 DMA 的工作模式并给 CPU 反映它当前的状态,地址寄存器存放 DMA 作业时的源地址和目标地址,数据寄存器存放要 DMA 转移的数据。
DMA 方式是在 I/O 设备与主存之间建立一条直接数据通路。是一种不经过 CPU 而直接从主存存取数据的数据交换模式,不过这条数据通路只是逻辑上的,实际并未直接建立一条物理线路,而通常是通过总线进行的。
- CPU 干预的频率
仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。 - 数据传送的单位
每次读/写一个或多个块(注意:每次读写的只能是连续的多个块,且这些块读入内存后在内存中也必须是连续的)。 - 数据的流向(不再需要经过 CPU )
读操作(数据输入):I/O 设备 -> 内存
写操作(数据输出):内存 -> I/O 设备 - 主要缺点和主要优点
- 优点:数据传输以“块”为单位,CPU 介入频率进一步降低。数据的传输不再需要先经过 CPU 再写入内存,数据传输效率进一步增加。CPU 和 I/O 设备的并行性得到提升。
- 缺点:CPU 每发出一条 I/O 指令,只能读/写一个或多个连续的数据块。
如果要读/写多个离散存储的数据块,或者要将数据分别写到不同的内存区域时,CPU 要分别发出多条 I/O 指令,进行多次中断处理才能完成。
4. 通道控制方式
通道:一种硬件,可以理解为是 “弱鸡版的CPU”(与 CPU 相比,通道可以执行的指令很单一,并且通道程序是放在主机内存中的,也就是说通道与 CPU 共享内存。)通道可以识别并执行一系列通道指令。
- CPU 向通道发出 I/O 指令。指明通道程序在内存中的位置,并指明要操作的是哪个 IO 设备。之后 CPU 就切换到其他进程执行了。
- 通道执行内存中的通道程序(其中指明了要读入/写出多少数据,读/写的数据应放在内存的什么位置等信息)
- 通道执行完规定的任务后,向 CPU 发出中断信号,之后 CPU 对中断进行处理
- 编制好的通道程序是存放在主存中的。
- 来自通道的 I/O 中断事件由设备管理负责处理。
- CPU启动通道时不管成功与否,通道都要回到 CPU,通道在执行通道程序的过程中,CPU 与通道并行,通道完成同道程序的执行后,便发I/O中断向CPU报告。
设置通道后,CPU 只需向通道发送一条 I/O 指令。通道在收到该指令后,便从内存中取出本次要执行的通道程序,然后执行该通道程序,仅当通道完成规定的I/O任务后,才向CPU发出中断信号。因此通道用于完成内存与外设的信息交换。
- CPU 干预的频率
极低,通道会根据 CPU 的指示执行相应的通道程序,只有完成一组数据块的读/写后才需要发出中断信号,请求CPU干预。 - 数据传送的单位
每次读/写一组数据块。 - 数据的流向(在通道的控制下进行)
读操作(数据输入):I/O 设备 -> 内存
写操作(数据输出):内存 -> I/O 设备 - 主要缺点和主要优点
缺点:实现复杂,需要专门的通道硬件支持。
优点:CPU、通道、I/O 设备可并行工作,资源利用率很高。
总结:
完成一次读/写的过程 | CPU干预频率 | 每次I/O的数据传输单位 | 数据流向 | |
---|---|---|---|---|
程序直接控制方式 | CPU 发出 I/O 命令后需要不断轮询 | 极高 | 字 | 设备 <-> CPU <-> 内存 |
中断驱动方式 | CPU 发出 I/O 命令后可以做其他事,本次 I/O 完成后设备控制器发出中断信号 | 高 | 字 | 设备 <-> CPU <-> 内存 |
DMA方式 | CPU发出 I/O 命令后可以做其他事,本次 I/O 完成后DMA控制器发出中断信号 | 中 | 块 | 设备 <-> 内存 |
通道控制方式 | CPU发出 I/O 命令后可以做其他事。通道会执行通道程序以完成 I/O ,完成后通道向CPU发出中断信号 | 低 | 一组块 | 设备 <-> 内存 |
优缺点:每一个阶段的优点都是解决了上一阶段的最大缺点。总体来说,整个发展过程就是要尽量减少CPU对VO过程的干预,把CPU从繁杂的VO控制事务中解脱出来,以便更多地去完成数据处理任务。
难点理解:
通道=弱鸡版CPU
通道程序=任务清单
5.1.4 I/O 子系统的层次结构
为了使复杂的 I/O 软件具有清晰的结构、良好的可移植性和适应性,在 I/O 软件中普遍采用了层次式结构,将系统输入/输出功能组织成一系列的层次,每层都利用其下层提供的服务,完成输入/输出功能中的某些子功能,并屏蔽执行功能实现的细节,向高层提供服务。在层次式结构的 I/O 软件中,只要层次间的接口不变,对某一层次中的软件的修改都不会引起其下层或高层代码的变更,仅最底层才涉及硬件的具体特性。
1. 用户层软件
一般而言,大部分的I/O软件都在操作系统的内部,但仍有一小部分在用户层,包括与用户程序链接在一起的库函数,以及完全运行于内核之外的一些程序。用户层软件必须通过一组系统调用来获取操作系统服务。
2. 设备独立性软件
设备独立性软件,又称设备无关性软件。与设备的硬件特性无关的功能几乎都在这一层实现。
这一层主要实现的功能:
-
向上层提供统一的调用接口(如 read/write 系统调用)。
-
设备的保护。(原理类似与文件保护。设备被看做是一种特殊的文件,不同用户对各个文件的访问权限是不一样的,同理,对设备的访问权限也不一样)。
-
差错处理。(设备独立性软件需要对一些设备的错误进行处理)。
-
设备的分配与回收。
-
数据缓冲区管理。(可以通过缓冲技术屏蔽设备之间数据交换单位大小和传输速度的差异)
-
建立逻辑设备名到物理设备名的映射关系;根据设备类型选择调用相应的驱动程序。(用户或用户层软件发出 I/O 操作相关系统调用的系统调用时,需要指明此次要操作的I/O设备的逻辑设备名(eg:去学校打印店打印时,需要选择 打印机1/打印机2/打印机3 ,其实这些都是逻辑设备名);设备独立性软件需要通过“逻辑设备表(LUT,Logical UnitTable)”来确定逻辑设备对应的物理设备,并找到该设备对应的设备驱动程序)。
操作系统系统可以采用两种方式管理逻辑设备表(LUT):
- 第一种方式,整个系统只设置一张LUT,这就意味着所有用户不能使用相同的逻辑设备名,因此这种方式只适用于单用户操作系统。
- 第二种方式,为每个用户设置一张LUT,各个用户使用的逻辑设备名可以重复,适用于多用户操作系统。系统会在用户登录时为其建立一个用户管理进程,而LUT就存放在用户管理进程的 PCB 中。
思考:为什么不同类型的 I/O 设备需要有不同的驱动程序处理?
各式各样的设备,外形不同,其内部的电子部件(I/O控制器)也有可能不同
不同设备的内部硬件特性也不同,这些特性只有厂家才知道,因此厂家须提供与设备相对应的驱动程序,CPU 执行驱动程序的指令序列,来完成设置设 备寄存器,检查设备状态等工作。
系统只要按设备类型配置设备驱动程序即可,即每类设备只需一个设备驱动程序。
3. 设备驱动程序
与硬件直接相关,负责具体实现系统对设备发出的操作指令,驱动 I/O 设备工作的驱动程序。
驱动程序一般会以一个独立进程的方式存在。
4. 中断处理程序
当 I/O 任务完成时,I/O 控制器会发送一个中断信号,系统会根据中断信号类型找到相应的中断处理程序并执行。
5.1.5 补充
- 可寻址是块设备的基本特征。共享设备必须是可寻址的和可随机访问的设备。分配共享设备是不会引起进程死锁的。
- 虚拟设备是指把一个物理设备变换成多个对应的逻辑设备。不允许用户使用比系统中具有的物理设备更多的设备。
- 磁盘设备的 I/O 控制主要采用 DMA 方式。因为磁盘是典型的块设备,而 DMA 方式主要用户块设备。
- 为了便于上层软件的编制,设备控制器通常需要提供控制寄存器、状态寄存器和控制命令。控制寄存器和状态寄存器分别用于接收上层发来的命令并存放设备状态信号,是设备控制器与上层的接口;至于控制命令,每种设备对应的设备控制器都对应一组相应的控制命令,CPU 通过控制命令控制设备控制器。而中断寄存器是位于计算机主机,不存在 I/O 地址寄存器。
- 在设备控制器中用于实现设备控制功能的是 I/O 逻辑。而接口是用来传输信号的。
- 在设备管理中,设备映射表(DMT)的作用是建立逻辑设备与物理设备的对应关系。
- 通道是一种特殊的处理器,所以属于硬件技术。SPOOLing、缓冲池、内存覆盖都是在内存的基础上通过软件实现的。
- 设备分配时应考虑设备的固有属性、设备独立性和设备安全性,一般不需要考虑及时性。设备的固有属性决定了设备的使用方式,设备独立性可以提高设备分配的灵活性和设备的利用率,设备安全性可以保证分配设备时不会导致永久阻塞。
- 计算机系统为每台设备确定一个编号以便区分和识别设备,这个确定的编号称为设备的绝对号。
- 关于通道、设备控制器和设备之间的关系是,通道控制设备控制器,设备控制器控制设备。
- 所有设备的启动工作都由系统统一来做。
- 本地用户通过键盘登录系统时,首先获得键盘输入信息的程序是中断处理程序。键盘是典型的通过中断 I/O 方式工作的外设,当用户输入信息时,计算机响应中断并通过中断处理程序获得输入信息。
- 将系统调用参数翻译成设备操作命令的工作由设备无关的操作系统软件完成。系统调用命令是操作系统提供给用户程序的通用接口,不会因为具体设备的不同而改变。而设备驱动程序负责执行操作系统发出的 I/O 命令,它因设备不同而不同。
- 计算磁盘号、磁头号和扇区号的工作是由设备驱动程序完成的。
- DMA控制方式与中断控制方式的主要区别如下:
1)中断控制方法在每个数据传送完成后中断 CPU,而 DMA 控制方式则在所要求传送的一批数据全部传送结束时中断 CPU。
2)中断控制方式的数据传送在中断处理时由 CPU 控制完成,而 DMA 控制方式则在DMA控制器的控制下完成。不过,在 DMA 控制方式中,数据传送的方向、存放数据的内存始址及传送数据的长度等仍然由CPU控制。
3)DMA 方式以存储器为核心,中断控制方式以 CPU 为核心。因此 DMA 方式能与 CPU 并行工作。
4)DMA 方式传输批量的数据,中断控制方式的传输则以字节为单位。 - DMA方式与通道方式的主要区别是什么?
在DMA控制方式中,在 DMA 控制器控制下设备和主存之间可以成批地进行数据交换而不用 CPU 干预,这样既减轻了 CPU 的负担,又大大提高了 I/O 数据传送的速度。通道控制方式与 DMA 控制方式类似,也是一种以内存为中心实现设备与内存直接交换数据的控制方式。不过在通道控制方式中,CPU 只需发出启动指令,指出通道相应的操作和 I/O 设备,该指令就可以启动通道并使通道从内存中调出相应的通道程序执行。与 DMA 控制方式相比,通道控制方式所需的 CPU 干预更少,并且一个通道可以控制多台设备,进一步减轻了 CPU 的负担。另外,对通道来说,可以使用一些指令灵活改变通道程序,这一点DMA控制方式无法做到。
5.2 I/O 核心子系统
5.2.1 I/O 子系统概述
由于 I/O 设备种类繁多,功能和传输速率差异巨大,因此需要多种方法来进行设备控制。这些方法共同组成了操作系统内核的 I/O 子系统,它将内核的其他方面从繁重的 I/O 设备管理中解放出来。I/O 核心子系统提供的服务主要有 I/O 调度、缓冲与高速缓存、设备分配与回收、假脱机、设备保护和差错处理等。
假脱机技术(SPOOLing技术)需要请求“磁盘设备”的设备独立性软件的服务,因此一般来说假脱机技术是在用户层软件实现的。
设备保护:
- 操作系统需要实现文件保护功能,不同的用户对各个文件有不同的访问权限(如:只读、读和写等)。
- 在 UNIX 系统中,设备被看做是一种特殊的文件,每个设备也会有对应的 FCB。当用户请求访问某个设备时,系统根据 FCB 中记录的信息来判断该用户是否有相应的访问权限,以此实现“设备保护”的功能。(参考“文件保护”小节)
5.2.2 I/O 调度概念
I/O 调度:用某种算法确定一个好的顺序来处理各个 I/O 请求。
如:磁盘调度(先来先服务算法、最短寻道优先算法、SCAN 算法、C-SCAN 算法、LOOK 算法、C-LOOK 算法)。当多个磁盘 I/O 请求到来时,用某种调度算法确定满足 I/O 请求的顺序。
同理,打印机等设备也可以用先来先服务算法、优先级算法、短作业优先等算法来确定 I/O 调度顺序。
5.2.3 高速缓存与缓冲区
1. 缓冲区的概念和作用
缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可利用内存作为缓冲区。
使用硬件作为缓冲区的成本较高,容量也较小,一般仅用在对速度要求非常高的场合(如存储器管理中所用的联想寄存器,由于对页表的访问频率极高,因此使用速度很快的联想寄存器来存放页表项的副本)
一般情况下,更多的是利用内存作为缓冲区,“设备独立性软件”的缓冲区管理就是要组织管理好这些缓冲区。
缓冲区的作用:
- 缓和 CPU 和 I/O 设备之间速度不匹配的矛盾。
- 减少对 CPU 的中断频率,放宽对 CPU 中断响应时间的限制。
- 解决数据粒度不匹配的问题。如:输出进程每次可以生成一块数据,但 I/O 设备每次只能输出一个字符。
- 提高 CPU 与 I/O 设备之间的并行性。
2. 单缓冲
假设某用户进程请求某种块设备读入若干块的数据。若采用单缓冲的策略,操作系统会在主存中为其分配一个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)。
用户进程的内存空间中,会分出一片工作区来接受输入/输出数据(一般也默认工作区大小与缓冲区相同)。计算每处理一块数据平均时间:假定一个初始状态,分析下次到达相同状态需要多少时间,这就是处理这一块数据平均所需时间。在“单缓冲”题型中,可以假设初始状态为工作区满,缓冲区空。
块设备 -> (输入 T ) -> 缓冲区 -> (传送 M ) -> 用户进程的工作区 -> (处理 C ) -> CPU
注意:当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出;当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满以后,才能从缓冲区把数据传出。
1)假设 T > C
CPU 处理完数据后暂时不能将下一块数据传送到工作区,必须等待缓冲区中冲满数据。
2)假设 T < C
缓冲区中,冲满数据后暂时不能继续冲入下一块数据,必须等待 CPU 处理结束后将数据从缓冲区传送到工作区。
3. 双缓冲
假设某用户进程请求某种块设备读入若干块的数据。若采用双缓冲的策略,操作系统会在主存中为其分配两个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)
双缓冲题目中,假设初始状态为:工作区空,其中一个缓冲区满,另一个缓冲区空。
1)假设T > C+M
<
2)假设T < C+M
M(1) 表示“将缓冲区1中的数据传送到工作区”;M(2)表示“将缓冲区2中的数据传送到工作区”
结论:采用双缓冲策略,处理一个数据块的平均耗时为 Max (T, C+M)。
4. 使用单/双缓冲在通信时的区别
两台机器之间通信时,可以配置缓冲区用于数据的发送和接受。
若两个相互通信的机器只设置单缓冲区,A 机要发送的数据要先放入 A 机缓冲区中,等缓冲区满时将数据发出;B 机的将缓冲区中的数据全部取走后,才能向 A 机发送数据。在任一时刻只能实现数据的单向传输。
若两个相互通信的机器设置双缓冲区,则同一时刻可以实现双向的数据传输。
注:管道通信中的“管道”其实就是缓冲区。要实现数据的双向传输,必须设置两个管道。
5. 循环缓冲区
将多个大小相等的缓冲区链接成一个循环队列。
注:以下图示中,橙色表示已充满数据的缓冲区,绿色表示空缓冲区。
6. 缓冲池
缓冲池由系统中共用的缓冲区组成。这些缓冲区按使用状况可以分为:空缓冲队列、装满输入数据的缓冲队列(输入队列)、装满输出数据的缓冲队列(输出队列)。
另外,根据一个缓冲区在实际运算中扮演的功能不同,又设置了四种工作缓冲区:用于收容输入数据的工作缓冲区(hin)、用于提取输入数据的工作缓冲区(sin)、用于收容输出数据的工作缓冲区(hout)、用于提取输出数据的工作缓冲区(sout)。
5.2.4 设备分配与回收
设备分配是根据用户的I/O请求分配所需的设备。分配的总原则是充分发挥设备的使用效率,尽可能地让设备忙碌,又要避免由于不合理的分配方式造成进程死锁。
1. 设备分配时应考虑的因素
1)设备的固有属性
设备的固有属性可分为三种:独占设备、共享设备、虚拟设备。
独占设备——一个时段只能分配给一个进程(如打印机)
共享设备——可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上交替使用。
虚拟设备——采用 SPOOLing 技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用 SPOOLing 技术实现的共享打印机)
2)设备分配算法
设备的分配算法:
先来先服务
优先级高者优先
短任务优先
……
3)设备分配中的安全性
从进程运行的安全性上考虑,设备分配有两种方式:
- 安全分配方式:为进程分配一个设备后就将进程阻塞,本次I/O完成后才将进程唤醒。(eg:考虑进程请求打印机打印输出的例子)
- 一个时段内每个进程只能使用一个设备。
优点:破坏了“请求和保持”条件,不会死锁。
缺点:对于一个进程来说,CPU 和 I/O 设备只能串行工作。
- 一个时段内每个进程只能使用一个设备。
- 不安全分配方式:进程发出 I/O 请求后,系统为其分配 I/O 设备,进程可继续执行,之后还可以发出新的 I/O 请求。只有某个 I/O 请求得不到满足时才将进程阻塞。
- 一个进程可以同时使用多个设备。
优点:进程的计算任务和 I/O 任务可以并行处理,使进程迅速推进。
缺点:有可能发生死锁(死锁避免、死锁的检测和解除)。
- 一个进程可以同时使用多个设备。
2. 静态分配和动态分配
- 静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源。破坏了“请求和保持”条件,不会发生死锁。
- 动态分配:进程运行过程中动态申请设备资源。
3. 设备分配管理中的数据结构
“设备、控制器、通道”之间的关系: 一个通道可控制多个设备控制器,每个设备控制器可控制多个设备。
1)设备控制表(DCT)
系统为每个设备配置一张DCT,用于记录设备情况。
- 设备类型。如:打印机/扫描仪/键盘。
- 设备标识符。即物理设备名,系统中的每个设备的物理设备名唯一。
- 设备状态。忙碌/空闲/故障…
- 指向控制器表的指针。每个设备由一个控制器控制,该指针可找到相应控制器的信息。
- 重复执行次数或时间。当重复执行多次 I/O 操作后仍不成功,才认为此次 I/O 失败。
- 设备队列的队首指针。指向正在等待该设备的进程队列(由进程PCB组成队列)
注:“进程管理”章节中曾经提到过“系统会根据阻塞原因不同,将进程PCB挂到不同的阻塞队列中”。
2)控制器控制表(COCT)
每个设备控制器都会对应一张COCT。操作系统根据COCT的信息对控制器进行操作和管理。
- 控制器标识符。各个控制器的唯一 ID。
- 控制器状态。忙碌/空闲/故障…
- 指向通道表的指针。每个控制器由一个通道控制,该指针可找到相应通道的信息
- 控制器队列的队首指针。
- 控制器队列的队尾指针。指向正在等待该控制器的进程队列(由进程PCB组成队列)
3)通道控制表(CHCT)
每个通道都会对应一张CHCT。操作系统根据CHCT的信息对通道进行操作和管理。
- 通道标识符。各个通道的唯一ID 。
- 通道状态。忙碌/空闲/故障…
- 与通道连接的控制器表首址。可通过该指针找到该通道管理的所有控制器相关信息(COCT)
- 通道队列的队首指针。
- 通道队列的队尾指针。指向正在等待该通道的进程队列(由进程PCB组成队列)
4)系统设备表(SDT)
记录了系统中全部设备的情况,每个设备对应一个表目。
系统设备表(SDT):表目1 表目2 … 表目i …
表目i :设备类型(如:打印机/扫描仪/键盘)、设备标识符(即物理设备名)、DCT(设备控制表)、驱动程序入口。
4. 设备分配的步骤
-
根据进程请求的物理设备名查找 SDT(注:物理设备名是进程请求分配设备时提供的参数)
-
根据 SDT 找到 DCT,若设备忙碌则将进程 PCB 挂到设备等待队列中,不忙碌则将设备分配给进程。
-
根据 DCT 找到 COCT,若控制器忙碌则将进程 PCB 挂到控制器等待队列中,不忙碌则将控制器分配给进程。
-
根据 COCT 找到 CHCT,若通道忙碌则将进程 PCB 挂到通道等待队列中,不忙碌则将通道分配给进程。
注:只有设备、控制器、通道三者都分配成功时,这次设备分配才算成功,之后便可启动 I/O 设备进行数据传送。
缺点:
- 用户编程时必须使用“物理设备名”,底层细节对用户不透明,不方便编程。 ②若换了一个物理设备,则程序无法运行。
- 若进程请求的物理设备正在忙碌,则即使系统中还有同类型的设备,进程也必须阻塞等待。
改进方法:建立逻辑设备名与物理设备名的映射机制,用户编程时只需提供逻辑设备名。
5. 设备分配步骤的改进
- 根据进程请求的逻辑设备名查找 SDT(注:用户编程时提供的逻辑设备名其实就是“设备类型”)
- 查找 SDT,找到用户进程指定类型的、并且空闲的设备,将其分配给该进程。操作系统在逻辑设备表(LUT)中新增一个表项。
- 根据 DCT 找到 COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程。
- 根据 COCT 找到 CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程。
逻辑设备表(LUT)建立了逻辑设备名与物理设备名之间的映射关系。
某用户进程第一次使用设备时使用逻辑设备名向操作系统发出请求,操作系统根据用户进程指定的设备类型(逻辑设备名)查找系统设备表,找到一个空闲设备分配给进程,并在 LUT 中增加相应表项。
如果之后用户进程再次通过相同的逻辑设备名请求使用设备,则操作系统通过LUT表即可知道用户进程实际要使用的是哪个物理设备了,并且也能知道该设备的驱动程序入口地址。
逻辑设备表的设置问题:
整个系统只有一张 LUT:各用户所用的逻辑设备名不允许重复,适用于单用户操作系统。
每个用户一张 LUT:不同用户的逻辑设备名可重复,适用于多用户操作系统
5.2.5 SPOOLing 技术(假脱机技术)
1. 脱机技术的概念
手工操作阶段:主机直接从 I/O 设备获得数据,由于设备速度慢,主机速度很快。人机速度矛盾明显,主机要浪费很多时间来等待设备。
批处理阶段引入了脱机输入/输出技术(用磁带完成)。在外围控制机的控制下,慢速输入设备的数据先被输入到更快速的磁带上。之后主机可以从快速的磁带上读入数据,从而缓解了速度矛盾。
脱机:脱离主机的控制进行的输入/输出操作。
2. 假脱机技术——输入井和输出井
“假脱机技术”,又称“SPOOLing 技术”是用软件的方式模拟脱机技术。SPOOLing 系统的组成如下:
用户的打印结果首先是送到位于磁盘固定区域的输出井。
“输入井”模拟脱机输入时的磁带,用于收容 I/O 设备输入的数据。
“输出井”模拟脱机输出时的磁带,用于收容用户进程
3. 假脱机技术——输入进程和输出进程
要实现 SPOOLing 技术,必须要有多道程序技术的支持。系统会建立 “输入进程”和 “输出进程”。
“输入进程”模拟脱机输入时的外围控制机。
“输出进程”模拟脱机输出时的外围控制机。
输入进程模拟脱机输入时的外围控制机,将用户要求的数据从输入机通过输入缓冲区再送到输入井。当 CPU 需要输入数据时,直接将数据从输入井读入内存。输出进程模拟脱机输出时的外围控制机,把用户要求输出的数据先从内存送到输出井,待输出设备空闲时,再将输出井中的数据经过输出缓冲区送到输出设备。
4. 假脱机技术——输入/输出缓冲区
在输入进程的控制下,“输入缓冲区”用于暂存从输入设备输入的数据,之后再转存到输入井中。在输出进程的控制下,“输出缓冲区”用于暂存从输出井送来的数据,之后再传送到输出设备上。
注意,输入缓冲区和输出缓冲区是在内存中的缓冲区。
5. 共享打印机原理分析
独占式设备——只允许各个进程串行使用的设备。一段时间内只能满足一个进程的请求。
共享设备——允许多个进程“同时”使用的设备(宏观上同时使用,微观上可能是交替使用)。可以同时满足多个进程的使用请求。
打印机是种“独占式设备”,但是可以用 SPOOLing 技术改造成“共享设备”。当多个用户进程提出输出打印的请求时,系统会答应它们的请求,但是并不是真正把打印机分配给他们,而是由假脱机管理进程为每个进程做两件事:
- 在磁盘输出井中为进程申请一个空闲缓冲区(也就是说,这个缓冲区是在磁盘上的),并将要打印的数据送入其中;
- 为用户进程申请一张空白的打印请求表,并将用户的打印请求填入表中(其实就是用来说明用户的打印数据存放位置等信息的),再将该表挂到假脱机文件队列上。当打印机空闲时,输出进程会从文件队列的队头取出一张打印请求表,并根据表中的要求将要打印的数据从输出井传送到输出缓冲区,再输出到打印机进行打印。用这种方式可依次处理完全部的打印任务。
虽然系统中只有一个台打印机,但每个进程提出打印请求时,系统都会为在输出井中为其分配一个存储区(相当于分配了一个逻辑设备),使每个用户进程都觉得自己在独占一台打印机,从而实现对打印机的共享。
SPOOLing 技术可以把一台物理设备虚拟成逻辑上的多台设备,可将独占式设备改造成共享设备。
总结:
- 脱机技术
- 外围控制机+更高速的设备—磁带
- 作用:缓解设备与 CPU 的速度矛盾,实现预输入、缓输出
- 假脱机技术
- 又叫 SPOOLing 技术,用软件的方式模拟脱机技术
- 输入井和输出井—模拟脱机输入/输出时的磁带
- 输入进程和输出进程—模拟脱机输入/输出时的外围控制机
- 输入缓冲区和输出缓冲区—内存中的缓冲区,输入、输出时的“中转站”
- 共享打印机
- 用SPOOLing技术将独占式的打印机“虚拟"成共享打印机
5.2.6 补充
- 什么是设备的独立性?引入设备的独立性有什么好处?
设备独立性是指用户在编程序时使用的设备与实际设备无关。一个程序应独立于分配给它的某类设备的具体设备,即在用户程序中只指明I/O使用的设备类型即可。
设备独立性有以下优点:
1.方便用户编程。
2.使程序运行不受具体机器环境的限制。
3.便于程序移植。 - 磁盘属于共享设备。共享设备是指在一个时间间隔内可被多个进程同时访问的设备。
打印机和磁带机都不属于。打印机在一个时间间隔内被多个进程访问时打印出来的文档就会乱;磁带机旋转到所需的读写位置需要较长时间,若一个时间间隔内被多个进程访问,磁带机就只能一直旋转,没时间读写。 - 程序员利用系统调用打开 I/O 设备时,通常使用的设备标识是逻辑设备名。用户程序对I/O设备的请求采用逻辑设备名,而程序实际执行时使用物理设备名,它们之间的转换是由设备无关软件层完成的。
- 可以改善磁盘 I/O 性能的是重排 I/O 请求次序、预读和滞后写、优化文件物理块的分布。重排 I/O 请求次序是进行 I/O 调度,使进程之间公平地共享磁盘访问,减少 I/O 完成所需要的平均等待时间;缓冲区结合预读和滞后写技术对于具有重复性及阵发性的 I/O 进程改善磁盘 I/O 性能很有帮助;优化文件物理块的分布可以减少寻找时间与延迟时间,从而提高磁盘性能。
- 为了使并发进程能有效地进行输入和输出,最好采用缓冲池结构的缓存技术。缓冲池是系统的共用资源,可供多个进程共享。
- 输入井和输出井是在磁盘上开辟的存储空间,而输入/输出缓冲区则是在内存中开辟的。所以缓冲技术的缓冲区是在内存中的。
- 若 I/O 所花费的时间比 CPU 的处理时间短得多,则缓冲区几乎无效。因为缓冲区主要解决输入/输出速度比CPU 处理的速度满而造成数据积压的矛盾。
- 缓冲区管理要重要考虑的问题是实现进程访问缓冲区的同步。在缓冲机制中,无论是单缓冲、多缓冲还是缓冲池,由于缓冲区是一种临界资源,所以在使用缓冲区时都有一个申请和释放(及互斥)的问题需要考虑。
- 提高单机资源利用率的关键技术是多道程序设计技术。在单机系统中,最关键的资源是处理机资源,最大化地提高处理机利用率,技术最大化地提高系统效率。多道程序设计技术是提高处理机利用率的关键技术。
- SPOOLing 技术可将独占设备改造为共享设备,其主要目的是提高系统资源/独占设备的利用率。
- SPOOLing 技术需要使用磁盘空间(输入井和输出井)和内存空间(输入/输出缓冲区),并不需要外围计算机的支持。
- 在 SPOOLing 系统中,用户进程实际分配到的是外存区,即虚拟设备。
- SPOOLing 技术是操作系统中采用的以空间换取时间的技术。
- 在 SPOOLing 系统中,设备与输入井/输出井之间数据的传送是由系统实现的。