目录
一、第0章 操作系统演义
1.0——史前时代
1.1---UNIX演义(操作系统的真正开端)
1.2 个人操作系统——从DOS到Windows
1.3 嵌入式操作系统简介——当前和未来的主战场
二、第一章 操作系统绪论
2.1 操作系统的目标和作用
2.2 操作系统的发展过程
2.2.1 未配置操作系统的计算机系统
2.2.2 单道批处理系统
2.2.3 多道批处理系统
2.2.4 分时系统
2.3 操作系统的基本特性
2.3.1 并发
2.3.2 共享
1. 互斥共享方式
2. 同时访问方式
2.3.3 虚拟(Virtual)
2.3.4 异步性(Asynchronism)
2.4 操作系统的主要功能
2.4.1 处理机管理功能
1.进程控制:
2.进程同步:
3.进程通信:
2.4.2 存储器管理功能
1. 内存分配
2.内存保护
3. 地址映射
4. 内存扩充
2.4.3 设备管理功能
1. 缓冲管理
2. 设备分配
3. 设备处理
2.4.4 文件管理功能
1. 文件存储空间的管理
2. 目录管理
3. 文件的读/写管理和保护
2.4.5 用户接口
1. 命令接口
2. 程序接口
3. 图形接口
2.5 操作系统的结构设计
2.5.1 软件工程的基本概念
1. 软件的含义
2. 软件工程的含义
2.5.2 传统的操作系统结构
1. 无结构操作系统
2. 模块化OS结构
3. 分层式OS结构
2.5.3 微内核OS结构
1. 客户/服务器模式(Client-Server Model)
2. 面向对象的程序设计技术(Object-Orientated Programming)
3. 微内核技术
三、第二章 进程管理
3.1 前趋图和程序执行
3.1.1 前趋图
3.1.2 程序的顺序执行
1. 程序的顺序执行
2.顺序执行的特点
3.1.3 程序的并发执行
1. 程序的并发执行
2. 程序并发执行时的特征
3.2 进程的描述
3.2.1. 进程的定义
1) 结构特征
2) 动态性
3) 并发性
4) 独立性
5) 异步性
3.2.2 进程的基本状态及转换
1. 进程的三种基本状态
2. 三种基本状态的转换
3. 创建状态和终止状态
3.2.3 挂起操作和进程状态的转换
1. 挂起操作的引入
2. 引入挂起原语操作后三个进程状态的转换
3. 引入挂起操作后五个进程状态的转换
3.2.4 进程管理中的数据结构
1. 操作系统中用于管理控制的数据结构
2. 进程控制块PCB的作用
3. 进程控制块中的信息
4. 进程控制块的组织方式
3.3 进程控制
3.3.1 操作系统内核
3.3.2 进程的创建
1. 进程的层次结构
2. 进程图
3. 引起创建进程的事件
4. 进程的创建(Creation of Process)
3.3.3 进程的终止
1. 引起进程终止(Termination of Process)的事件
2. 进程的终止过程
3.3.4 进程的阻塞和唤醒
1. 引起进程阻塞和唤醒的事件
2. 进程阻塞过程
3. 进程唤醒过程
3.3.5 进程的挂起和激活
1. 进程的挂起
2. 进程的激活过程
3.4 进程同步
3.4.1 进程同步的基本概念
1. 两种形式的制约关系
2. 临界资源(Critical Resouce)
3. 临界区(critical section)
4. 同步机制应遵循的规则
3.4.2 硬件同步机制
1. 关中断
2. 利用Test-and-Set指令实现互斥
3. 利用Swap指令实现进程互斥
3.4.3 信号量机制
1. 整型信号量
2. 记录型信号量
3. AND型信号量
4. 信号量集
3.4.4 信号量的应用
1.用信号量实现进程互斥
2. 利用信号量实现前趋关系
3.4.5 管程(Monitors)机制
1.管程的定义
2. 条件变量
3.5 经典进程的同步问题
3.5.1 生产者-消费者(Producer-consumer)问题
3.5.2 哲学家进餐问题(The Dinning Philosophers)
1. 利用记录型信号量解决
2. 利用AND信号量机制解决哲学家进餐问题
3.5.3 读者-写者(Reader-Writer)问题
1. 利用记录型信号量解决读者-写者问题编辑
2. 利用信号量集机制解决读者-写者问题
3.6 进程通信
2.6.1 进程通信的类型
2. 管道(pipe)通信系统
3. 消息传递系统(Message passing system)
4. 客户机-服务器系统(Client-Server system)
3.6.2 消息传递通信的实现方式
1. 直接消息传递系统
2. 信箱通信
3.6.3 直接消息传递系统实例
1. 消息缓冲队列通信机制中的数据结构
2. 发送原语
3. 接收原语
3.7 线程(Threads)的基本概念
3.7.1 线程的引入
1. 进程的两个基本属性
2. 程序并发执行所需付出的时空开销
3. 线程——作为调度和分派的基本单位
3.7.2 线程和进程的比较
1) 调度的基本单位
3.7.3 线程的状态和线程控制块
1. 线程运行的三个状态
2. 线程控制块TCB
3. 多线程OS中的进程属性
3.8 线程的实现
2.8.1 线程的实现方式
1. 内核支持线程KST(Kernel Supported Threads)
2. 用户级线程ULT(User Level Threads)
3. 组合方式
3.8.2 线程的实现
1. 内核支持线程的实现
2.8.3 线程的创建和终止
1. 线程的创建
2. 线程的终止
第一台现代电子计算机:ENIAC
世界上第一台现代意义的通用计算机:EDVAC
约翰·冯·诺依曼:“现代电子计算机之父”(EDVAC的发明者)。 冯.诺依曼体系结构奠定了现代计算机的基础。
冯.诺依曼体系结构:
在1945年3月的EDVAC设计报告中提出;
主要思想:二进制;存储程序;顺序执行 ;
基本框架:1、必须有一个存储器;2、必须有一个控制器;3、必须有一个运算器,用于完成算术运算和逻辑运算;4、必须有输入设备和输出设备,用于进行人机通信。 程序和数据统一存储并在程序控制下自动工作。
另一种体系结构:哈佛结构
大型机时代——操作系统的发源和雏形:
早期没有操作系统;用卡片或打孔纸带作为输入介质;程序读入机器后,机器就开始工作直到程序停止;由于程序难免有误,所以机器通常都会中途崩溃;程序一般通过控制板的开关和状态灯来调试。 后来,机器引入帮助程序输入输出等工作的代码库。这是现代操作系统的起源。然而,机器每次只能执行一件任务。
早期的操作系统非常多样化,生产商生产出针对各自硬件的系统。最能反映这一状况的是,厂家每生产一台新的机器都会配备一套新的操作系统。 这种情况一直持续到二十世纪六十年代IBM公司开发了System/360系列机器。尽管这些机器在性能上有明显的差异,但是他们有统一的操作系统——S/360。
1961年底,IBM公司开始了S/360计划, 该计划希望能有一个通用的计算机系统,不同型号的计算机能使用相同的设备,相同的软件,能共同工作。但是360的软件开发项目并未取得预期的目的。
Unix之父——肯·汤普逊&丹尼斯·里奇
1.1.1 Unix演义——从一个失败的合作开始
1964年贝尔实验室、麻省理工学院及美国通用电气公司开始共同研发一套操作系统Multics,其目的是安装在大型主机上多人多工。 项目开发以失败告终,1969年2月,贝尔实验室退出了该项目。
Ken Thompson为MULTICS写了个游戏叫“Space Travel”。 AT&T退出项目后,为了让这个游戏能玩,他找来Dennis Ritchie为这个游戏开发一个极其简单的操作系统。 系统本想在DEC-10上写,后来没有申请到,只好在Digital PDP-7的迷你计算机进行。 他们用汇编语言仅一个月的时间就开发了一个操作系统的原型。他们的同事Brian Kernighan非常不喜欢这个系统,嘲笑Ken Thompson说:“你写的系统好真差劲,干脆叫Unics算了。1971年,Ken Thompson写了充分长篇的申请报告,申请到了一台PDP-11/24的机器,于是Unix第一版出来了。 这台电脑只有24KB的物理内存和500K磁盘空间。Unix占用了12KB的内存,剩下的一半内存可以支持两用户进行Space Travel的游戏。而著名的fork()系统调用也就是在这时出现的。
1.1.2 Linux的出现
1990年,Linus Torvalds用汇编语言写了一个在80386保护模式下处理多任务切换的程序。 后来开始写了一些硬件的设备驱动程序,一个小的文件系统。这样0.0.1版本的Linux就出来了。 在1991年10 月5号发布Linux 0.0.2版本,在这个版本中已经可以运行bash 和gcc。从一开始,Linus就决定自由扩散Linux,包括原代码。 随即Linux引起黑客们(hacker)的注意,通过计算机网络加入了Linux的内核开发。 Linux 发展迅猛,几乎一两个礼拜就有新版或修正版的出现 到1993年底94年初,Linux 1.0终于诞生了!1992年Linux与其他GNU软件结合,完全自由的操作系统正式诞生。该操作系统往往被称为“GNU/Linux”或简称Linux。 尽管如此GNU计划自己的内核Hurd依然在开发中,目前已经发布Beta版本。
LINUX的特点:Linux是一个免费,开源的操作系统。 Linux具有高效性和灵活性。 Linux模块化的设计结构,使得它既能在价格昂贵的工作站上运行,也能够在廉价的PC机上实现全部的Unix特性,具有多任务、多用户的能力。 Linux上可以运行丰富的软件,Linux+Appache+ MySQL成为大量网站的标准配置。
第一台个人计算机——Altair
GUI的发端:GUI(Graphics User Interface,图形用户接口)技术是图形界面的核心,使得用户以图形的方式与微机交流成为可能,Windows和Macintosh的操作系统都基于这一技术。它们为用户提供了划时代的WYSIWYG(What You See Is What You Get,即“所见即所得”)功能。PARC的工程师们发明了SmartTalk种语言,可以把屏幕分割成不同的窗口,使不同的程序运行在各自的窗口里,互不干扰,要在不同的程序之间切换,只要用鼠标点一点就可以了。 后来施乐在自己的计算机产品上安装了这套系统,并且又相继推出了“图标”等概念,但因为没有意识到将要到来的这一场革命,没有把这套系统做成大众化的产品推广,从而失去了一次宝贵的机会。
Windows比之DOS的进步:图形化界面; 多任务环境,可以在多个软件之间进行切换; 统一的通用的操作界面,免去了记忆大量命令的烦恼;
Apple的创立:苹果公司由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和Ron Wayn在1976年4月1日正式创立。
嵌入式系统就是指以应用为中心,以计算机技术为基础,软硬件可裁剪,适合应用系统对功能、可靠性、成本、体积、功耗等(严格)要求的一种专用计算机系统。 它一般由以下几部分组成:嵌入式微处理器 、外围硬件设备、 嵌入式操作系统、特定的应用程序。优点:专用、灵活性、小巧轻便、成本低、功耗低、效率高
操作系统的目标: 1. 有效性 2. 方便性 3. 可扩充性 4. 开放性
操作系统的作用:
1.OS作为用户与计算机硬件系统之间的接口
(应注意,OS是一个系统软件,因而这种接口是软件接口。)
(1) 命令方式。这是指由OS提供了一组联机命令(语言), 用户可通过键盘输入有关命令,来直接操纵计算机系统。(2) 系统调用方式。OS提供了一组系统调用,用户可在自己的应用程序中通过相应的系统调用,来操纵计算机。(3) 图形、窗口方式。用户通过屏幕上的窗口和图标来操纵计算机系统和运行自己的程序。
2. OS作为计算机系统资源的管理者
在一个计算机系统中,通常都含有各种各样的硬件和软件资源。归纳起来可将资源分为四类:处理器、存储器、 I/O设备以及信息(数据和程序)。相应地,OS的主要功能也正是针对这四类资源进行有效的管理,即:处理机管理, 用于分配和控制处理机;存储器管理,主要负责内存的分配与回收;I/O设备管理,负责I/O设备的分配与操纵;文件管理,负责文件的存取、共享和保护。
1. 人工操作方式 (1) 用户独占全机。 (2) CPU等待人工操作。
2. 脱机输入/输出(Off-Line I/O)方式 (1)减少了CPU的空闲时间。 (2) 提高I/O速度。
多道程序设计的基本概念:在该系统中, 用户所提交的作业都先存放在外存上并排成一个队列,称为“后备队列”;然后,由作业调度程序按一定的算法从后备队列中选择若干个作业调入内存,使它们共享CPU和系统中的各种资源。
多道批处理系统的优缺点 :
(1) 资源利用率高。
(2) 系统吞吐量大。
(3) 平均周转时间长。
(4) 无交互能力
多道批处理系统需要解决的问题:
(1) 处理机争用问题。
(2) 内存分配和保护问题。
(3) I/O设备分配问题。
(4) 文件的组织和管理问题。
(5) 作业管理问题。
(6)用户与系统的接口问题。
分时系统的引入:如果说,推动多道批处理系统形成和发展的主要动力,是提高资源利用率和系统吞吐量,那么,推动分时系统形成和发展的主要动力,则是用户的需求。或者说, 分时系统是为了满足用户需求所形成的一种新型OS。它与多道批处理系统之间,有着截然不同的性能差别。用户的需求具体表现在以下几个方面: (1) 人—机交互。 (2) 共享主机。 (3) 便于用户上机。
分时系统的关键问题 :最关键的问题是如何使用户能与自己的作业进行交互,即当用户在自己的终端上键入命令时, 系统应能及时接收并及时处理该命令,再将结果返回给用户。 此后, 用户可继续键入下一条命令,此即人—机交互。应强调指出,即使有多个用户同时通过自己的键盘键入命令,系统也应能全部地及时接收并处理。 (1) 及时接收 (2) 及时处理
分时系统的特征 :(1) 多路性 (2) 独立性 (3) 及时性 (4) 交互性
实时系统的类型: 所谓“实时”,是表示“及时”,而实时系统(Real-Time System)是指系统能及时(或即时)响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致地运行。(1) 工业控制系统 (2) 信息查询系统 (3) 多媒体系统 (4) 嵌入式系统
实时任务的类型 : 按任务执行时是否呈现周期性来划分 (1) 周期性实时任务。 (2) 非周期性实时任务。外部设备所发出的激励信号并无明显的周期性,但都必须联系着一个截止时间(Deadline)。它又可分为:① 开始截止时间——任务在某时间以前必须开始执行; ② 完成截止时间——任务在某时间以前必须完成。
并发(concurrence) 共享(sharing) 虚拟(virtual) 异步性(Asynchronism)
并行性和并发性是既相似又有区别的两个概念,并行性是指两个或多个事件在同一时刻发生;而并发性是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内,宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可同时执行。
在操作系统环境下,所谓共享是指系统中的资源可供内存中多个并发执行的进程(线程)共同使用。由于资源属性的不同,进程对资源共享的方式也不同,目前主要有以下两种资源共享方式: 互斥共享和 同时访问
系统中的某些资源,如打印机、磁带机,虽然它们可以提供给多个进程(线程)使用,但为使所打印或记录的结果不致造成混淆,应规定在一段时间内只允许一个进程(线程)访问该资源。为此,当一个进程A要访问某资源时,必须先提出请求, 如果此时该资源空闲,系统便可将之分配给请求进程A使用, 此后若再有其它进程也要访问该资源时(只要A未用完)则必须等待。 仅当A进程访问完并释放该资源后, 才允许另一进程对该资源进行访问。我们把这种资源共享方式称为互斥式共享,而把在一段时间内只允许一个进程访问的资源称为临界critical资源或独占资源。 计算机系统中的大多数物理设备,以及某些软件中所用的栈、变量和表格,都属于临界资源,它们要求被互斥地共享。
系统中还有另一类资源,允许在一段时间内由多个进程“同时”对它们进行访问。这里所谓的“同时”往往是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问。典型的可供多个进程“同时”访问的资源是磁盘设备,一些用重入码编写的文件,也可以被“同时”共享,即若干个用户同时访问该文件。
并发和共享是操作系统的两个最基本的特征,它们又是互为存在的条件。一方面,资源共享是以程序(进程)的并发执行为条件的,若系统不允许程序并发执行,自然不存在资源共享问题;另一方面,若系统不能对资源共享实施有效管理, 协调好诸进程对共享资源的访问,也必然影响到程序并发执行的程度,甚至根本无法并发执行。
操作系统中的所谓“虚拟”,是指通过某种技术把一个物理实体变为若干个逻辑上的对应物。物理实体(前者)是实的, 即实际存在的;而后者是虚的,是用户感觉上的东西。 用于实现虚拟的技术,称为虚拟技术。在OS中利用了多种虚拟技术( 虚拟处理机技术,虚拟存储器技术,虚拟设备技术),分别用来实现虚拟处理机、虚拟内存、 虚拟外部设备和虚拟信道等。
在虚拟处理机技术中,是通过多道程序设计技术,让多道程序并发执行的方法,来分时使用一台处理机的。利用多道程序设计技术,把一台物理上的CPU虚拟为多台逻辑上的CPU,也称为虚拟处理机,我们把用户所感觉到的CPU称为虚拟处理器。
虚拟存储器技术,将一台机器的物理存储器变为虚拟存储器,以便从逻辑上来扩充存储器的容量。此时,虽然物理内存的容量可能不大(如32 MB), 但它可以运行比它大得多的用户程序(如128 MB)。这使用户所感觉到的内存容量比实际内存容量大得多,认为该机器的内存至少也有128 MB。当然这时用户所感觉到的内存容量是虚的。我们把用户所感觉到的存储器称为虚拟存储器。
我们还可以通过虚拟设备技术,将一台物理I/O设备虚拟为多台逻辑上的I/O设备,并允许每个用户占用一台逻辑上的I/O设备,这样便可使原来仅允许在一段时间内由一个用户访问的设备(即临界资源),变为在一段时间内允许多个用户同时访问的共享设备。此外,也可以把一条物理信道虚拟为多条逻辑信道(虚信道)。在操作系统中,虚拟的实现主要是通过分时使用的方法。显然,如果n是某物理设备所对应的虚拟的逻辑设备数,则虚拟设备的平均速度必然是物理设备速度的1/n。
在多道程序环境下,允许多个进程并发执行, 但只有进程在获得所需的资源后方能执行。在单处理机环境下,由于系统中只有一个处理机,因而每次只允许一个进程执行,其余进程只能等待。当正在执行的进程提出某种资源要求时,如打印请求,而此时打印机正在为其它某进程打印,由于打印机属于临界资源,因此正在执行的进程必须等待,且放弃处理机,直到打印机空闲,并再次把处理机分配给该进程时,该进程方能继续执行。可见,由于资源等因素的限制,使进程的执行通常都不是“一气呵成”,而是以“停停走走”的方式运行。
处理机管理功能 存储器管理功能 设备管理功能 文件管理功能
在传统的多道程序环境下,要使作业运行,必须先为它创建一个或几个进程,并为之分配必要的资源。当进程运行结束时,立即撤消该进程,以便能及时回收该进程所占用的各类资源。进程控制的主要功能是为作业创建进程、撤消已结束的进程,以及控制进程在运行过程中的状态转换。 在现代OS中,进程控制还应具有为一个进程创建若干个线程的功能和撤消(终止)已完成任务的线程的功能。
为使多个进程能有条不紊地运行,系统中必须设置进程同步机制。进程同步的主要任务是为多个进程(含线程)的运行进行协调。有两种协调方式:
① 进程互斥方式, 这是指诸进程(线程)在对临界资源进行访问时, 应采用互斥方式;
② 进程同步方式,指在相互合作去完成共同任务的诸进程(线程)间,由同步机构对它们的执行次序加以协调。
为了实现进程同步,系统中必须设置进程同步机制。最简单的用于实现进程互斥的机制,是为每一个临界资源配置一把锁W,当锁打开时,进程(线程)可以对该临界资源进行访问;而当锁关上时,则禁止进程(线程)访问该临界资源。
在多道程序环境下,为了加速应用程序的运行,应在系统中建立多个进程,并且再为一个进程建立若干个线程,由这些进程(线程)相互合作去完成一个共同的任务。而在这些进程(线程)之间,又往往需要交换信息。当相互合作的进程(线程)处于同一计算机系统时,通常在它们之前是采用直接通信方式,即由源进程利用发送命令直接将消息(message)挂到目标进程的消息队列上,以后由目标进程利用接收命令从其消息队列中取出消息。
调度: 在后备队列上等待的每个作业,通常都要经过调度才能执行。在传统的操作系统中,包括作业调度和进程调度两步。作业调度的基本任务,是从后备队列中按照一定的算法,选择出若干个作业,为它们分配其必需的资源(首先是分配内存)。 在将它们调入内存后,便分别为它们建立进程,使它们都成为可能获得处理机的就绪进程,并按照一定的算法将它们插入就绪队列。而进程调度的任务,则是从进程的就绪队列中选出一新进程,把处理机分配给它,并为它设置运行现场, 使进程投入执行。值得提出的是,在多线程OS中,通常是把线程作为独立运行和分配处理机的基本单位,为此,须把就绪线程排成一个队列,每次调度时,是从就绪线程队列中选出一个线程,把处理机分配给它。
OS在实现内存分配时,可采取静态和动态两种方式。 在静态分配方式中,每个作业的内存空间是在作业装入时确定的;在作业装入后的整个运行期间, 不允许该作业再申请新的内存空间,也不允许作业在内存中“移动”; 在动态分配方式中,每个作业所要求的基本内存空间, 也是在装入时确定的,但允许作业在运行过程中,继续申请新的附加内存空间,以适应程序和数据的动态增涨,也允许作业在内存中“移动”。
为了实现内存分配,在内存分配的机制中应具有这样的结构和功能:
① 内存分配数据结构, 该结构用于记录内存空间的使用情况, 作为内存分配的依据;
② 内存分配功能,系统按照一定的内存分配算法, 为用户程序分配内存空间;
③ 内存回收功能,系统对于用户不再需要的内存,通过用户的释放请求,去完成系统的回收功能。
内存保护的主要任务,是确保每道用户程序都只在自己的内存空间内运行,彼此互不干扰。 为了确保每道程序都只在自己的内存区中运行,必须设置内存保护机制。 一种比较简单的内存保护机制,是设置两个界限寄存器,分别用于存放正在执行程序的上界和下界。系统须对每条指令所要访问的地址进行检查,如果发生越界,便发出越界中断请求,以停止该程序的执行。如果这种检查完全用软件实现,则每执行一条指令,便须增加若干条指令去进行越界检查,这将显著降低程序的运行速度。因此,越界检查都由硬件实现。当然, 对发生越界后的处理, 还须与软件配合来完成。
一个应用程序(源程序)经编译后,通常会形成若干个目标程序;这些目标程序再经过链接便形成了可装入程序。这些程序的地址都是从“0”开始的,程序中的其它地址都是相对于起始地址计算的; 由这些地址所形成的地址范围称为“地址空间”, 其中的地址称为“逻辑地址”或“相对地址”。此外,由内存中的一系列单元所限定的地址范围称为“内存空间”, 其中的地址称为“物理地址”。 在多道程序环境下,每道程序不可能都从“0”地址开始装入(内存), 这就致使地址空间内的逻辑地址和内存空间中的物理地址不相一致。使程序能正确运行,存储器管理必须提供地址映射功能,以将地址空间中的逻辑地址转换为内存空间中与之对应的物理地址。该功能应在硬件的支持下完成。
存储器管理中的内存扩充任务,并非是去扩大物理内存的容量,而是借助于虚拟存储技术,从逻辑上去扩充内存容量,使用户所感觉到的内存容量比实际内存容量大得多; 或者是让更多的用户程序能并发运行。这样,既满足了用户的需要,改善了系统的性能,又基本上不增加硬件投资。 为了能在逻辑上扩充内存,系统必须具有内存扩充机制, 用于实现下述各功能:
(1) 请求调入功能
(2) 置换功能
设备管理用于管理计算机系统中所有的外围设备。 设备管理的主要任务是,完成用户进程提出的I/O请求; 为用户进程分配其所需的I/O设备;提高CPU和I/O设备的利用率;提高I/O速度;方便用户使用I/O设备。 为实现上述任务,设备管理应具有缓冲管理、设备分配和设备处理,以及虚拟设备等功能。
CPU运行的高速性和I/O低速性间的矛盾自计算机诞生时起便已存在。 如果在I/O设备和CPU之间引入缓冲,则可有效地缓和CPU和I/O设备速度不匹配的矛盾,提高CPU的利用率,进而提高系统吞吐量。
最常见的缓冲区机制有单缓冲机制、能实现双向同时传送数据的双缓冲机制,以及能供多个设备同时使用的公用缓冲池机制。
设备分配的基本任务,是根据用户进程的I/O请求、系统的现有资源情况以及按照某种设备分配策略,为之分配其所需的设备。如果在I/O设备和CPU之间,还存在着设备控制器和I/O通道时,还须为分配出去的设备分配相应的控制器和通道。
为了实现设备分配,系统中应设置设备控制表、控制器控制表等数据结构,用于记录设备及控制器的标识符和状态。据这些表格可以了解指定设备当前是否可用,是否忙碌,以供进行设备分配时参考。在进行设备分配时,应针对不同的设备类型而采用不同的设备分配方式。对于独占设备(临界资源)的分配,还应考虑到该设备被分配出去后,系统是否安全。 设备使用完后,还应立即由系统回收。
设备处理程序又称为设备驱动程序。其基本任务是用于实现CPU和设备控制器之间的通信,即由CPU向设备控制器发出I/O命令,要求它完成指定的I/O操作;反之由CPU接收从控制器发来的中断请求,并给予迅速的响应和相应的处理。
处理过程是:设备处理程序首先检查I/O请求的合法性,了解设备状态是否是空闲的,了解有关的传递参数及设置设备的工作方式。然后,便向设备控制器发出I/O命令,启动I/O设备去完成指定的I/O操作。设备驱动程序还应能及时响应由控制器发来的中断请求,并根据该中断请求的类型,调用相应的中断处理程序进行处理。对于设置了通道的计算机系统, 设备处理程序还应能根据用户的I/O请求,自动地构成通道程序。
由文件系统对诸多文件及文件的存储空间,实施统一的管理。其主要任务是为每个文件分配必要的外存空间,提高外存的利用率,并能有助于提高文件系统的运行速度。
为此,系统应设置相应的数据结构,用于记录文件存储空间的使用情况,以供分配存储空间时参考;系统还应具有对存储空间进行分配和回收的功能。为了提高存储空间的利用率,对存储空间的分配,通常是采用离散分配方式,以减少外存零头,并以盘块为基本分配单位。盘块的大小通常为512 B~8 KB。
为了使用户能方便地在外存上找到自己所需的文件,通常由系统为每个文件建立一个目录项。目录项包括文件名、文件属性、文件在磁盘上的物理位置等。由若干个目录项又可构成一个目录文件。 目录管理的主要任务, 是为每个文件建立其目录项,并对众多的目录项加以有效的组织,以实现方便的按名存取。即用户只须提供文件名, 即可对该文件进行存取。其次,目录管理还应能实现文件共享,这样,只须在外存上保留一份该共享文件的副本。此外,还应能提供快速的目录查询手段,以提高对文件的检索速度。
(1) 文件的读/写管理。该功能是根据用户的请求,从外存中读取数据;或将数据写入外存。在进行文件读(写)时,系统先根据用户给出的文件名,去检索文件目录,从中获得文件在外存中的位置。然后,利用文件读(写)指针,对文件进行读(写)。一旦读(写)完成,便修改读(写)指针,为下一次读(写)做好准备。由于读和写操作不会同时进行,故可合用一个读/写指针。
(2) 文件保护。① 防止未经核准的用户存取文件; ② 防止冒名顶替存取文件; ③ 防止以不正确的方式使用文件。
(1) 联机用户接口。这是为联机用户提供的,它由一组键盘操作命令及命令解释程序所组成。当用户在终端或控制台上每键入一条命令后,系统便立即转入命令解释程序,对该命令加以解释并执行该命令。在完成指定功能后,控制又返回到终端或控制台上,等待用户键入下一条命令。这样,用户可通过先后键入不同命令的方式,来实现对作业的控制,直至作业完成。
(2) 脱机用户接口。该接口是为批处理作业的用户提供的,故也称为批处理用户接口。该接口由一组作业控制语言JCL组成。批处理作业的用户不能直接与自己的作业交互作用,只能委托系统代替用户对作业进行控制和干预。这里的作业控制语言JCL便是提供给批处理作业用户的、为实现所需功能而委托系统代为控制的一种语言。用户用JCL把需要对作业进行的控制和干预,事先写在作业说明书上,然后将作业连同作业说明书一起提供给系统。当系统调度到该作业运行时,又调用命令解释程序,对作业说明书上的命令,逐条地解释执行。如果作业在执行过程中出现异常现象,系统也将根据作业说明书上的指示进行干预。这样,作业一直在作业说明书的控制下运行,直至遇到作业结束语句时,系统才停止该作业的运行。
该接口是为用户程序在执行中访问系统资源而设置的,是用户程序取得操作系统服务的惟一途径。它是由一组系统调用组成,每一个系统调用都是一个能完成特定功能的子程序,每当应用程序要求OS提供某种服务(功能)时,便调用具有相应功能的系统调用。早期的系统调用都是用汇编语言提供的,只有在用汇编语言书写的程序中,才能直接使用系统调用;但在高级语言以及C语言中,往往提供了与各系统调用一一对应的库函数,这样,应用程序便可通过调用对应的库函数来使用系统调用。但在近几年所推出的操作系统中,如UNIX、OS/2版本中,其系统调用本身已经采用C语言编写,并以函数形式提供,故在用C语言编制的程序中, 可直接使用系统调用。
用户虽然可以通过联机用户接口来取得OS的服务,但这时要求用户能熟记各种命令的名字和格式,并严格按照规定的格式输入命令,这既不方便又花时间,于是,图形用户接口便应运而生。图形用户接口采用了图形化的操作界面, 用非常容易识别的各种图标(icon)来将系统的各项功能、各种应用程序和文件,直观、逼真地表示出来。用户可用鼠标或通过菜单和对话框,来完成对应用程序和文件的操作。此时用户已完全不必像使用命令接口那样去记住命令名及格式,从而把用户从繁琐且单调的操作中解脱出来。
所谓软件,是指当计算机运行时,能提供所要求的功能和性能的指令和程序的集合,该程序能够正确地处理信息的数据结构;作为规范软件,还应具有描述程序功能需求以及程序如何操作使用的文档。如果说,硬件是物理部件, 那么,软件则是一种逻辑部件,它具有与硬件完全不同的特点。
软件工程是指运用系统的、规范的和可定量的方法,来开发、运行和维护软件;或者说,是采用工程的概念、 原理、 技术和方法,来开发与维护软件, 其目的是为了解决在软件开发中所出现的编程随意、软件质量不可保证以及维护困难等问题。
操作系统是一个十分复杂的大型软件。为了控制该软件的复杂性,在开发OS时,先后引入了分解、模块化、 抽象和隐蔽等方法。这里,我们把第一代至第三代的OS结构, 称为传统的OS结构,而把微内核的OS结构称为现代OS结构。
在早期开发操作系统时,设计者只是把他的注意力放在功能的实现和获得高的效率上,缺乏首尾一致的设计思想。 此时的OS是为数众多的一组过程的集合,各过程之间可以相互调用,在操作系统内部不存在任何结构,因此,这种OS是无结构的,也有人把它称为整体系统结构。
此时程序设计的技巧,只是如何编制紧凑的程序,以便于有效地利用内存、对GOTO语句的使用不加任何限制,所设计出的操作系统既庞大又杂乱,缺乏清晰的程序结构。这一方面会使所编制出的程序错误很多,给调试工作带来很多困难;另一方面也使程序难以阅读和理解,增加了维护人员的负担。
1) 模块化结构
模块化程序设计技术,是最早(20世纪60年代)出现的一种程序设计技术。该技术是基于“分解”和“模块化”原则来控制大型软件的复杂度的。为使OS具有较清晰的结构,OS不再是由众多的过程直接构成,而是将OS按其功能划分为若干个具有一定独立性和大小的模块。每个模块具有某方面的管理功能,如进程管理模块、存储器管理模块、I/O设备管理模块和文件管理模块等,并规定好各模块间的接口, 使各模块之间能通过该接口实现交互,然后再进一步将各模块细分为若干个具有一定管理功能的子模块,如把进程管理模块又分为进程控制、 进程同步、 进程通信和进程调度等子模块, 同样也要规定各子模块之间的接口。若子模块较大时,再进一步将它细分。
2) 模块化OS的优缺点
(1)提高了OS设计的正确性、 可理解性和可维护性。
(2) 增强了OS的可适应性。
(3) 加速了OS的开发过程。
模块化结构设计的缺点有二:1.在开始设计OS时,对模块的划分及对接口的规定并不精确, 而且还可能存在错误,因而很难保证按此规定所设计出的模块会完全正确, 这将使在把这些模块装配成OS时发生困难;2.从功能观点来划分模块时,未能将共享资源和独占资源加以区别; 由于管理上的差异,又会使模块间存在着复杂的依赖关系使OS结构变得不清晰。
1) 有序分层的基本概念
从改进设计方式上说,应使我们的每一步设计都是建立在可靠的基础上。我们可以从物理机器开始, 在其上面先添加一层具有一定功能的软件A1, 由于A1是建立在完全确定的物理机器上的,在经过精心设计和几乎是穷尽无遗的测试后,可以认为A1是正确的;然后再在A1上添加一层新软件A2,……,如此一层一层地自底向上增添软件层,每一层都实现若干功能,这样,一旦发现Ai出现错误时,通常该错误只会局限于Ai,因为它与所有其高层的软件无关,而Ai层以下的各层软件,又都经过仔细的调试,最后总能构成一个能满足需要的OS。
2) 层次的设置
(1) 程序嵌套。 通常OS的每个功能的实现, 并非是只用一个程序便能完成的,而是要经由若干个软件层才有可能完成。因此在划分OS层次时,首先要考虑在实现OS 的每个功能时所形成的程序嵌套。例如,作业调度模块须调用进程控制模块;在为某作业创建一进程时,进程控制模块又须调用内存管理模块为新进程分配内存空间,可见,进程控制模块应在内存管理模块之上; 而作业调度模块又应在更高层。
(2) 运行频率。在分层结构中,各层次软件的运行速度是不同的,因为A1层软件能直接在物理机器上运行, 故它有最高的运行速度。随着层次的增高,其相应软件的运行速度就随之下降,因而An层软件的运行速度最低。 为了提高OS的运行效率,应该将那些经常活跃的模块放在最接近硬件的A1层,如时钟管理、进程调度,通常都放在A1层。
(3) 公用模块。应把供多种资源管程程序调用的公用模块, 设置在最低层,不然,会使比它低的层次模块由于无法调用它而须另外配置相应功能的模块。例如,用于对信号量进行操作的原语Signal和Wait。
(4) 用户接口。为方便用户(程序),OS向用户提供了“用户与OS的接口”,如命令接口、程序接口以及图形用户接口。这些接口应设置在OS的最高层,直接提供给用户使用。
1) 基本概念
为了提高OS的灵活性和可扩充性而将OS划分为两部分, 一部分是用于提供各种服务的一组服务器(进程),如用于提供进程管理的进程服务器、提供存储器管理的存储器、服务器提供文件管理的文件服务器等,所有这些服务器(进程)都运行在用户态。 当有一用户进程(现在称为客户进程)要求读文件的一个盘块时,该进程便向文件服务器(进程)发出一个请求;当服务器完成了该客户的请求后,便给该客户回送一个响应。 操作系统的另一部分是内核,用来处理客户和服务器之间的通信, 即由内核来接收客户的请求,再将该请求送至相应的服务器;同时它也接收服务器的应答, 并将此应答回送给请求客户。 此外,在内核中还应具有其它一些机构,用于实现与硬件紧密相关的和一些较基本的功能。
2) 客户/服务器模式的优点
(1)提高了系统的灵活性和可扩充性。
(2) 提高了OS的可靠性。
(3) 可运行于分布式系统中。
1) 面向对象技术的基本概念
面向对象技术是基于“抽象”和“隐蔽”原则来控制大型软件的复杂度的。所谓对象,是指在现实世界中具有相同属性、服从相同规则的一系列事物的抽象,而把其中的具体事物称为对象的实例。OS中的各类实体如进程、线程、消息、存储器等,都使用了对象这一概念,相应地,便有进程对象、线程对象、 存储器对象等。
2) 面向对象技术的优点
(1) 可修改性和可扩充性。由于隐蔽了表示实体的数据和操作,因而可以改变对象的表示而不会影响其它部分, 从而可以方便地改变老的对象和增加新的对象。
(2) 继承性。继承性是面向对象技术所具有的重要特性。继承性是指子对象可以继承父对象的属性,这样,在创建一个新的对象时, 便可减少大量的时空开销。
(3) 正确性和可靠性。由于对象是构成操作系统的基本单元,可以独立地对它进行测试,这样,比较易于保证其正确性和可靠性,从而比较容易保证整个系统的正确性和可靠性。
(1) 微内核技术的引入
所谓微内核技术,是指精心设计的、能实现现代OS核心功能的小型内核,它与一般的OS(程序)不同, 它更小更精炼,它不仅运行在核心态,而且开机后常驻内存, 它不会因内存紧张而被换出内存。微内核并非是一个完整的OS, 而只是为构建通用OS提供一个重要基础。由于在微内核OS结构中,通常都采用了客户/服务器模式,因此OS的大部分功能和服务,都是由若干服务器来提供的, 如文件服务器、作业服务器和网络服务器等。
2) 微内核的基本功能
微内核所提供的功能,通常都是一些最基本的功能:
(1) 进程管理。
(2) 存储器管理。
(3) 进程通信管理。
(4) I/O设备管理。
在早期未配置OS的系统和单道批处理系统中,程序的执行方式是顺序执行,这种方式具有浪费资源、系统运行效率低等缺点。
前趋图(Precedence Graph)是一个有向无环图,记为DAG(Directed Acyclic Graph),用于描述进程之间执行的前后关系。
1)图中的每个结点可用于描述一个程序段或进程,乃至一条语句;
2)结点间的有向边则用于表示两个结点之间存在的偏序(Partial Order,亦称偏序关系)或前趋关系(Precedence Relation)“→”。
→={(Pi,Pj)|Pi must complete before Pj may start},如果(Pi,Pj)∈→,可写成Pi→Pj,称Pi是Pj的直接前趋,而称Pj是Pi的直接后继。
在前趋图中,把没有前趋的结点称为初始结点(Initial Node),把没有后继的结点称为终止结点(Final Node)。此外,每个结点还具有一个重量(Weight),用于表示该结点所含有的程序量或结点的执行时间。
对于上图所示的前趋图,存在下述前趋关系:
P1→P2,P1→P3,P1→P4,P2→P5,P3→P5,P4→P6,P4→P7,P5→P8,P6→P8,P7→P9,P8→P9
或表示为: P={P1,P2,P3,P4,P5,P6,P7,P8,P9} →={(P1,P2),(P1,P3),(P1,P4),(P2,P5),(P3,P5),(P4,P6),(P4,P7),(P5,P8),(P6,P8),(P7,P9),(P8,P9)}
应当注意,前趋图中必须不存在循环,但在图(b)中却有着下述的前趋关系:S2→S3,S3→S2
前驱图练习:
请画出下面语句的前趋关系
题:(已知x,y,z是常量) S1:a=x+y+1 S2:b=a+z S3:c=1 S4:d=b+c S5:e=3*c S6:f=d++ S7:g= a+b+c+d+e+f
解:
(先找出初始结点,然后按结点下标从小到大画图)
(为了避免遗漏,可按下标数字从小到大作为直接前趋,依次写出前趋关系)
描述:S={S1,S2,S3,S4,S5, S6 , S7} →={(S1,S2),(S2,S4),(S3,S4),(S3,S5),(S4,S6),(S5,S7),(S6,S7)}
通常可以把一个应用程序分成若干个程序段,在各程序段之间,必须按照某种先后次序顺序执行,仅当前一操作(程序段)执行完后,才能执行后继操作。 例如,在进行计算时,总须先输入用户的程序和数据,然后进行计算,最后才能打印计算结果。
(1) 顺序性:处理机的操作严格按照程序所规定的顺序执行,即每一操作必须在上一个操作结束之后开始。
(2) 封闭性:程序是在封闭的环境下执行的,即程序运行时独占全机资源,资源的状态(除初始状态外)只有本程序才能改变它。程序一旦开始执行,其执行结果不受外界因素影响。
(3) 可再现性:只要程序执行时的环境和初始条件相同,当程序重复执行时,不论它是从头到尾不停顿地执行,还是“停停走走”地执行,都将获得相同的结果。 程序顺序执行时的特性,为程序员检测和校正程序的错误带来了很大的方便。
输入程序、计算程序和打印程序三者之间,存在着Ii→Ci→Pi这样的前趋关系,以至对一个作业的输入、计算和打印三个操作,必须顺序执行,但并不存在Pi→Ii+1的关系,因而在对一批程序进行处理时,可使它们并发执行。
例如,输入程序在输入第一个程序后,在计算程序对该程序进行计算的同时,可由输入程序再输入第二个程序,从而使第一个程序的计算操作可与第二个程序的输入操作并发执行。一般来说,输入程序在输入第i+1个程序时,计算程序可能正在对第i个程序进行计算,而打印程序正在打印第i-1个程序的计算结果。下图示出了输入、计算和打印这三个程序对一批作业进行处理的情况。
1)间断性
程序在并发执行时,由于它们共享系统资源,以及为完成同一项任务而相互合作,致使在这些并发执行的程序之间,形成了相互制约的关系。简而言之,相互制约将导致并发程序具有“执行—暂停—执行”这种间断性的活动规律。
2) 失去封闭性
程序在并发执行时,是多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行失去了封闭性。这样,某程序在执行时,可能会受到其它程序的影响。例如,当处理机这一资源已被某个程序占有时,另一程序必须等待。
3) 不可再现性
程序在并发执行时,由于失去了封闭性,也将导致其再失去可再现性。
例如,有两个循环程序A和B,它们共享一个变量N。 程序A每执行一次时,都要做N:=N+1操作; 程序B每执行一次时,都要执行Print(N)操作,然后再将N置成“0”。 程序A和B以不同的速度运行。 这样,可能出现三种情况(假定某时刻变量N的值为n)。程序在并发执行时,由于失去了封闭性,其计算结果与并发程序的执行速度有关,从而使程序的执行失去了可再现性,亦即,程序经过多次执行后,虽然它们执行时的环境和初始条件相同,但得到的结果却各不相同。
在多道程序环境下,程序的执行属于并发执行,此时它们将失去其封闭性,并具有间断性及不可再现性的特征。这决定了通常的程序是不能参与并发执行的,因为程序执行的结果是不可再现的。这样,程序的运行也就失去了意义。
通常的程序是不能并发执行的。为使程序(含数据)能独立运行,应为之配置一进程控制块,即PCB(Process Control Block);而由程序段、相关的数据段和PCB三部分便构成了进程实体。在早期的UNIX版本中,把这三部分总称为“进程映像”。值得指出的是,在许多情况下所说的进程,实际上是指进程实体,例如,所谓创建进程,实质上是创建进程实体中的PCB;而撤消进程,实质上是撤消进程的PCB。
对于进程的定义,从不同的角度可以有不同的定义,其中较典型的定义有: (1) 进程是程序的一次执行。 (2) 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。 (3) 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。 进程的定义:进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
进程的实质是进程实体的一次执行过程,因此,动态性是进程的最基本的特征。 动态性还表现在:“它由创建而产生,由调度而执行,由撤消而消亡”。 可见, 进程实体有一定的生命期, 而程序则只是一组有序指令的集合,并存放于某种介质上,其本身并不具有运动的含义,因而是静态的。
这是指多个进程实体同存于内存中,且能在一段时间内同时运行。并发性是进程的重要特征,同时也成为OS的重要特征。引入进程的目的也正是为了使其进程实体能和其它进程实体并发执行;而程序(没有建立PCB)是不能并发执行的。
在传统的OS中,独立性是指进程实体是一个能独立运行、独立分配资源和独立接受调度的基本单位。凡未建立PCB的程序都不能作为一个独立的单位参与运行。
这是指进程按各自独立的、 不可预知的速度向前推进,或说进程实体按异步方式运行。
进程执行时的间断性决定了进程可能具有多种状态。事实上,运行中的进程可能具有以下三种基本状态。
1) 就绪(Ready)状态
当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行,进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。
2) 执行状态
进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。
3) 阻塞状态
正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。 致使进程阻塞的典型事件有:请求I/O,申请缓冲空间等。通常将这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而把处于阻塞状态的进程排成多个队列。
进程在运行过程中会经常发生状态的转换。例如,处于就绪状态的进程,在调度程序为之分配了处理机之后便可执行,相应地,其状态就由就绪态转变为执行态;正在执行的进程(当前进程)如果因分配给它的时间片已完而被剥夺处理机暂停执行时,其状态便由执行转为就绪;如果因发生某事件,致使当前进程的执行受阻(例如进程访问某临界资源,而该资源正被其它进程访问时),使之无法继续执行,则该进程状态将由执行转变为阻塞。图2-5示出了进程的三种基本状态,以及各状态之间的转换关系。
1) 创建状态
如前所述,进程是由创建而产生。创建一个进程是个很复杂的过程,一般要通过多个步骤才能完成:如首先由进程申请一个空白PCB,并向PCB中填写用于控制和管理进程的信息;然后为该进程分配运行时所必须的资源;最后,把该进程转入就绪状态并插入就绪队列之中。但如果进程所需的资源尚不能得到满足,比如系统尚无足够的内存使进程无法装入其中,此时创建工作尚未完成,进程不能被调度运行,于是把此时进程所处的状态称为创建状态。
2) 终止状态
进程的终止也要通过两个步骤:首先,是等待操作系统进行善后处理,最后将其PCB清零,并将PCB空间返还系统。当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态。进入终止态的进程以后不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其他进程收集。一旦其他进程完成了对其信息的提取之后,操作系统将删除该进程,即将其PCB清零,并将该空白PCB返还系统。
在不少系统中进程只有上述三种状态,但在另一些系统中,又增加了一些新状态,最a重要的是挂起状态。引入挂起状态的原因有:
(1) 终端用户的请求。当终端用户在自己的程序运行期间发现有可疑问题时,希望暂时使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态称为挂起状态。
(2) 父进程请求。有时父进程希望挂起自己的某个子进程,以便考查和修改该子进程,或者协调各子进程间的活动。
(3) 负荷调节的需要。当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
(4) 操作系统的需要。操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。
在引入挂起状态后,又将增加从挂起状态(又称为静止状态)到非挂起状态(又称为活动状态)的转换;或者相反。可有以下几种情况:
(1) 活动就绪→静止就绪。当进程处于未被挂起的就绪状态时,称此为活动就绪状态,表示为Readya。当用挂起原语Suspend将该进程挂起后,该进程便转变为静止就绪状态,表示为Readys,处于Readys状态的进程不再被调度执行。
(2) 活动阻塞→静止阻塞。当进程处于未被挂起的阻塞状态时,称它是处于活动阻塞状态,表示为Blockeda。当用Suspend原语将它挂起后,进程便转变为静止阻塞状态,表示为Blockeds。处于该状态的进程在其所期待的事件出现后,将从静止阻塞变为静止就绪。
(3) 静止就绪→活动就绪。处于Readys状态的进程,若用激活原语Active激活后,该进程将转变为Readya状态。
(4) 静止阻塞→活动阻塞。处于Blockeds状态的进程,若用激活原语Active激活后,该进程将转变为Blockeda状态
在进程状态转换时,与第一幅图所示的进程五状态转换相比较,要增加考虑下面的几种情况:
(1) NULL→创建:一个新进程产生时,该进程处于创建状态。
(2) 创建→活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创建的必要操作后,相应的系统进程将进程的状态转换为活动就绪状态。
(3) 创建→静止就绪:考虑到系统当前资源状况和性能要求,并不分配给新建进程所需资源,主要是主存资源,相应的系统进程将进程状态转为静止就绪状态此时进程创建工作尚未完成。
(4) 执行→终止:当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,进程即进终止状态。
在计算机系统中,对于每个资源和每个进程都设置了一个数据结构,用于表征其实体,我们称之为资源信息表或进程信息表,其中包含了资源或进程的标识、描述、状态等信息以及一批指针。通过这些指针,可以将同类资源或进程的信息表,或者同一进程所占用的资源信息表分类链接成不同的队列,便于操作系统进行查找。
如下图所示,OS管理的这些数据结构一般分为以下四类:内存表、设备表、文件表和用于进程管理的进程表,通常进程表又被称为进程控制块PCB。
为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录型数据结构。PCB中记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
进程控制块的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。
可见,在进程的整个生命期中,系统总是通过PCB对进程进行控制的,亦即,系统是根据进程的PCB而不是任何别的什么而感知到该进程的存在的。所以说,PCB是进程存在的惟一标志。
当系统创建一个新进程时,就为它建立了一个PCB;进程结束时又回收其PCB,进程于是也随之消亡。 PCB可以被操作系统中的多个模块读或修改,如被调度程序、资源分配程序、中断处理程序以及监督和分析程序等读或修改。 因为PCB经常被系统访问,尤其是被运行频率很高的进程及分派程序访问,故PCB应常驻内存。系统将所有的PCB组织成若干个链表(或队列),存放在操作系统中专门开辟的PCB区内。
(1) 作为独立运行基本单位的标志。
(2) 能实现间断性运行方式。
(3) 提供进程管理所需要的信息。
(4) 提供进程调度所需要的信息。
(5) 实现与其它进程的同步与通信。
1) 进程标识符
进程标识符用于惟一地标识一个进程。一个进程通常有两种标识符: (1) 内部标识符。在所有的操作系统中,都为每一个进程赋予了一个惟一的数字标识符,它通常是一个进程的序号。设置内部标识符主要是为了方便系统使用。 (2) 外部标识符。它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。为了描述进程的家族关系,还应设置父进程标识及子进程标识。此外,还可设置用户标识,以指示拥有该进程的用户。
2) 处理机状态
处理机状态信息主要是由处理机的各种寄存器中的内容组成的。处理机在运行时,许多信息都放在寄存器中。当处理机被中断时,所有这些信息都必须保存在PCB中,以便在该进程重新执行时,能从断点继续执行。这些寄存器包括:① 通用寄存器,又称为用户可视寄存器,它们是用户程序可以访问的,用于暂存信息,在大多数处理机中,有 8~32个通用寄存器,在RISC结构的计算机中可超过100个; ② 指令计数器,其中存放了要访问的下一条指令的地址; ③ 程序状态字PSW,其中含有状态信息,如条件码、执行方式、中断屏蔽标志等;④ 用户栈指针,指每个用户进程都有一个或若干个与之相关的系统栈,用于存放过程和系统调用参数及调用地址,栈指针指向该栈栈顶。
3) 进程调度信息
在PCB中还存放一些与进程调度和进程对换有关的信息,包括: ① 进程状态,指明进程的当前状态,作为进程调度和对换时的依据; ② 进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级高的进程应优先获得处理机; ③ 进程调度所需的其它信息,它们与所采用的进程调度算法有关,比如,进程已等待CPU的时间总和、进程已执行的时间总和等; ④ 事件,指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
4) 进程控制信息
进程控制信息包括: ① 程序和数据的地址,指进程的程序和数据所在的内存或外存地(首)址,以便再调度到该进程执行时,能从PCB中找到其程序和数据;② 进程同步和通信机制,指实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等,它们可能全部或部分地放在PCB中; ③ 资源清单,即一张列出了除CPU以外的、进程所需的全部资源及已经分配到该进程的资源的清单; ④ 链接指针,它给出了本进程(PCB)所在队列中的下一个进程的PCB的首地址。
在一个系统中,通常可拥有数十个、数百个乃至数千个PCB。为了能对它们加以有效的管理,应该用适当的方式将这些PCB组织起来。目前常用的组织方式有以下三种。
(1) 线性方式,即将系统中所有的PCB都组织在一张线性表中,将该表的首址存放在内存的一个专用区域中。该方式实现简单、开销小,但每次查找时都需要扫描整张表,因此适合进程数目不多的系统。
(2) 链接方式,即把具有相同状态进程的PCB分别通过PCB中的链接字链接成一个队列。这样,可以形成就绪队列、若干个阻塞队列和空白队列等。对就绪队列而言,往往按进程的优先级将PCB从高到低进行排列,将优先级高的进程PCB排在队列的前面。同样,也可把处于阻塞状态进程的PCB根据其阻塞原因的不同,排成多个阻塞队列,如等待I/O操作完成的队列和等待分配内存的队列等。
(3) 索引方式,即系统根据所有进程状态的不同,建立几张索引表,例如,就绪索引表、阻塞索引表等,并把各索引表在内存的首地址记录在内存的一些专用单元中。在每个索引表的表目中,记录具有相应状态的某个PCB在PCB表中的地址。
进程控制是进程管理中最基本的功能,主要包括创建新进程、终止已完成的进程、将因发生异常情况而无法继续运行的进程置于阻塞状态、负责进程运行中的状态转换等功能。如当一个正在执行的进程因等待某事件而暂时不能继续执行时,将其转变为阻塞状态,而在该进程所期待的事件出现后,又将该进程转换为就绪状态等。进程控制一般是由OS的内核中的原语来实现的。
处理机的执行状态分为两种
系统态(核心态、管态): 有特权, 能执行所有指令, 能访问所有寄存器和存储区。
用户态(目态): 无特权, 只能执行规定指令,只能访问指定的寄存器和存储区。
用户程序运行在用户态,它不能执行OS指令不能访问OS区域,防止对OS的破坏。OS内核运行在系统态。进程控制包含在OS的内核中,它常驻内存,执行效率高。
1. 支撑功能 (1) 中断处理。 (2) 时钟管理。 (3) 原语操作。
原语(Primitive)是由若干条指令组成的,用于完成一定功能的一个过程。它与一般过程的区别在于:它们是“原子操作(Action Operation)”。所谓原子操作,是指一个操作中的所有动作要么全做,要么全不做。换言之,它是一个不可分割的基本单位,因此,在执行过程中不允许被中断。原子操作在管态下执行,常驻内存。
2. 资源管理功能 (1) 进程管理。 (2) 存储器管理。 (3) 设备管理。
在OS中,允许一个进程创建另一个进程,通常把创建进程的进程称为父进程,而把被创建的进程称为子进程。子进程可继续创建更多的孙进程,由此便形成了一个进程的层次结构。如在UNIX中,进程与其子孙进程共同组成一个进程家族(组)。
为了形象地描述一个进程的家族关系而引入了进程图(Process Graph)。所谓进程图就是用于描述进程间关系的一棵有向树。
在多道程序环境中,只有(作为)进程(时)才能在系统中运行。因此,为使程序能运行,就必须为它创建进程。导致一个进程去创建另一个进程的典型事件,可有以下四类:
(1) 用户登录。在分时系统中,用户在终端键入登录命令后,如果是合法用户,系统将为该终端建立一个进程,并把它插入就绪队列中。
(2) 作业调度。在批处理系统中,当作业调度程序按一定的算法调度到某作业时,便将该作业装入内存,为它分配必要的资源,并立即为它创建进程,再插入就绪队列中。
(3) 提供服务。当运行中的用户程序提出某种请求后,系统将专门创建一个进程来提供用户所需要的服务。例如文件打印。
在上述三种情况下,都是由系统内核为它创建一个新进程;
(4) 应用请求。基于应用进程的需求,由它自己创建一个新进程,以便使新进程以并发运行方式完成特定任务。例如,某应用程序需要不断地从键盘终端输入数据,继而又要对输入数据进行相应的处理,然后,再将处理结果以表格形式在屏幕上显示。该应用进程为使这几个操作能并发执行,以加速任务的完成,可以分别建立键盘输入进程、表格输出进程。
一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Creat( )按下述步骤创建一个新进程。
(1) 申请空白PCB。为新进程申请获得惟一的数字标识符,并从PCB集合中索取一个空白PCB。
(2) 为新进程分配资源。为新进程的程序和数据以及用户栈分配必要的内存空间。显然,此时操作系统必须知道新进程所需内存的大小
(3) 初始化进程控制块。PCB的初始化包括: ① 初始化标识信息,将系统分配的标识符和父进程标识符填入新PCB中; ② 初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶; ③ 初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显式方式提出高优先级要求。
(4) 将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。
1) 正常结束
在任何计算机系统中,都应有一个用于表示进程已经运行完成的指示。 例如,在批处理系统中,通常在程序的最后安排一条Holt指令或终止的系统调用。当程序运行到Holt指令时,将产生一个中断,去通知OS本进程已经完成。在分时系统中,用户可利用Logs off去表示进程运行完毕,此时同样可产生一个中断,去通知OS进程已运行完毕。
2) 异常结束
在进程运行期间,由于出现某些错误和故障而迫使进程终止(Termination of Process)。这类异常事件很多,常见的有下述几种: (1) 越界错误。这是指程序所访问的存储区已越出该进程的区域。 (2) 保护错。这是指进程试图去访问一个不允许访问的资源或文件,或者以不适当的方式进行访问,例如,进程试图去写一个只读文件。 (3) 非法指令。这是指程序试图去执行一条不存在的指令。出现该错误的原因,可能是程序错误地转移到数据区,把数据当成了指令。 (4) 特权指令错。这是指用户进程试图去执行一条只允许OS执行的指令。 (5) 运行超时。这是指进程的执行时间超过了指定的最大值。 (6) 等待超时。这是指进程等待某事件的时间超过了规定的最大值。 (7) 算术运算错。这是指进程试图去执行一个被禁止的运算,例如被0除。(8) I/O故障。这是指在I/O过程中发生了错误等。
3) 外界干预
外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。这些干预有: (1) 操作员或操作系统干预。由于某种原因,例如,发生了死锁,由操作员或操作系统终止该进程。 (2) 父进程请求。由于父进程具有终止自己的任何子孙进程的权力,因而当父进程提出请求时,系统将终止该进程。 (3) 父进程终止。当父进程终止时,OS也将它的所有子孙进程终止。
如果系统中发生了上述要求终止进程的某事件,OS便调用进程终止原语,按下述过程去终止指定的进程。
(1) 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态。
(2) 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度。
(3) 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防它们成为不可控的进程。
(4) 将被终止进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
(5) 将被终止进程(PCB)从所在队列(或链表)中移出,等待其他程序来搜集信息。
1) 请求系统服务
当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求时,该进程只能转变为阻塞状态来等待。
2) 启动某种操作
当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成。
3) 新数据尚未到达
对于相互合作的进程,如果其中一个进程需要先获得另一(合作)进程提供的数据后才能对数据进行处理,则只要其所需数据尚未到达,该进程只有(等待)阻塞。
4) 无新工作可做
系统往往设置一些具有某特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来。
正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻塞原语block把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。 进入block过程后, 由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的状态由“执行”改为“阻塞”,并将PCB插入阻塞队列。 如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。 最后,转调度程序进行重新调度,将处理机分配给另一就绪进程并进行切换,亦即,保留被阻塞进程的处理机状态(在PCB中),再按新进程的PCB中的处理机状态设置CPU的环境。
当被阻塞进程所期待的事件出现时,如I/O完成或其所期待的数据已经到达,则由有关进程(比如用完并释放了该I/O设备的进程)调用唤醒原语wakeup( ),将等待该事件的进程唤醒。 唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。 应当指出,block原语和wakeup原语是一对作用刚好相反的原语。因此,如果在某进程中调用了阻塞原语,则必须在与之相合作的另一进程中或其他相关的进程中安排唤醒原语,以能唤醒阻塞进程;否则,被阻塞进程将会因不能被唤醒而长久地处于阻塞状态,从而再无机会继续运行。
当出现了引起进程挂起的事件时,比如,用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起,系统将利用挂起原语suspend( )将指定进程或处于阻塞状态的进程挂起。 挂起原语的执行过程是: 首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪; 对于活动阻塞状态的进程,则将之改为静止阻塞。 为了方便用户或父进程考查该进程的运行情况而把该进程的PCB复制到某指定的内存区域。 最后,若被挂起的进程正在执行,则转向调度程序重新调度其他进程。
当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的该进程换入内存。这时,系统将利用激活原语active( )将指定进程激活。激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞,便将之改为活动阻塞。 假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较, 如果被激活进程的优先级更低,就不必重新调度; 否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。
在OS中引入进程后,一方面可以使系统中的多道程序并发执行,这不仅能有效地改善资源利用率,还可显著地提高系统的吞吐量,但另一方面却使系统变得更加复杂。如果不能采取有效的措施,对多个进程的运行进行妥善的管理,必然会因为这些进程对系统资源的无序争夺给系统造成混乱。致使每次处理的结果存在着不确定性,即显现出其不可再现性。
在多道程序环境下,当程序并发执行时,由于资源共享和进程合作,使同处于一个系统中的诸进程之间可能存在着以下两种形式的制约关系。
(1) 间接相互制约关系。同处于一个系统中的进程,通常都共享着某种系统资源,如共享CPU、共享I/O设备等。所谓间接相互制约即源于这种资源共享。例如,有两个进程A和B,如果在A进程提出打印请求时,系统已将惟一的一台打印机分配给了进程B,则此时进程A只能阻塞;一旦进程B将打印机释放,则A进程才能由阻塞改为就绪状态。
(2) 直接相互制约关系。这种制约主要源于进程间的合作。 例如,有一输入进程A通过单缓冲向进程B提供数据。 当该缓冲空时,计算进程因不能获得所需数据而阻塞,而当进程A把数据输入缓冲区后,便将进程B唤醒; 反之,当缓冲区已满时,进程A因不能再向缓冲区投放数据而阻塞,当进程B将缓冲区数据取走后便可唤醒A。
许多硬件资源如打印机、磁带机等,都属于临界资源(Critical Resource),诸进程间应采取互斥方式,实现对这种资源的共享。 定义:一段时间内只允许一个进程访问的资源,如打印机,扫描仪等。 使用临界资源的著名进程同步问题包括: 生产者-消费者(Producer-consumer)问题、 哲学家进餐(The Dinning Philosophers)问题 、读者-写者(Reader-Writer)问题
由前所述可知,不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。人们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。 显然,若能保证诸进程互斥地进入自己的临界区,便可实现诸进程对临界资源的互斥访问。为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。 如果此刻该临界资源未被访问,进程便可进入临界区对该资源进行访问,并设置它正被访问的标志; 如果此刻该临界资源正被某进程访问,则本进程不能进入临界区。
因此,必须在临界区前面增加一段用于进行上述检查的代码,把这段代码称为进入区(entry section)。相应地,在临界区后面也要加上一段称为退出区(exit section)的代码,用于将临界区正被访问的标志恢复为未被访问的标志。进程中除上述进入区、临界区及退出区之外的其它部分的代码,在这里都称为剩余区。这样,可把一个访问临界资源的循环进程描述如下:
repeat
entry section 进入区
critical section; 临界区
exit section 退出区
remainder section;
until false;
(1) 空闲让进。当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效地利用临界资源。 (2) 忙则等待。当已有进程进入临界区时,表明临界资源正在被访问,因而其它试图进入临界区的进程必须等待,以保证对临界资源的互斥访问。 (3) 有限等待。对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”状态。 (4) 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。
虽然可以利用软件方法解决诸进程互斥进入临界区的问题,但有一定难度,并且存在很大的局限性,因而现在已很少采用。相应地,目前许多计算机已提供了一些特殊的硬件指令,允许对一个字中的内容进行检测和修正,或者是对两个字的内容进行交换等。可利用这些特殊的指令来解决临界区问题。
关中断是实现互斥的最简单的方法之一。在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样,进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会发生进程或线程切换。由此,保证了对锁的测试和关锁操作的连续性和完整性,有效地保证了互斥。但是,关中断的方法存在许多缺点:① 滥用关中断权力可能导致严重后果;② 关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力;③ 关中断方法也不适用于多CPU 系统,因为在一个处理器上关中断并不能防止进程在其它处理器上执行相同的临界段代码。
这是一种借助一条硬件指令——“测试并建立”指令TS(Test-and-Set)以实现互斥的方法。在许多计算机中都提供了这种指令。
该指令称为对换指令,在Intel 80x86中又称为XCHG指令,用于交换两个字的内容。
最初由荷兰科学家Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。很长时间以来,这两个操作一直被分别称为P、V操作。
在整型信号量机制中的wait操作,只要是信号量S≤0,就会不断地测试。因此,整型信号量机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针L,用于链接上述的所有等待进程。
在记录型信号量机制中,S.value的初值表示系统中某类资源的数目,因而又称为资源信号量。 对记录型信号量的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为S.value:=S.value-1; 当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入到信号量链表S.L中。 可见,该机制遵循了“让权等待”准则。此时S.value的绝对值表示在该信号量链表中已阻塞进程的数目。
对记录型信号量的每次signal操作,表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故S.value:=S.value+1操作表示资源数目加1。 若加1后仍是S.value≤0,则表示在该信号量链表中,仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S.L链表中的第一个等待进程唤醒。 如果S.value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程互斥。
上述的进程互斥问题,是针对各进程之间只共享一个临界资源而言的。在有些应用场合,是一个进程需要先获得两个或更多的共享资源后方能执行其任务。 假定现有两个进程A和B,他们都要求访问共享数据D和E。当然,共享数据都应作为临界资源。为此,可为这两个数据分别设置用于互斥的信号量Dmutex和Emutex,并令它们的初值都是1。相应地,在两个进程中都要包含两个对Dmutex和Emutex的操作,即
AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。 亦即,对若干个临界资源的分配,采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配。由死锁理论可知,这样就可避免上述死锁情况的发生。为此,在wait操作中,增加了一个“AND”条件,故称为AND同步,或称为同时wait操作,即Swait(Simultaneous wait)定义如下:
在记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能获得或释放一个单位的临界资源。而当一次需要N个某类临界资源时,便要进行N次wait(S)操作,显然这是低效的。 此外,在有些情况下,当资源数量低于某一下限值时,便不予以分配。因而,在每次分配之前,都必须测试该资源的数量,看其是否大于其下限值。
下面我们讨论一般“信号量集”的几种特殊情况: (1) Swait(S,d,d)。此时在信号量集中只有一个信号量S,但允许它每次申请d个资源,当现有资源数少于d时,不予分配。 (2) Swait(S,1,1)。此时的信号量集已蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。 (3) Swait(S,1,0)。这是一种很特殊且很有用的信号量操作。当S≥1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
为使多个进程能互斥地访问某临界资源,只须为该资源设置一互斥信号量mutex,并设其初始值为1,然后将各进程访问该资源的临界区CS置于wait(mutex)和signal(mutex)操作之间即可.这样,每个欲访问该临界资源的进程在进入临界区之前,都要先对mutex执行wait操作,若该资源此刻未被访问,本次wait操作必然成功,进程便可进入自己的临界区,这时若再有其他进程也欲进入自己的临界区,此时由于对mutex执行wait操作定会失败,因而该进程阻塞,从而保证了该临界资源能被互斥地访问。当访问临界资源的进程退出临界区后,又应对mutex执行signal操作,以便释放该临界资源。利用信号量实现进程互斥的进程可描述如下:
还可利用信号量来描述程序或语句之间的前趋关系。设有两个并发执行的进程P1和P2。P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2。为实现这种前趋关系,我们只须使进程P1和P2共享一个公用信号量S,并赋予其初值为0,将signal(S)操作放在语句S1后面;而在S2语句前面插入wait(S)操作,即 在进程P1中,用S1;signal(S); 在进程P2中,用wait(S);S2;
由于S被初始化为0,这样,若P2先执行必定阻塞,只有在进程P1执行完S1;signal(S);操作后使S增为1时,P2进程方能执行语句S2成功。同样,我们可以利用信号量,按照语句间的前趋关系(见图2-12),写出一个更为复杂的可并发执行的程序。 图2-12示出了一个前趋图,其中S1,S2,S3,…,S6是最简单的程序段(只有一条语句)。为使各程序段能正确执行,应设置若干个初始值为“0”的信号量。如为保证S1→S2,S1→S3的前趋关系,应分别设置信号量a 和b,同样,为了保证S2→S4,S2→S5,S3→S6,S4→S6和S5→S6,应设置信号量c,d,e,f,g。
系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。进程对共享资源的申请、释放和其它操作,都是通过这组过程对共享数据结构的操作来实现的,这组过程还可以根据资源的情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。 Hansan为管程所下的【定义】:一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
由定义可知,管程由四部分组成: ① 管程的名称; ② 局部于管程内部的共享数据结构说明; ③ 对该数据结构进行操作的一组过程; ④ 对局部于管程内部的共享数据设置初始值的语句。
需要指出的是,局部于管程内部的数据结构,仅能被局部于管程内部的过程所访问,任何管程外的过程都不能访问它;反之,局部于管程内部的过程也仅能访问管程内的数据结构。 由此可见,管程相当于围墙,它把共享变量和对它进行操作的若干过程围了起来,所有进程要访问临界资源时,都必须经过管程(相当于通过围墙的门)才能进入,而管程每次只准许一个进程进入管程,从而实现了进程互斥。
管程是一种程序设计语言结构成分,它和信号量有同等的表达能力,从语言的角度看,管程主要有以下特性: (1) 模块化。管程是一个基本程序单位,可以单独编译。 (2) 抽象数据类型。管程中不仅有数据,而且有对数据的操作。 (3) 信息掩蔽。管程中的数据结构只能被管程中的过程访问,这些过程也是在管程内部定义的,供管程外的进程调用,而管程中的数据结构以及过程(函数)的具体实现外部不可见
管程和进程不同,主要体现在以下几个方面: (1) 虽然二者都定义了数据结构,但进程定义的是私有数据结构PCB,管程定义的是公共数据结构,如消息队列等; (2) 二者都存在对各自数据结构上的操作,但进程是由顺序程序执行有关的操作,而管程主要是进行同步操作和初始化操作; (3) 设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题;(4) 进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程则为主动工作方式; (5) 进程之间能并发执行,而管程则不能与其调用者并发; (6) 进程具有动态性,由“创建”而诞生,由“撤销”而消亡,而管程则是操作系统中的一个资源管理模块,供进程调用。
在利用管程实现进程同步时,必须设置同步工具,如两个同步操作原语wait和signal。当某进程通过管程请求获得临界资源而未能满足时,管程便调用wait原语使该进程等待,并将其排在等待队列上,如图2-13所示。仅当另一进程访问完成并释放该资源之后,管程才又调用signal原语,唤醒等待队列中的队首进程。
问题描述:一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为了使它们能并发进行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它生产的产品放入缓冲区中;消费者进程从一个缓冲区中取走产品去消费,它们以异步方式运行,但保持同步,即不允许消费者从空缓冲区取走产品消费,也不允许生产者向满缓冲区中投放产品。
在生产者—消费者问题中应注意:
首先,在每个程序中用于实现互斥的wait(mutex)和signal(mutex)必须成对地出现;
其次,对资源信号量empty和full的wait和signal操作,同样需要成对地出现,但它们分别处于不同的程序中。例如,wait(empty)在生产者进程中,而signal(empty)则在消费者进程中,生产者进程若因执行wait(empty)而阻塞,则以后将由消费者进程将它唤醒;
最后,在每个程序中的多个wait操作顺序不能颠倒,应先执行对资源信号量的wait操作,然后再执行对互斥信号量的wait操作,否则可能引起进程死锁。
五个哲学家共用一张圆桌,分别作在周围的五张椅子上,在圆桌上有五个碗和五个筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时试图取其左右的筷子,只有拿到两只筷子时才能进餐,进餐完毕,放下筷子继续思考。
哲学家进餐问题 经分析可知,放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组。其描述如下: Var chopstick: array[0,…,4] of semaphore;
所有信号量均被初始化为1,第i位哲学家的活动可描述为:
repeat
think;
wait(chopstick[i]);
wait(chopstick[(i+1) mod 5]);
eat;
signal(chopstick[i]);
signal(chopstick[(i+1) mod 5]);
until false;
在以上描述中,当哲学家饥饿时,总是先去拿他左边的筷子,即执行wait(chopstick[i]); 成功后,再去拿他右边的筷子,即执行wait(chopstick[(i+1) mod 5]);又成功后便可进餐。进餐完毕,又先放下他左边的筷子,然后再放右边的筷子。 虽然上述解法可保证不会有两个相邻的哲学家同时进餐,但有可能引起死锁。假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0; 当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待。对于这样的死锁问题,可采取以下几种解决方法:
(1) 至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。
(2) 仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。
(3) 规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则相反。按此规定,将是0、1号哲学家竞争1号筷子;2、3号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。
哲学家进餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐,这在本质上就是前面所介绍的AND同步问题,故用AND信号量机制可获得最简洁的解法。描述如下:
Var chopstick array of semaphore:=(1,1,1,1,1);
processi
repeat
think;
Sswait(chopstick[(i+1) mod 5],chopstick[i]);
eat;
Ssignal(chopstick[(i+1) mod 5],chopstick[i]);
until false;
描述:一个数据文件或者记录,可被多个进程共享,把只要求读文件的进程称作“Reader”进程,其他进程则称为“Writer进程”。允许多个进程同时读一个共享对象,因为读操作不会使数据文件混乱。但不允许Writer进程和其他Reader进程或者Writer进程同时访问共享对象以保证进程互斥访问共享对象,实现进程同步。
读者和写者之间的同步:
读者读数据 VS 读者读数据
读者读数据 VS 写者写数据
写者写数据 VS 读者读数据
写者写数据 VS 写者写数据
为实现Reader与Writer进程间在读或写时的互斥而设置了一个互斥信号量Wmutex。 另外,再设置一个整型变量Readcount表示正在读的进程数目。由于只要有一个Reader进程在读,便不允许Writer进程去写。 因此,仅当Readcount=0,表示尚无Reader进程在读时,Reader进程才需要执行Wait(wmutex)操作。 若Wait(wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。 同理,仅当Reader进程在执行了Readcount减1操作后其值为0时,才须执行signal(wmutex)操作,以便让Writer进程写。 又因为Readcount是一个可被多个Reader进程访问的临界资源,因此,也应该为它设置一个互斥信号量rmutex。
读者—写者问题可描述如下:
Var rmutex,wmutex: semaphore:=1,1;
readcount: integer:=0;
begin
parbegin
Writer
Reader
parend
end
这里的读者—写者问题与前面的略有不同,它增加了一个限制,即最多只允许RN个读者同时读。为此,又引入了一个信号量L,并赋予其初值为RN,通过执行wait(L,1,1)操作,来控制读者的数目。每当有一个读者进入时,就要先执行wait(L,1,1)操作,使L的值减1。当有RN个读者进入读后,L便减为0,第RN+1个读者要进入读时,必然会因wait(L,1,1)操作失败而阻塞。对利用信号量集来解决读者—写者问题的描述如下:
进程通信是指进程之间的信息交换。由于进程的互斥与同步,需要在进程间交换一定的信息,但只能把它们称为低级进程通信。我们以信号量机制为例来说明,它们之所以低级的原因在于:① 效率低,生产者每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;② 通信对用户不透明,OS只为进程之间的通信提供了共享存储器。
在进程之间要传送大量数据时,应当利用OS提供的高级通信工具,该工具最主要的特点是: (1) 使用方便。OS隐藏了实现进程通信的具体细节,向用户提供了一组用于实现高级通信的命令(原语),用户可方便地直接利用它实现进程之间的通信。或者说,通信过程对用户是透明的。这样就大大减少了通信程序编制上的复杂性。 (2) 高效地传送大量数据。用户可直接利用高级通信命令(原语)高效地传送大量的数据。
(1) 基于共享数据结构的通信方式。在这种通信方式中,要求诸进程公用某些数据结构,借以实现诸进程间的信息交换。如在生产者—消费者问题中,就是用有界缓冲区这种数据结构来实现通信的。这里,公用数据结构的设置及对进程间同步的处理,都是程序员的职责。这无疑增加了程序员的负担,而操作系统却只须提供共享存储器。因此,这种通信方式是低效的,只适于传递相对少量的数据。
(2) 基于共享存储区的通信方式。为了传输大量数据,在存储器中划出了一块共享存储区,诸进程可通过对共享存储区中数据的读或写来实现通信。这种通信方式属于高级通信。 进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将该分区的描述符返回给申请者,继之,由申请者把获得的共享存储分区连接到本进程上;此后,便可像读、写普通存储器一样地读、写该公用存储分区。
【定义】所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。 由于发送进程和接收进程是利用管道进行通信的,故又称为管道通信。这种方式首创于UNIX系统,由于它能有效地传送大量数据,因而又被引入到许多其它的操作系统中。
为了协调双方的通信,管道机制必须提供以下三方面的协调能力: (1) 互斥,即当一个进程正在对pipe执行读/写操作时,其它(另一)进程必须等待。 (2) 同步,指当写(输入)进程把一定数量(如4KB)的数据写入pipe,便去睡眠等待,直到读(输出)进程取走数据后,再把它唤醒。当读进程读一空pipe时,也应睡眠等待,直至写进程将数据写入管道后,才将之唤醒。 (3) 确定对方是否存在,只有确定了对方已存在时,才能进行通信。
消息传递系统(Message passing system)是当前应用最为广泛的一种进程间的通信机制。在该机制中,进程间的数据交换是以格式化的消息(message)为单位的; 在计算机网络中,又把message称为报文。程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且还隐藏了通信的实现细节,使通信过程对用户是透明的,从而大大减化了通信程序编制的复杂性,因而获得了广泛的应用. 消息传递系统的通信方式属于高级通信方式。又因其实现方式的不同而进一步分成直接通信方式和间接通信方式两种。
1) 套接字(Socket)
套接字起源于20世纪70年代加州大学伯克利分校版本的UNIX(即BSD Unix),是UNIX 操作系统下的网络通信接口。一开始,套接字被设计用在同一台主机上多个应用程序之间的通信(即进程间的通信),主要是为了解决多对进程同时通信时端口和物理线路的多路复用问题。随着计算机网络技术的发展以及UNIX 操作系统的广泛使用,套接字已逐渐成为最流行的网络通信程序接口之一。
2) 远程过程调用和远程方法调用
远程过程(函数)调用RPC(Remote Procedure Call),是一个通信协议,用于通过网络连接的系统。该协议允许运行于一台主机(本地)系统上的进程调用另一台主机(远程)系统上的进程,而对程序员表现为常规的过程调用,无需额外地为此编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称做远程方法调用。
实际上,远程过程调用的主要步骤是: (1) 本地过程调用者以一般方式调用远程过程在本地关联的客户存根,传递相应的参数,然后将控制权转移给客户存根; (2) 客户存根执行,完成包括过程名和调用参数等信息的消息建立,将控制权转移给本地客户进程; (3) 本地客户进程完成与服务器的消息传递,将消息发送到远程服务器进程; (4) 远程服务器进程接收消息后转入执行,并根据其中的远程过程名找到对应的服务器存根,将消息转给该存根;(5) 该服务器存根接到消息后,由阻塞状态转入执行状态,拆开消息从中取出过程调用的参数,然后以一般方式调用服务器上关联的过程; (6) 在服务器端的远程过程运行完毕后,将结果返回给与之关联的服务器存根; (7) 该服务器存根获得控制权运行,将结果打包为消息,并将控制权转移给远程服务器进程; (8) 远程服务器进程将消息发送回客户端; (9) 本地客户进程接收到消息后,根据其中的过程名将消息存入关联的客户存根,再将控制权转移给客户存根; (10) 客户存根从消息中取出结果,返回给本地调用者进程,并完成控制权的转移。
在直接通信方式中,源进程可直接将消息发送给目标进程,此类操作系统通常提供send(receiver, message)和receive(sender,message)两条通信命令(原语)供用户使用。
在间接通信方式中,进程间需要通过某种中间实体(即信箱)来进行通信。发送进程将消息投入信箱,而接受进程则从信箱中取得消息。因此,它不仅能实现实时通信,还能实现非实时通信。此时,操作系统应提供若干条原语,分别用于信箱的创建、撤销和消息的发送、接收等。
1) 直接通信原语
(1) 对称寻址方式。
(2) 非对称寻址方式。
这是指发送进程利用OS所提供的发送命令,直接把消息发送给目标进程。此时,要求发送进程和接收进程都以显式方式提供对方的标识符。通常,系统提供下述两条通信命令
(原语): Send(Receiver,message); 发送一个消息给接收进程;
Receive(Sender,message); 接收Sender发来的消息;
例如,原语Send(P2,m1)表示将消息m1发送给接收进程
2) 消息的格式
在消息传递系统中所传递的消息,必须具有一定的消息格式。在单机系统环境中,由于发送进程和接收进程处于同一台机器中,有着相同的环境,所以消息的格式比较简单,可采用比较短的定长消息格式,以减少对消息的处理和存储开销。该方式可用于办公自动化系统中,为用户提供快速的便笺式通信。但这种方式对于需要发送较长消息的用户是不方便的。为此,可采用变长的消息格式,即进程所发送消息的长度是可变的。对于变长消息,系统无论在处理方面还是存储方面,都可能会付出更多的开销,但其优点在于方便了用户。
3) 进程的同步方式
在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程还是接收进程,在完成消息的发送或接收后,都存在两种可能性,即进程或者继续发送(或接收)或者阻塞。由此,我们可得到以下三种情况: (1) 发送进程阻塞,接收进程阻塞。这种情况主要用于进程之间紧密同步(tight synchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合(rendezrous)。(2) 发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标; 而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒。 (3) 发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。
4) 通信链路
为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路。有两种方式建立通信链路。第一种方式是:由发送进程在通信之前用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路,在链路使用完后拆除链路。 为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路(communication link)。有两种方式建立通信链路。 第一种方式是由发送进程在通信之前用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路;在链路使用完后,也用显式方式拆除链路。这种方式主要用于计算机网络中。 第二种方式是发送进程无须明确提出建立链路的请求,只须利用系统提供的发送命令(原语),系统会自动地为之建立一条链路。这种方式主要用于单机系统中。
间接通信方式指进程之间的通信需要通过作为共享数据结构的实体。该实体用来暂存发送进程发送给目标进程的消息;接收进程则从该实体中取出对方发送给自己的消息。通常把这种中间实体称为信箱。消息在信箱中可以安全地保存,只允许核准的目标用户随时读取。因此,利用信箱通信方式,既可实现实时通信,又可实现非实时通信。 系统为信箱通信提供了若干条原语,分别用于信箱的创建、撤消和消息的发送、接收等。
(1) 信箱的创建和撤消。进程可利用信箱创建原语来建立一个新信箱。创建者进程应给出信箱名字、信箱属性(公用、私用或共享);对于共享信箱,还应给出共享者的名字。当进程不再需要读信箱时,可用信箱撤消原语将之撤消。
(2) 消息的发送和接收。当进程之间要利用信箱进行通信时,必须使用共享信箱,并利用系统提供的下述通信原语进行通信:
Send(mailbox,message); 将一个消息发送到指定信箱;
Receive(mailbox,message); 从指定信箱中接收一个消息;
3) 信箱的类型
信箱可由操作系统创建,也可由用户进程创建,创建者是信箱的拥有者。据此,可把信箱分为以下三类。 (1) 私用信箱 用户进程可为自己建立一个新信箱,并作为该进程的一部分。信箱的拥有者有权从信箱中读取消息,其他用户则只能将自己构成的消息发送到该信箱中。这种私用信箱可采用单向通信链路的信箱来实现。当拥有该信箱的进程结束时,信箱也随之消失。(2) 公用信箱 它由操作系统创建,并提供给系统中的所有核准进程使用。核准进程既可把消息发送到该信箱中,也可从信箱中读取发送给自己的消息。显然,公用信箱应采用双向通信链路的信箱来实现。通常,公用信箱在系统运行期间始终存在。 (3) 共享信箱 它由某进程创建,在创建时和创建后指明它是共享的,同时指出共享进程(用户)的名字。
利用信箱通信时,发送进程和接收进程之间存在以下四种关系: (1)一对一关系。发送进程和接收进程建立一条两者专用的通信链路。 (2)多对一关系:允许提供服务的进程和多个接收进程进行交互。 (3) 一对多关系。允许一个发送进程与多个接收进程进行交互,使发送进程可用广播方式向接收者(多个)发送消息。 (4) 多对多关系。允许建立一个公用信箱,让多个进程都能向信箱中投递消息;也可从信箱中取走属于自己的消息。
消息缓冲队列通信机制首先由美国的Hansan提出,在这种通信机制中,发送进程利用Send原语将消息直接发送给接收进程;接收进程则利用Receive原语接收消息。
(1) 消息缓冲区。
(2) PCB中有关通信的数据项。
在消息缓冲队列通信方式中,主要利用的数据结构是消息缓冲区。它可描述如下:
type message buffer=record
sender ;发送者进程标识符
size ; 消息长度
text ; 消息正文
next ; 指向下一个消息缓冲区的指针
end
此外,进程的PCB块中增加一些数据项以支持消息缓冲区的通信机制实现; 如:mq,消息链首指针;mutex,消息链互斥信号量;Sm,消息链同步计数信号量
在操作系统中采用了消息缓冲队列通信机制时,除了需要为进程设置消息缓冲队列外,还应在进程的PCB中增加消息队列队首指针,用于对消息队列进行操作,以及用于实现同步的互斥信号量mutex和资源信号量sm。在PCB中应增加的数据项可描述如下
type process control block=record
mq ; 消息队列队首指针
mutex ; 消息队列互斥信号量
sm ; 消息队列资源信号量
end
发送进程在利用发送原语发送消息之前,应先在自己的内存空间设置一发送区a,把待发送的消息正文、发送进程标识符、消息长度等信息填入其中,然后调用发送原语,把消息发送给目标(接收)进程。发送原语首先根据发送区a中所设置的消息长度a.size来申请一缓冲区i,接着,把发送区a中的信息复制到缓冲区i中。为了能将i挂在接收进程的消息队列mq上,应先获得接收进程的内部标识符j,然后将i挂在j.mq上。由于该队列属于临界资源,故在执行insert操作的前后都要执行wait和signal操作。
接收进程调用接收原语receive(b),从自己的消息缓冲队列mq中摘下第一个消息缓冲区i,并将其中的数据复制到以b为首址的指定消息接收区内
如果说,在操作系统中引入进程的目的,是为了使多个程序能并发执行,以提高资源利用率和系统吞吐量,那么,在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性。为了说明这一点,我们首先来回顾进程的两个基本属性: ① 进程是一个可拥有资源的独立单位;② 进程同时又是一个可独立调度和分派的基本单位。正是由于进程有这两个基本属性,才使之成为一个能独立运行的基本单位,从而也就构成了进程并发执行的基础。然而,为使程序能并发执行,系统还必须进行以下的一系列操作。
为使程序能并发执行,系统必须进行以下的一系列操作: (1) 创建进程,系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O设备,以及建立相应的PCB; (2) 撤消进程,系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消PCB; (3) 进程切换,对进程进行上下文切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境,因而须花费不少的处理机时间。
如何能使多个程序更好地并发执行,同时又尽量减少系统的开销,已成为近年来设计操作系统时所追求的重要目标。有不少研究操作系统的学者们想到,要设法将进程的上述两个属性分开,由OS分开处理,亦即并不把作为调度和分派的基本单位也同时作为拥有资源的单位,以做到“轻装上阵”;而对于拥有资源的基本单位,又不对之施以频繁的切换。正是在这种思想的指导下,形成了线程的概念。
1) 调度的基本单位
在传统的操作系统中,作为拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位,把传统进程的两个属性分开,使线程基本上不拥有资源,这样线程便能轻装前进,从而可显著地提高系统的并发程度。在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。
2) 并发性
在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,使得操作系统具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。
例如,在一个未引入线程的单CPU操作系统中,若仅设置一个文件服务进程,当该进程由于某种原因而被阻塞时,便没有其它的文件服务进程来提供服务。在引入线程的操作系统中,则可以在一个文件服务进程中设置多个服务线程。当第一个线程等待时,文件服务进程中的第二个线程可以继续运行,以提供文件服务;当第二个线程阻塞时,则可由第三个继续执行,提供服务。显然,这样的方法可以显著地提高文件服务的质量和系统的吞吐量。
3) 拥有资源
不论是传统的操作系统,还是引入了线程的操作系统,进程都可以拥有资源,是系统中拥有资源的一个基本单位。一般而言,线程自己不拥有系统资源(也有一点必不可少的资源),但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源,如已打开的文件、I/O设备等,可以供该进程中的所有线程所共享。
4) 独立性
在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低得多。 这是因为,为防止进程之间彼此干扰和破坏,每个进程都拥有一个独立的地址空间和其他资源,除了共享全局变量外,不允许其他进程的访问。
5) 系统开销
在创建或撤消进程时,系统都要为之创建和回收进程控制块,分配或回收资源,如内存空间和I/O设备等,操作系统所付出的开销明显大于线程创建或撤消时的开销。
类似地,在进程切换时,涉及到当前进程CPU环境的保存及新被调度运行进程的CPU环境的设置,而线程的切换则仅需保存和设置少量寄存器内容,不涉及存储器管理方面的操作,所以就切换代价而言,进程也是远高于线程的。 此外,由于一个进程中的多个线程具有相同的地址空间,在同步和通信的实现方面线程也比进程容易。在一些操作系统中,线程的切换、同步和通信都无须操作系统内核的干预。
6) 支持多处理机系统
在多处理机系统中,对于传统的进程,即单线程进程,不管有多少个处理机,该进程只能运行在一个处理机上。但对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,这无疑将加速进程的完成。
与传统的进程一样,在各线程之间也存在着共享资源和相互合作的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下述三种基本状态: (1) 执行状态,表示线程已获得处理机而正在运行; (2) 就绪状态,指线程已具备了各种执行条件,只须再获得CPU便可立即执行; (3) 阻塞状态,指线程在执行中因某事件受阻而处于暂停状态,例如,当一个线程执行从键盘读入数据的系统调用时,该线程就被阻塞。
如同每个进程有一个进程控制块一样,系统也为每个线程配置了一个线程控制块TCB,将所有用于控制和管理线程的信息记录在线程控制块中。
在多线程OS中,进程是作为拥有系统资源的基本单位,通常的进程都包含多个线程并为它们提供资源,但此时的进程就不再作为一个执行的实体。多线程OS中的进程有以下属性:
(1) 作为系统资源分配的单位。在多线程OS中,仍是将进程作为系统资源分配的基本单位,在任一进程中所拥有的资源包括受到分别保护的用户地址空间、用于实现进程间和线程间同步和通信的机制、已打开的文件和已申请到的I/O设备,以及一张由核心进程维护的地址映射表,该表用于实现用户程序的逻辑地址到其内存物理地址的映射。
(2) 可包括多个线程。通常,一个进程都含有多个相对独立的线程,其数目可多可少,但至少也要有一个线程,由进程为这些(个)线程提供资源及运行环境,使这些线程可并发执行。在OS中的所有线程都只能属于某一个特定进程。
(3) 进程不是一个可执行的实体。在多线程OS中,是把线程作为独立运行的基本单位,所以此时的进程已不再是一个可执行的实体。虽然如此,进程仍具有与执行相关的状态。例如,所谓进程处于“执行”状态,实际上是指该进程中的某线程正在执行。此外,对进程所施加的与进程状态有关的操作,也对其线程起作用。例如,在把某个进程挂起时,该进程中的所有线程也都将被挂起;又如,在把某进程激活时,属于该进程的所有线程也都将被激活。
线程已在许多系统中实现,但各系统的实现方式并不完全相同。在有的系统中,特别是一些数据库管理系统,如infomix所实现的是用户级线程; 而另一些系统(如Macintosh和OS/2操作系统)所实现的是内核支持线程;还有一些系统如Solaris操作系统,则同时实现了这两种类型的线程。
在OS中的所有进程,无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,是与内核紧密相关的。而内核支持线程KST同样也是在内核的支持下运行的,它们的创建、阻塞、撤消和切换等,也都是在内核空间实现的。为了对内核线程进行控制和管理,在内核空间也为每一个内核线程设置了一个线程控制块,内核根据该控制块而感知某线程的存在,并对其加以控制。当前大多数OS都支持内核支持线程。
这种线程实现方式主要有四个主要优点: (1) 在多处理器系统中,内核能够同时调度同一进程中的多个线程并行执行; (2) 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程; (3) 内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小; (4) 内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
用户级线程是在用户空间中实现的。对线程的创建、 撤消、同步与通信等功能,都无需内核的支持,即用户级线程是与内核无关的。在一个系统中的用户级线程的数目可以达到数百个至数千个。由于这些线程的任务控制块都是设置在用户空间,而线程所执行的操作也无需内核的帮助,因而内核完全不知道用户级线程的存在。
使用用户级线程方式有许多优点: (1) 线程切换不需要转换到内核空间。 (2) 调度算法可以是进程专用的。 (3) 用户级线程的实现与OS平台无关,因为对于线程管理的代码是属于用户程序的一部分,所有的应用程序都可以对之进行共享.
而用户级线程方式的主要缺点则在于: (1) 系统调用的阻塞问题。在基于进程机制的OS中,大多数系统调用将使进程阻塞,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞。而在内核支持线程方式中,则进程中的其它线程仍然可以运行。 (2) 在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点,内核每次分配给一个进程的仅有一个CPU,因此,进程中仅有一个线程能执行,在该线程放弃CPU之前,其它线程只能等待。
有些OS把用户级线程和内核支持线程两种方式进行组合,提供了组合方式ULT/KST 线程。在组合方式线程系统中,内核支持多个内核支持线程的建立、调度和管理,同时,也允许用户应用程序建立、调度和管理用户级线程。
在仅设置了内核支持线程的OS中,一种可能的线程控制方法是,系统在创建一个新进程时,便为它分配一个任务数据区PTDA(Per Task Data Area),其中包括若干个线程控制块TCB空间
2) 内核控制线程
这种线程又称为轻型进程LWP(Light Weight Process)。每一个进程都可拥有多个LWP,同用户级线程一样,每个LWP都有自己的数据结构(如TCB),其中包括线程标识符、优先级、状态,另外还有栈和局部存储区等。LWP也可以共享进程所拥有的资源。LWP可通过系统调用来获得内核提供的服务,这样,当一个用户级线程运行时,只须将它连接到一个LWP上,此时它便具有了内核支持线程的所有属性。这种线程实现方式就是组合方式。
应用程序在启动时,通常仅有一个线程在执行,人们把线程称为“初始化线程”,它的主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小,以及用于调度的优先级等。在线程的创建函数执行完后,将返回一个线程标识符供以后使用。
当一个线程完成了自己的任务(工作)后,或是线程在运行中出现异常情况而须被强行终止时,由终止线程通过调用相应的函数(或系统调用)对它执行终止操作。但有些线程(主要是系统线程),它们一旦被建立起来之后,便一直运行下去而不被终止。在大多数的OS中,线程被中止后并不立即释放它所占有的资源,只有当进程中的其它线程执行了分离函数后,被终止的线程才与资源分离,此时的资源才能被其它线程利用。