CH2 处理器管理
处理器管理 是操作系统的重要组成部分,它负责管理,调度和分派计算机系统的重要资源处理器,并控制程序的执行 。 由于处理器管理是操作系统中最内核的组成部分,任何程序的执行都必须真正占有处理器,因此处理器管理直接影响系统的性能 。
2.1 中央处理器
2.1.1 单处理器系统和多处理器系统目前计算机系统可以分作以下四类:
l单指令流单数据流 ( SISD),一个处理器在一个存储器中的数据上执行单条指令流 。
l单指令流多数据流 ( SIMD),单条指令流控制多个处理单元同时执行,每个处理单元包括处理器和相关的数据存储,一条指令事实上控制了不同的处理器对不同的数据进行了操作 。
向量机和阵列机是这类计算机系统的代表 。
l多指令流单数据流 ( MISD),一个数据流被传送给一组处理器,通过这一组处理器上的不同指令操作最终得到处理结果 。
l多指令流多数据流 ( MIMD),多个处理器对各自不同的数据集同时执行不同的指令流 。
紧密耦合 MIMD系统可以分为主从式系统和对称式系统 (SMP)两类。
主从式系统的基本思想是:在一个特别的处理器上运行操作系统内核,其他处理器上则运行用户程序和操作系统例行程序,内核负责分配和调度各个处理器,
并向其它程序提供各种服务 ( 如输入输出 ) 。 这种方式实现简单,但是主处理器的崩溃会导致整个系统的崩溃,并且极可能在主处理器形成性能瓶颈 。
对称式多处理器系统 ( SMP) 中,操作系统内核可以运行在任意一个处理器上,每个处理器都可以自我调度运行的进程和线程,并且操作系统内核也被设计成多进程或多线程,内核的各个部分可以并行执行 。
对称多处理机 (Symmetric Multiprocessor,SMP)是迄今开发出的最成功的并行机,有一种 SMP机最多可支持 64个处理器,多个处理器之间采用共享主存储器 。 SMP机有对称性,单一地址空间,低通信延迟和一致的高速缓存等特点,具有高可靠性,可扩充性,易伸缩性 。 这一系统中任何 CPU可访问任何存储单元及 I/O设备; CPU间通信代价很低,而并行度较高;由于共享存储器中只要保存一个操作系统和数据库副本,既有利于动态负载平衡,又有利于保证数据的完整性和一致性 。 Dec Alpha
Server,HP9000/T600,IBMRS600/40,Sun Ultra
Enterprise 6000,SGI Power Challenge XL都 `是 SMP机,
主要用于在线数据服务,数据库和数据仓库等应用 。
2.1.2 寄存器
l通用寄存器 可由程序设计者指定许多功能,如存放操作数或用作寻址寄存器 。
l数据寄存器 它们作为内存数据的高速缓存,可以被系统程序和用户程序直接使用并进行计算 。 用以存放操作数 。
l 地址寄存器 用于指明内存地址 。 如索引寄存器,段寄存器 ( 基址 /限长 ),堆栈指针寄存器等等 。
lI/O地址寄存器 用于指定 I/O设备 。
lI/O缓冲寄存器 用于处理器和 I/O设备交换数据 。
l 控制寄存器 用于存放处理器的控制和状态信息,包括程序计数器和指令寄存器,中断寄存器以及用于存储器和 I/O模块控制的寄存器也属于这一类 。 此外还有:
存放将被访问的存储单元地址的存储器地址寄存器和存放从存储器读出或欲写入的数据的存储器数据寄存器 。
l程序状态字寄存器也属于 CPU 。
2.1.3 机器指令计算机机器指令的集合称指令系统,它反映了一台机器的功能和处理能力,可以分为以下四类:
l 数据处理类指令:用于执行算术和逻辑运算 。
l 控制类指令:如转移,用于改变执行指令序列 。
l 寄存器数据交换类指令:用于在处理器的寄存器和存储器之间交换数据 。
l I/O类指令:用于启动外围设备,让主存和设备交换数据 。
2.1.4 特权指令
在多道程序设计环境中,从资源管理和控制程序执行的角度出发,把指令系统中的指令分作两 类,
特权指令 (Privileged Instructions)和非特权指令 。
所谓特权指令是指那些只能在特态下才能正常执行的,提供给操作系统的核心程序使用的指令,
如启动输入输出设备,设置时钟,控制中断屏蔽位,清内存,建立存储键,加载 PSW,…,等 。
一般用户在目态下运行,只能执行非特权指令,
否则会导致非法执行特权指令而产生中断 。 只有操作系统才能执行全部指令 ( 特权指令和非特权指令 ) 。
2.1.5 处理器状态
处理器状态又称为处理器的运行模式,
有些系统把处理器状态划分为核心状态,管理状态和用户状态,而大多数系统把处理器状态简单的划分为管理状态 ( 又称特权状态,系统模式,特态或管态 ) 和用户状态 ( 又称目标状态,用户模式,常态或目态 ) 。
当处理器处于管理状态时,可以执行全部指令,使用所有资源,并具有改变处理器状态的能力;当处理器处于用户状态时,只能执行非特权指令 。 没有硬件支持的多运行模式会引起系统严重后果,如 MS-DOS
是为 Intel8088结构配的操作系统,它没有双模式,可能发生用户把数据写到操作系统区,或几个用户同时使用一台设备 。
PDP系列计算机具有两个处理器状态,用户态和核心态 。
Pentium的处理器状态有四种,支持 4个保护级别,
0级权限最高,3级权限最 `低 。 一种典型的应用是把 4个保护级别依次设定为:
l 0级为操作系统内核级 。 处理 I/O,存储管理,
和其他关键操作 。
l 1级为系统调用处理程序级 。 用户程序可以通过调用这里的过程执行系统调用,但是只有一些特定的和受保护的过程可以被调用 。
l 2级为共享库过程级 。 它可以被很多正在运行的程序共享,用户程序可以调用这些过程,都去它们的数据,但是不能修改它们 。
l 3级为用户程序级 。 它受到的保护最少 。
下面两类情况会导致从用户态向核心态转换,
一是程序请求操作系统服务,执行一条系统调用;
二是程序运行时,产生了一个中断事件,中断处理程序进行工作 。
Unix系统上进程执行在两个状态之下:用户态和核心态
用户态下的进程执行一个系统调用时,进程的执行态从用户态变为核心态,由操作系统执行并试图为用户的请求服务 。
Unix两个处理器状态之间的差别是:用户态下的进程能存取自己的指令和数据,但不能存取内核指令和数据,也不能存取其它进程的指令和数据 。 然而,核心态下的进程能够存取内核和用户地址 。
用户态下的进程不能执行特权指令,这是系统用以保证安全性的措施之一 。 注意到内核是为用户进程工作的,它不是与用户进程平行运行的软件,而是作为用户进程的一部分 。
例如,Shell通过系统调用读用户终端,
在执行读操作时,由用户态转入核心态 。 于是,正在为该 Shell进程执行的内核软件对终端的操作进行控制,并把键入的字符返回给 Shell,此时,处理器又从核心态转回用户态,然后,
Shell在用户态下继续执行,对字符流作分析和解释完成规定的操作,而解释执行过程中又允许请求引用其它系统调用 。
2.1.6 程序状态字寄存器
程序状态字 PSW是 CPU中的特殊寄存器,用来控制指令的执行顺序并且保留和指示与程序有关的系统状态的集合 。 它的主要作用是方便地实现程序状态的保护和恢复 。 一般来说,程序状态字寄存器包括以下几类内容:
l 程序基本状态 。 包括,1) 程序计数器:指明下一条执行的指令地址; 2) 条件码:表示指令执行的结果状态; 3) 处理器状态位:指明当前的处理器状态,如目态或管态,运行或等待 。
l中断码 。 保存程序执行时当前发生的中断事件 。
l中断屏蔽位 。 指明程序执行中发生中断事件时,
是否响应出现的中断事件 。
IBM360/370系列计算机的程序状态字基本格式如图 2-1所示:
8位系统屏蔽
XX X X XXXX X X XXXXXX
4位 CMWP字段 4位程序屏蔽
4位保护键 16位中断码字段指令长和条件码
24位指令地址图 2-1 IBM 370系统程序状态字的其本控制格式
l 系统屏蔽位 8位 (0-7位 ) 0-7依次为通道 0-6和外中断屏蔽位 。
l 保护键 4位 (6-11位 ) 当没有设置存储器保护时,该 4位为 0当设置存储器保护时,PSW中的这四位保护键与欲访问的存储区的存储键相匹配,否则指令不能执行 。
l CMWP位 (12-15位 ) 依次为 PSW基本
/扩充控制方式位,开 /关中断位,运行
/等待位,目态 /特态位 。
l中断码 16位 中断码字段与中断事件对应,
记录当前产生的中断源 。
l 指令长度字段 2位 (32-33位 01/10/11分别表示半字长指令,整字长指令和一字半长指令 。
l条件码 2位 (34-35位 )
l 程序屏蔽 4位 (36-39位 ) 表示允许 (为 1) 或禁止 (为 0) 程序性中断,自左向右各位体对应的程序性事件是:定点溢出,十进溢出,
阶 下 溢,39 位备用 。
l 指令地址 24位 (40-63位 )
Pentium的程序状态字由标志寄存器
EFLAGS和指令指针寄存器 EIP组成标志可划分为三组:
状态标志算术运算指令使用 OF(溢出标志 ),SF(符号标志 ),ZF(结果为零标志 ),AF(辅助进位标志 ),CF(进位标志 ),PF(奇偶校验标志 );串扫描,串比较,循环指令使用 ZF通知其操作结束 。
控制标志
DF(方向标志 )控制串指令操作,设定 DF
为 1,使得串指令自动减量,即从高地址向低地址处理串操作; DF为 0时,串指令自动增量 。 VM(虚拟 86方式标志 )为 1时,从保护模式进入虚拟 8086模式 。 TF(步进标志 )
为 1时,使处理机执行单步操作 。 IF(陷阱标志 )为 1时,允许响应中断,否则关中断 。
系统标志 。
IOPL(I/O特权级标志 ),NT(嵌套任务标志 )和 RF(恢复标志 ),被用于保护模式 。
指令指针寄存器 EIP的低 16位称为 IP,存放下一条顺序执行的指令相对于当前代码段开始地址的一个偏移地址,IP可当作一个单元使用,这在某些情况下是很有用的 。
2.2 中断技术
2.2.1 中断的概念中断 是指程序执行过程中,当发生某个事件时,中止 CPU上现行程序的运行,引出处理该事件的程序执行的过程。
采用中断技术实现 CPU和 I/O设备交换信息可使
CPU与 I/O并行工作。在计算机运行过程中,除了会遇到 I/O中断外,还有许多事件会发生,如硬件故障、电源掉电、人机联系和程序出错、
请求操作系统服务等,这些事件必须及时加以处理。此外,在实时系统,如生产自动控制系统中,必须及时将传感器传来的温度、距离、
压力、湿度等变化信息送给计算机,计算机则暂停当前工作,转去处理和解决异常情况。所以,为了提高系统效率,处理突发事件,满足实时要求,中断概念被提出来了。
引起中断的事件称为中断源
在不同的硬件结构中,通常有不同的中断源和不同的中断装置,但它们有一个共性,即:当中断事件发生后,中断装置能改变处理器内操作执行的顺序,可见中断是现代操作系统实现并发性的基础之一 。
2.2.2 中断源的分类从中断事件的性质来说,可以分成强迫性中断事件和自愿性中断事件两大类,
强迫性中断事件 不是正在运行的程序所期待的,
而是由于某种事故或外部请求信息所引起的 。
这类中断事件大致有以下几种:
l处理器中断事件 。 例如电源故障,主存储器出错等 。
l程序性中断事件 。 例如定点溢出,除数为 0,
地址越界等 。
l外部中断事件 。 例如时钟的定时中断,控制台发控制信息等 。
l输入输出中断事件 。 例如设备出错,传输结束等 。
自愿性中断事件 是正在运行的程序所期待的事件。这种事件是由于执行了一条访管指令而引起的,
它表示正在运行的程序对操作系统有某种需求,一旦机器执行这一中断时,便自愿停止现行程序而转入访管中断处理程序处理。
图 2-2表示出了上述的两类中断事件运行程序中断处理程序中断装置处理器中断事件程序性中断事件外二部中断事件输入输出中断事件运行程序中断处理程序中断装置访管指令中断源可以分成两类,
一类是不可屏蔽中断,如电源掉电,
CPU不能禁止响应;
另一类是可屏蔽中断,通常 PSW的一些位指示某个可屏蔽中断源是否禁止,
CPU可根据该中断源是否被屏蔽来确定是否给予响应。
有些机器中断源并不分类,但按中断来源的不同,分为两类:中断和捕俘,依据中断优先级的高低排成中断级
中断指由 CPU以外产生的事件引起的中断,如 I/O中断、时钟中断、外中断 ;
捕俘又称陷入,指 CPU内部事件或运行程序执行中产生的事件引起的中断,如电源故障、程序故障和访管指令。
图 2-3一种典型的中断级机器故障中断 高优先级低优先级时钟中断磁盘中断网络设备中断软件中断终端设备中断
2.2.3 中断装置计算机系统都采用硬件和软件结合的方法实现中断处理 。 一般说,中断装置主要做以下三件事:
l发现中断源,提出中断请求 。 当发现多个中断源时,它将根据规定的优先级,先后发出中断请求 。
l保护现场,将处理器中某些寄存器内的信息存放于内存储器,使得中断处理程序运行时,不会破坏被中断程序的有用信息,
以便在中断处理结束后它能够继续运行 。
l启动处理中断事件的程序 。
中断 响应 过程
微型计算机采用堆栈保存被中断程序的状态信息,中断响应时,把现行寄存器内容压进堆栈,并接受中断处理程序的中断向量地址和有关信息,这就引出了中断处理程序 。
返回原程序时,只要把栈顶内容弹出送入现行寄存器 。
IP
CS
PSW
现行寄存器新 IP
新 CS
老 IP
老 CS
老 PSW少新栈顶主存新 PSW
2.2.4 中断事件的处理
2.2.4.1 中断响应和中断处理程序中断处理程序主要做以下四项工作:
l保护末被硬件保护的一些必需的处理状态 。 例如,
将通用寄存器的内容送入主存储器,从而使中断处理程序在运行中可以使用通用寄存器 。
l识别各个中断源,即分析产生中断的原因 。
l处理发生的中断事件 。 中断处理程序将根据不同的中断源,进行各种处理操作 。 有简单的操作,如置一个特征标志;也有相当复杂的操作,如重新启动磁带机倒带并执行重读操作 。
l恢复正常操作 。 恢复正常操作一般有几种情况:恢复中断前的程序按断点执行;重新启动一个新的程序或者甚至重新启动操作系统 。
2.2.4.2 处理器中断事件的处理
这种事件是由硬件的故障而产生,
排除这种故障必须进行人工干预 。
中断处理能做的工作一般是保护现场,防止故障曼延,报告操作员并提供故障信息以便维修和校正,以及对程序中所造成的破坏进行估价和恢复 。 下面列举一些处理器中断事件的处理办法 。
2.2.4.3 程序性中断事件的处理
处理程序性中断事件有两种处理办法
中断续元处理
中断续元处理需要的设施调试语句中断续元入口表
中断续元处理过程
>
2.2.4.4 自愿中断事件的处理
这类中断是由于系统程序或用户程序执行访管指令 (例如,Unix中的 trap指令,
MS-DOS 中的 int 指令,IBM 中的
supervisor指令等 )而引起的,它表示运行的程序对操作系统功能的调用,所以也称系统调用,可以看作是机器指令的一种扩充 。
访管指令包括操作码和访管参数两部分,前者表示这条指令是访管指令,
后者表示具体的访管要求。
硬件在执行访管指令时,把访管参数作为中断字并入程序状态字,同时将它送入主存指定单元,然后转向操作系统处理。操作系统分析访管参数,
进行合法性检查后按照访管参数的要求进行相应的处理。
系统调用共性处理流程,
系统调用机制本质上通过特殊硬指令和中断系统来实现,不同机器系统调用命令的格式和功能号的解释不尽相同,共性处理流程如下:
l用户程序执行 n号系统调用
l 通过中断系统进入访管中断处理,保护现场,按功能号跳转
l通过系统调用入口表找相应功能入口地址
l执行相应例行程序,结束后正常情况返回系统调用的下一条指令执行
2.2.4.5 外部中断事件的处理
1) 时钟中断事件的处理
时钟是操作系统进行调度工作的重要工具
绝对时钟
间隔时钟
操作系统有关时钟的任务不同,但一般包括以下内容:
l维护绝对日期和时间 ;
l防止进程的运行时间超出其允许值,发现陷入死循环的进程 ;
l对使用 CPU的用户进程记帐 ;
l处理进程的间隔时钟 (闹钟 );
l对系统的功能或部件提供监视定时器 。
2)控制台中断事件的处理
操作员可以利用控制台开关请求操作系统工作,当使用控制台开关后,就产生一个控制台中断事件通知操作系统 。 操作系统处理这种中断就如同接受一条操作命令一样,转向处理操作命令的程序执行 。
2.2.5 中断的优先级和多重中断
2.2.5.1 中断的优先级
中断装置按照预定的顺序来响应,这个预定的顺序称为中断的优先级,中断装置首先响应优先级高的中断事件 。
如何决定 中断源的优先顺序根据某个中断源或中断级若得不到及时响应,造成计算机出错的严重性程度而定 。
中断系统如何按予先规定的优先顺序响应呢?
可以使用硬件和软件两种办法 。
前者根据排定的优先次序做一个硬件链式排队器,当有高一级的中断事件产生时,
应该封住比它优先级低的所有中断源;
后者编写一个查询程序,依据优先级次序,
自高到低进行查询,一旦发现有一个中断请求,便转入该中断事件处理程序入口 。
1BM360/370系统的中断优先级由高到低的顺序是,
机器校验中断;自愿访管中断;程序性中断;外部中断;输入输出中断;重新启动中断 。
注意,中断的优先级只是表示中断装置响应中断的次序,而并不表示处理它的先后顺序 。
2.2.5.2 中断的屏蔽
主机可以允许或禁止某类中断的响应。如主机可以允许或禁止所有的输入输出中断、外部中断、
机器校验中断以及某些程序中断
有些中断是不能被禁止的,例如,计算机中的自愿访管中断就不能被禁止
主机是否允许某类中断;由当前程序状态字中的某些位来决定 。 一般,当屏蔽位为 1时,主机允许相应的中断,当屏蔽位为 0时,相应中断被禁止 。 按照屏蔽位的标志,可能禁止某一类内的全部中断,也可能有选择地禁止某一类内的部分中断 。
2.2.5.3 多重中断事件的处理
在一个计算机系统运行过程中,由于中断可能同时出现,或者虽不同时出现但却被硬件同时发现,或者出现在其它中断正在进行处理期间,这时 CPU又响应了这个新的中断事件,于是暂时停止正在运行的中断处理程序,转去执行新的中断处理程序,这就叫多重中断 (又称中断嵌套 )
对于多个不同类型的中断将区别不同情况作如下处理:
l 在运行 —个中断处理程序时,往往屏蔽某些中断;例如,在运行处理 I/O中断的例行程序时,可以屏蔽外部中断或其它 I/O中断 。
l 如前所述,中断可以分优先级,对于有些必须处理且优先级更高的中断源,采用屏蔽方法有时可能是不妥的,因此,在中断系统中往往允许在运行某些中断例行程序时,仍然可以响应中断,这时,系统应负责保护被中断的中断处理例行程序的现场 (有的计算机中断系统对断点的保存是在中断周期内,由中断隐指令实现,对用户是透明的 ),然后,再转向处理新中断的例行程序,以便处理结束时有可能返回原来的中断处理例行程序继续运行 。 操作系统必须预先作出规定,哪些中断类型允许嵌套?
嵌套的最大级数? 嵌套的级数视系统规模而定,
一般不超过三重为宜,因为过多重的 ‘ 嵌套 ’
将会增加不必要的系统开销 。
l在运行中断处理例行程序时,如果出现任何程序性中断源,一般情况下,表明这时中断处理程序有错误,应立即响应并进行处理 。
Unix中断和捕俘的多重中断
中断优先级共 6级,优先级从高到低为:
机器故障、时钟、磁盘 I/O、网络设备、
终端和软件中断
紧急中断事件优先级高,用户程序运行时中断事件优先级最低,可见任何中断事件都能中断用户程序运行
Unix多重中断 的实现
内核开辟中断现场保护区 -内核堆栈,总把当前被中断程序的现场信息压进堆栈 。 例如,若一个用户程序发出了一条系统调用,于是该用户程序被暂停,用户程序现场信息压进堆栈 。
在此系统调用处理期间,磁盘 I/O中断发生,
这时系统调用处理程序被中断,系统调用处理程序现埸信息压入堆栈 。 在磁盘 I/O中断处理程序处理期间,又发生了时钟中断,这时磁盘
I/O中断处理程序被中断,现场信息进入堆栈,
时钟中断处理程序开始工作 。 返回时逐级上推内核堆栈,依次逐级返回 。
2.2.6 实例研究,Windows 2000的中断处理
2.2.6.1 Windows 2000的中断处理概述
在 Pentium上 Windows中断的实现中,使用了陷阱,中断和异常等术语 。 中断和异常是把处理器转向正常控制流之外的代码的操作系统情况 。
中断是异步事件,可能随时发生,与处理器正在执行的内容无关 。
中断 主要由 I/O设备,处理器时钟或定时器,
以及软件等产生,可以启用或禁用 。
异常 是同步事件,它是某一个特定指令执行的结果 。 异常的一些例子是内存访问错误,调试指令,除数为零 。 内核也将系统服务调用视作异常,在技术也可以把它作为系统陷阱 。
陷阱 是指这样一种机制,当中断或异常发生时,它俘获正在执行的进程,把它从用户态切换到核心态,并将控制权交给内核的陷阱处理程序 。 不难看出,陷阱机制相当于本节前面所讨论的中断响应和中断处理机构 。
图 2-5 Windows2000的陷阱调度中断服务例程中断服务例程中断服务例程异常调度器虚存管理器的页面管理器中断调度器系统服务调度器异常调度器陷阱处理程序异常帧虚拟地址异常硬件异常软件异常系统服务调用中断陷阱处理程序
它在保存机器状态期间,暂时禁用中断 。 同时,创建一个,陷阱帧,来保存被中断线程的执行状态,当内核处理完中断或异常后恢复线程执行前,通过陷阱帧中保存的信息恢复处理器现场 。
陷阱处理程序本身可以解决一些问题,如一些虚拟地址异常,但在大多数情况下,陷阱处理程序只是确定发生的情况,并把控制转交给其它的内核或执行体模块 。
例如,如果情况是设备中断产生的,陷阱处理程序把控制转交给设备驱动程序提供给该设备的中断服务例程
ISR(Interrupt Service Routine);如果情况是调用系统服务产生的,陷阱处理程序把控制转交给执行体中的系统服务代码;其它异常由内核自身的异常调度器响应 。
2.2.6.2 Windows 2000的中断调度
1) 中断类型和优先级
中断请求级 IRQL(Interrupt Request Level)的标准集
IRQL将按照优先级排列中断,并按照优先级顺序服务中断,较高优先级中断抢占较低优先级中断服务
这一组内核维护的 IRQL是可以移植的,如果处理器具有特殊的与中断相关的特性 ( 如第二时钟 ),则可以增加可移植的 IRQL
图 2-6 x86体系结构 Windows的中断请求级系统关闭高31
掉电30
处理器间的中断29
时钟28
配置文件设备 n
………
设备 1
Dispatch/DPC2
APC1
低0
硬件中断软件中断正常的线程执行
Windows的中断屏蔽
每一个处理器都有一个 IRQL设置,其值随着操作系统代码的执行而改变,决定了该处理器可以接收哪些中断 。 IRQL也被用于同步访问核心数据结构 。 当核心态线程运行时,它可以提高或降低处理器的 IRQL。
如果中断源高于当前的 IRQL设置,则响应中断;否则该中断将被屏蔽,处理器不会响应该中断,直到一个正在执行的线程降低了 IRQL。
图 2-7 Windows的中断屏蔽高掉电处理器间的中断时钟配置文件设备 n
………
设备 1
在处理器 A上被屏蔽的中断
Dispatch/DPC
APC
低
IRQL=时钟处理器 A
在处理器 B上被屏蔽的中断
IRQL= Dispatch/DPC
处理器 B
2)中断处理
当中断产生时,陷阱处理程序将保存计算机运行程序的状态,然后禁用中断并调用中断调度程序 。 中断调度程序立刻提高处理器的 IRQL到中断源的级别,以使得在中断服务过程中屏蔽等于和低于当前中断源级别的其他中断 。 然后,重新启用中断,以使高优先级的中断仍然能够得到服务 。
中断分配表 IDT(Interrupt Dispatch Table)
Windows2000使用中断分配表
IDT(Interrupt Dispatch Table)来查找处理特定中断的例程。中断源的 IRQL作为表的索引,表的入口指向中断处理例程图 2-8 Windows的中断服务高掉电处理器间的中断时钟设备 n
…
…
…
设备 1
② 中断调度程序接收到中断源的 IRQL,
用作查询 IDT的索引
Dispatch/DPC
APC
低
① 有中断产生
…
…
…
线程调度程序 / DPC处理程序
( 无 )
系统关闭例程系统调电例程处理器间中断处理程序时钟处理程序设备 n ISR
设备 1 ISR
APC处理程序
③ 中断调度程序跟随该指针,
调用相应的处理程序
在 x86系统中,IDT是处理器控制区
PCR(Processing Control Region)指向的硬件结构,有的系统用软件实现 。
PCR和它的扩展 ——处理器控制块
PRCB包括了系统中各种处理器状态信息 。 内核和硬件抽象层 HAL使用该信息来执行体系结构特定的操作和机器特定的操作 。 这些信息包括:
当前运行线程,选定下一个运行的线程,处理器的中断级等等 。
中断服务例程执行结束处理
在中断服务例程执行之后,中断调度程序将降低处理器的 IRQL
到该中断发生前的级别,然后加载保存的机器状态 。 被中断的线程将从它停止的位置继续执行 。
多处理器系统中断处理
每个处理器都有单独的 IDT,这样,不同的处理器就可以运行不同的中断服务例程 ISR。 在多处理器系统中,每个处理器都可以收到时钟中断,但只有一个处理器在响应该中断时更新系统时钟 。
然而所有处理器都使用该中断来测量线程的时间片并在时间片结束后启动线程调度 。 同样的,某些系统配置可能要求特殊的处理器处理某一设备中断 。
中断对象
大多数处理中断的例程都在内核中,例如:内核更新时钟时间,在电源级中断产生时关闭系统 。 然而,键盘,I/O设备和磁盘驱动器等外部设备也会产生许多中断,这些设备的种类很多,变化很大,设备驱动程序需要一种方法来告诉内核:当设备中断发生时应调用哪个例程 。 为此,内核提供了一个可移植的机制 ——中断对象,它是一种内核控制对象,允许设备驱动程序注册其设备的 ISR。 中断对象包含内核所需的将设备
ISR和中断特定级相联系的所有信息,其中有,ISR地址,设备中断的 IRQL,以及与 ISR相联系的内核入口 。
当中断对象被初始化后,称为,调度代码,的一些汇编语言代码指令就会被存储在对象中 。 当中断发生时执行此代码,这个中断对象常驻代码调用真正的中断调度程序,给它传递一个指向中断对象的指针 。 中断对象包括了第二个调度程序例程所需要的信息,以便定位和正确使用设备驱动程序提供的 ISR。
连接 /分离一个中断对象
把 ISR与特殊中断级相关联称为连接一个中断对象,而从 IDT入口分离 ISR叫做断开一个中断对象 。 这些操作允许在设备驱动程序加载到系统时打开 ISR,在卸载设备驱动程序时关闭
ISR。 如果多个设备驱动程序创建多个中断对象并将它们连接到同一个 IDT入口,那么当中断在指定中断级上发生时,中断调度程序会调用每一个例程 。 这样就使得内核很容易地支持
,菊花链,配置,在这种构造中几个设备在相同的中断行上中断 。
使用中断对象的优点努
使用中断对象来注册 ISR,可以防止设备驱动程序直接随意中断硬件,并使设备驱动程序无需了解 IDT的任何细节,
从而有助于创建可移植的设备驱动程序 。
另外,通过使用中断对象,内核可以使
ISR与可能同 ISR共享数据的设备驱动程序的其他部分同步执行 。 进而,中断对象使内核更容易调用多个任何中断级的
ISR。
3)软件中断
Windows2000也为多种任务产生软件中断,他们包括:启动线程调度,处理定时器到时,在特定线程的描述表中异步执行一个过程,支持异步 I/O操作等 。
( 1)调度或延迟过程调用 DPC(Deferred
Procedure Call)中断
当一个线程不能继续执行时,内核应该直接调用调度程序实现描述符表切换 。 然而,
有时内核在深入多层代码内检测到应该进行重调度,最好的方法就是请求调度,但应延迟调度的产生直到内核完成当前的活动为止 。 使用 DPC软件中断是实现这种延迟的简单方法 。
当需要同步访问共享的内核结构时,内核总 是 将 处 理 器 的 IRQL 提 高 到
Dispatch/DPC级之上,这样就禁止了其他的软件中断和线程调度 。 当内核检测到调度应该发生时,它 将 请 求 一 个
Dispatch/DPC级中断;但是由于 IRQL等于或高于 Dispatch/DPC级,处理器将在检查期间保存该中断 。 当内核完成了当前活动后,它将 IRQL降低到 Dispatch/DPC级之下,于是调度中断就可以出现 。
除了延迟线程调度之外,内核在这个
IRQL上也处理延迟过程调用 DPC。 DPC
是执行系统任务的函数,该任务比当前任务次要,被称作 ’ 延迟函数 ’,因为,他们可能不立即执行 。 DPC为操作系统提供了在内核态下产生中断,并执行系统函数的能力 。 内核使用 DPC处理定时器到时并释放在定时器上等待的线程,在线程时间片结束后重调度处理器 。 设备驱动程序还可以通过 DPC完成延迟的 I/O请求 。
DPC由,DPC对象,表示,它也是一个内核控制对象 。 该对象对于用户态程序是不可见的,但对于设备驱动程序和其他系统代码是可见的 。 DPC对象包含的最重要信息是当内核处理 DPC中断时将调用的系统函数的地址 。 等待执行的 DPC例程被保存在叫做 DPC队列的内核管理队列中 。 为了调用一个 DPC,系统代码将调用内核来初始化 DPC对象,并把它放入 DPC队列中 。
将一个 DPC放入 DPC队列会促使内核请求一个在 Dispatch/DPC级的中断 。 因为通常 DPC是运行在较高 IR QL级的软件对它进行排队的,所以被请求中断直到内核降低 IRQL到 Dispatch/DPC级之下才出现,图 2-9描述了 DPC的处理 。
图 2-9 提交 DPC
高掉电
…
…
…
② 如果 IRQL降到比
Dispatch/DPC级低,
则 DPC中断发生 。
Dispatch/DPC
APC
低
① 定时器到时,内核排好 DPC队列,
准备释放等候在定时器上的所有线程,然后内核请求软件中断 。
…
…
…
调度程序
③ DPC中断之后,控制传送给 ( 线程 ) 调度程序
DPC
DPC DPC
④ 调度程序执行 DPC中的每一个 DPC例程,然后使队列变空 。 如果需要,调度程序还重新安排处理器
( 2)异步过程调用中断
异步过程调用 APC(Asynchronous procedure Call)
为用户程序 /系统代码提供了一种在特殊用户线程的描述表 ( 一个特殊的地址空间 ) 中执行代码的方法 。
APC由内核控制对象描述,称为 APC对象,等待执行的 APC在由内核管理的 APC队列中 。
APC队列和 DPC队列的不同之处在于,DPC队列是系统范围的,而 APC队列是属于每个线程的 。 当内核被要求对 APC排队时,内核将 APC
插入到将要执行 APC例程的线程的 APC队列中 。
内核依次请求 APC级的软件中断,并当线程最终开始运行时,执行 APC。
2.2.6.3 异常调度
异常是直接由运行程序的执行产生的情况 。
WIN32引入了结构化异常处理 (Structures
Exception Handling)工具,它允许应用程序在异常发生时可以得到控制 。 然后,应用程序可以固定这个状态并返回到异常发生的地方,从而终止引发异常的子例程的执行;也可以向系统声明不能识别异常,并继续搜寻能处理异常的异常处理程序 。
核心态异常
如果异常产生于核心态,异常调度程序将简单地调用一个例程来定位处理该异常的基于框架的异常处理程序 。 由于没有被处理的核心态异常是一种致命的操作系统错误,所以异常调度程序必须能找到异常处理程序 。
一些异常处理
内核俘获和处理某些对用户程序透明的异常,如调试断点异常,此时内核将调用调试程序来处理这个异常 。 少数异常可以被允许原封不动地过滤回用户态,如操作系统不处理的内存越界或算术异常 。 环境子系统能够建立
,基于框架的异常处理程序,来处理这些异常 。,基于框架的异常处理程序,是指与特殊过程激活相关的异常处理程序,当调用过程时,代表该过程激活的堆栈框架就会被推入堆栈,堆栈框架可以有一个或多个与它相关的异常处理程序,每个程序都保存在源程序的一个特定代码块内 。 当异常发生时,内核将查找与当前堆栈框架相关的异常处理程序,如果没有,内核将查找与前一个堆栈框架相关的异常处理程序,如此往复,如果还没有找到,内核将调用系统默认的异常处理程序 。
当异常发生时都将在内存产生一个事件链 。 硬件把控制交给陷阱处理程序,陷阱处理程序将创建一个陷阱框架 。 如果完成了异常处理,陷阱框架将允许系统从中断处继续运行 。 陷阱处理程序同时还要创建一个包含异常原因和其他有关信息的异常纪录 。
2.2.6.4 系统服务调度
INT 2E指令将引起一个系统陷阱用户态核心态系统服务调用 陷阱处理程序系统服务调度程序系统服务调度表
0
1
2
3
………
n
系统服务扩展系统服务 2
系统服务调度程序将校验正确的参数值,
并且将调用者的参数从线程的用户态堆栈复制到它的核心态堆栈中,然后执行系统服务 。 如果传递给系统服务的参数指向了在用户空间中的缓冲区,则在核心态代码访问用户缓冲区前,必须查明这些缓冲区的可访问性 。
系统服务表
每个线程都有一个指向系统服务表的指针 。
Windows2000有两个内置的系统服务表,第一个默认表定义了在 NTOSKRNL.EXE中实现的核心执行体系统服务;另一个包含了在 WIN32
子系统 WIN32K.SYS的核心态部分中实现的
WIN32 USER及 GDI服务 。 当 WIN32线程调用
WIN32 USER及 GDI服务时,线程系统服务表的地址将指向包含 WIN32 USER及 GDI的服务表 。
如图 2-11 所示,KERNEL32.DLL 中的
WIN32 WriteFile函数调用 NTDLL.DLL中的 NtWriteFile函数,它依次执行适当的指令以引发系统陷阱,传递代表 NtWriteFile
的系统服务号码 。 然后系统服务调度程序
( NTOSKRNL.EXE中的 KiSystemService
函数 ) 调用真正的 NtWriteFile来处理 I/O
请求 。 对于 WIN32 USER及 GDI函数,系统服务调度调用在 WIN32子系统可加载核心态部分 WIN32K.SYS中的函数 。
Windows200的系统服务调度调用
WriteFile()Win32应用程序调用 NtWriteFile
返回调用者
KERNEL32.DLL
中的 WriteFile
INT 2E
返回调用者
NTDLL.DLL
中的 NtWriteFile
调用 NtWriteFile
解除中断
NTOSKRNL.EXE中的
KiSystemService
执行操作返回调用者
NTOSKRNL.EXE
中的 NtWriteFile
调用 USER及 GDI
服务 应用程序
INT 2E
返回调用者
GDI32.DLL或
USER32.DLL
调用 WIN32例程解除中断
NTOSKRNL.EXE中的
KiSystemService
执行操作返回调用者
WIN32K.SYS
中的服务入口点用户态核心态
WIN32专用
WIN32专用所有子系统使用软件中断 软件中断
WIN32内核 API WIN32 USER及 GDI API
2.3 进程及其实现
2.3.1 进程的定义和性质
进程 由数据结构以及在其上执行的程序 (语句序列 )组成,是程序在这个数据集合上的运行过程,也是操作系统进行资源分配和保护的基本单位 。
进程的概念是操作系统中最基本,最重要的概念 。 它是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律而引进的一个新概念,所有的多道程序设计操作系统都建立在进程的基础上 。
操作系统专门引入进程的概念,从理论角度看,
是对正在运行的程序过程的抽象;从实现角度看,则是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序 。
进程 的 属性
l 结构性:进程包含了数据集合和运行于其上的程序 。
l 共享性:同一程序同时运行于不同数据集合上时,构成不同的进程 。 或者说,
多个不同的进程可以共享相同的程序 。
l 动态性:进程是程序在数据集合上的一次执行过程,是动态概念,同时,它还有生命周期,由创建而产生,由撤销而消亡;而程序是一组有序指令序列,
是静态概念,所以,程序作为一种系统资源是永久存在的 。
进程 的 属性
l独立性:进程既是系统中资源分配和保护的基本单位,也是系统调度的独立单位 (单线程进程 )。 凡是未建立进程的程序,都不能作为独立单位参与运行 。 通常,每个进程都可以各自独立的速度在
CPU上进行 。
l制约性:并发进程之间存在着制约关系,进程在进行的关键点上需要相互等待或互通消息,以保证程序执行的可再现性和计算结果的唯一性 。
进程 的 属性
l并发性:进程可以并发地执行 。 对于一个单处理器的系统来说,m个进程 P1,P2,…,Pm
是轮流占用处理器并发地执行 。 例如可能是这样进行的:,进程 P1执行了 nl条指令后让出处理器给 P2,P2执行了 n2条指令后让出处理器给
P3,…,Pm执行了 nm条指令后让出处理器给
P1,… 。 因此,进程的执行是可以被打断的,
或者说,进程执行完一条指令后在执行下一条指令前,可能被迫让出处理器,由其它若干个进程执行若干条指令后才能再次获得处理器而执行 。
操作系统中 为什么 要引入进程概念?
原因 1-它能较好地解决系统的,并发性,和刻划系统的,动态性,,它是并发程序设计的一种有力工具 。从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,则是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
原因 2-它能较好地解决系统的,共享性,
另一种称,可再用,程序由于它被调用过程中具有自身修改,在调用它的程序退出以前是不允许其它程序来调用它的。
,可再入,程序的是指能被多个程序同时调用的程序。,可再入”程序具有以下性质:它是纯代码的,即它在执行中自身不改变;调用它的各程序应提供工作区,因此,可再入的程序可以同时被几个程序调用。
举例
假定编译程序 P现在正在编译源程序甲,编译程序从 A点开始工作,当执行到 B点时需要将信息记到磁盘上,且程序 P在 B点等待磁盘传输 。 这时处理器空闲,为了提高系统效率,利用编译程序 P的
,可再入,性,可让编译程序 P再为源程序乙进行编译,仍从 A点开始工作 。 现在应怎样来描述编译程序 P的状态呢?称它为在 B点等待磁盘传输状态,
还是称它为正在从 A点开始执行的状态?编译程序 P
只有一个,但加工对象有甲,乙两个源程序,所以再以程序作为占用处理器的单位显然是不适合的了 。 为此,我们把编译程序 P,与服务对象联系起来,P为甲服务就说构成进程 P甲,P为乙服务则构成进程 P乙 。 这两个进程虽共享程序 P,
但它们可同时执行且彼此按各自的速度独立执行 。 现在我们可以说进程 P甲在 B
点处于等待磁盘传输,而进程 P乙正在从
A点开始执行 。 可见程序与计算 (程序的执行 )不再一一对应,延用程序概念不能描述这种共享性,因而,引入了新的概念 -进程 。
编译程序 P
(P 的入口,处理源程序乙 )
(P把源程序甲的信息记盘等磁盘完成 )
A
B
源程序甲 源程序乙
2.3.2 进程的状态和转换
2.1.1.1 三态模型
一个进程从创建而产生至撤销而消亡的整个生命周期,可以用一组状态加以刻划,为了便于管理进程,一般来说,按进程在执行过程中的不同状况至少定义三种不同的进程状态:
三种不同的进程状态:
l 运行态 ( running),占有处理器正在运行 。
l 就绪态 ( ready),具备运行条件,等待系统分配处理器以便运行 。
l 等待态 ( blocked),不具备运行条件,
正在等待某个事件的完成 。
图 2-12 进程三态模型及其状态转换运行态就绪态 等待态选中落选出现等待事件等待事件结束引起进程状态转换的具体原因:
l 运行态 —→ 等待态:等待使用资源;
如等待外设传输;等待人工干预 。
l 等待态 —→ 就绪态:资源得到满足;
如外设传输结束;人工干预完成 。
l 运行态 —→ 就绪态:运行时间片到;
出现有更高优先权进程 。
l 就绪态 —→ 运行态,CPU空闲时选择一个就绪进程 。
2.3.2.2 五态模型
进程五态模型及其转换 。
运行态就绪态 等待态选中落选出现等待事件等待事件结束新建态 终止态新建态
新建态对应于进程刚刚被创建的状态 。
创建一个进程要通过两个步骤,首先,是为一个新进程创建必要的管理信息,然后,让该进程进入就绪态 。 此时进程将处于新建态,它并没有被提交执行,而是在等待操作系统完成创建进程的必要操作 。 必须指出的是,操作系统有时将根据系统性能或主存容量的限制推迟新建态进程的提交 。
终止态
进程的终止也要通过两个步骤,首先,是等待操作系统进行善后,然后,退出主存。当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止态。进入终止态的进程以后不再执行,但依然临时保留在操作系统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。
进程状态转换的具体原因如下:
lNULL—→ 新建态:执行一个程序,创建一个子进程 。
l新建态 —→ 就绪态:当操作系统完成了进程创建的必要操作,并且当前系统的性能和虚拟内存的容量均允许 。
l运行态 —→ 终止态:当一个进程到达了自然结束点,
或是出现了无法克服的错误,或是被操作系统所终结,
或是被其他有终止权的进程所终结 。
l终止态 —→NULL,完成善后操作 。
l就绪态 —→ 终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程 。
l等待态 —→ 终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程 。
2.3.2.3 进程的挂起
为什么要有,挂起,状态?
事实上,可能出现这样一些情况,例如由于进程的不断创建,系统的资源已经不能满足进程运行的要求,这个时候就必须把某些进程挂起( suspend),对换到磁盘镜像区中,
暂时不参与进程调度,起到平滑系统操作负荷的目的。
l系统中的进程均处于等待状态,处理器空闲,此时需要把一些阻塞进程对换出去,以腾出足够的内存装入就绪进程运行 。
l进程竞争资源,导致系统资源不足,负荷过重,
此时需要挂起部分进程以调整系统负荷,保证系统的实时性或让系统正常运行 。
l 把一些定期执行的进程 ( 如审计程序,监控程序,
记账程序 ) 对换出去,以减轻系统负荷 。
l用户要求挂起自己的进程,以便根据中间执行情况和中间结果进行某些调试,检查和改正 。
l父进程要求挂起自己的后代进程,以进行某些检查和改正 。
l操作系统需要挂起某些进程,检查运行中资源使用情况,以改善系统性能 ;或当系统出现故障或某些功能受到破坏时,需要挂起某些进程以排除故障 。
图 2-14 具有挂起功能系统的进程状态及其转换挂起等待事件结束出现等待事件解除挂起 挂起落选选中运行态就绪态等待事件结束终止态新建态挂起就绪态解除挂起 挂起挂起等待态等待态提交提交具有挂起进程功能系统中的进程状态
。 在此类系统中,进程增加了两个新状态:挂起就绪态 ( ready suspend) 挂起就绪态表明了进程具备运行条件但目前在二级存储器中,只有当它被对换到主存才能被调度执行 。
挂起等待态 ( blocked suspend) 挂起等待态则表明了进程正在等待某一个事件且在二级存储器中 。
进程状态转换的具体原因如下:
l 等待态 —→ 挂起等待态:如果当前不存在就绪进程,
那么至少有一个等待态进程将被对换出去成为挂起等待态;
操作系统根据当前资源状况和性能要求,可以决定把等待态进程对换出去成为挂起等待态 。
l 挂起等待态 —→ 挂起就绪态:引起进程等待的事件发生之后,相应的挂起等待态进程将转换为挂起就绪态 。
l 挂起就绪态 —→ 就绪态:当内存中没有就绪态进程,或者挂起就绪态进程具有比就绪态进程更高的优先级,系统将把挂起就绪态进程转换成就绪态 。
l 就绪态 —→ 挂起就绪态:操作系统根据当前资源状况和性能要求,也可以决定把就绪态进程对换出去成为挂起就绪态 。
挂起进程具有如下特征:
l该进程不能立即被执行 。
l挂起进程可能会等待一个事件,但所等待的事件是独立于挂起条件的,事件结束并不能导致进程具备执行条件 。
l进程进入挂起状态是由于操作系统,父进程或进程本身阻止它的运行 。
l结束进程挂起状态的命令只能通过操作系统或父进程发出 。
2.3.3 进程的描述
2.3.3.1 操作系统的控制结构
操作系统的核心控制结构是进程结构,资源管理的数据结构将围绕进程结构展开。
操作系统的控制表分为四类,进程控制表,存储控制表,I/O
控制表和文件控制表 。
l进程控制表用来管理进程及其相关信息 。
l存储控制表用来管理一级 ( 主 ) 存储器和二级
( 虚拟 ) 存储器,主要内容包括:主存储器的分配信息,二级存储器的分配信息,存储保护和分区共享信息,虚拟存储器管理信息 。
lI/O控制表用来管理计算机系统的 I/O设备和通道,
主要内容包括,I/O设备和通道是否可用,I/O设备和通道的分配信息,I/O操作的状态和进展,I/O操作传输数据所在的主存区 。
l文件控制表用来管理文件,主要内容包括:被打开文件的信息,文件在主存储器和二级存储器中的位置信息,被打开文件的状态和其他属性信息。
图 2-15 操作系统控制表的通用结构
Memory
Devices
Files
Processes
Memory Tables
I/O Tables
File Tables
Primary Process Table
Process 1
……
Process N
Process 2
Process Image
Process 1
Image
……
Process N
Image
2.3.3.2 进程映像
操作系统中把进程物理实体和支持进程运行的环境合称为进程上下( context)。
当系统调度新进程占有处理器时,新老进程随之发生上下文切换。因此,进程的运行被认为是在上下文中执行。
操作系统进程上下文包括三个组成部分:
l用户级上下文:由用户程序块,用户数据块 ( 含共享数据块 ) 和用户堆栈组成的进程地址空间 。
l系统级上下文:包括进程的标识信息,
现场信息和控制信息,进程环境块,以及系统堆栈等组成的进程地址空间 。
l寄存器上下文:由程序状态字寄存器和各类控制寄存器,地址寄存器,通用寄存器组成 。
进程有四个要素组成:
控制块、
程序块、
数据块、
堆栈。
l进程程序块,即被执行的程序,规定了进程一次运行应完成的功能 。 通常它是纯代码,作为一种系统资源可被多个进程共享 。
l进程数据块,即程序运行时加工处理对象,
包括全局变量,局部变量和常量等的存放区以及开辟的工作区,常常为一个进程专用 。
l系统 /用户堆栈,每一个进程都将捆绑一个系统 /用户堆栈 。 用来解决过程调用或系统调用时的地址存储和参数传递 。
l进程控制块,每一个进程都将捆绑一个进程控制块,用来存储进程的标志信息,现场信息和控制信息 。 进程创建时,建立一个 PCB;进程撤销时,回收 PCB,它与进程一一对应 。
图 2-16 用户进程在虚拟内存中的组织进程标识信息进程现场信息进程控制信息用户堆栈共享地址空间用户私有地址空间
(代码、数据)
进程控制块
2.3.3.3 进程控制块
进程控制块 (Process Control
Block),是操作系统用于记录和刻划进程状态及有关信息的数据结构。也是操作系统掌握进程的唯一资料结构,它包括了进程执行时的情况,以及进程让出处理器后所处的状态、断点等信息。
进程控制块包含三类信息:
l标识信息 。 用于唯一地标识一个进程,常常分由用户使用的外部标识符和被系统使用的内部标识号 。 几乎所有操作系统中进程都被赋予一个唯一的,内部使用的数值型的进程号,操作系统的其他控制表可以通过进程号来交叉引用进程控制表 。 常用的标识信息有进程标识符,
父进程的标识符,用户进程名,用户组名等 。
l现场信息 。 用于保留一个进程在运行时存放在处理器现场中的各种信息,任何一个进程在让出处理器时必须把此时的处理器现场信息保存到进程控制块中,而当该进程重新恢复运行时也应恢复处理器现场 。 常用的现场信息包括通用寄存器的内容,控制寄存器 (如 PSW寄存器 )的内容,用户堆栈指针,系统堆栈指针等 。
进程控制块包含三类信息:
l 控制信息 。 用于管理和调度一个进程 。 常用的控制信息包括,1) 进程的调度相关信息,
如进程状态,等待事件和等待原因,进程优先级,队列指引元等; 2) 进程组成信息,如正文段指针,数据段指针 ;3) 进程间通信相关信息,
如消息队列指针,信号量等互斥和同步机制;
4) 进程在二级存储器内的地址; 5) CPU资源的占用和使用信息,如时向片余量,进程己占用 CPU的时间,进程己执行的时间总和,记帐信息 ;6) 进程特权信息,如在内存访问和处理器状态方面的特权 。 7) 资源清单,包括进程所需全部资源,已经分得的资源,如主存资源,I/O
设备,打开文件表等 。
2.3.3.4 进程管理
把处于同一状态 (例如就绪态 )的所有进程控制块链接在一起,这样的数据结构称为进程队列 (Process Queues),简称队列。
同一状态进程的 PCB既可按先来先到的原则排成队列 ;也可以按优先数或其它原则排成多个队列。例如,对于等待态的进程队列可以进一步细分,每一个进程按等待的原因进入相应的等待队列。
链接进程控制块的方法
单向链接方法是在每个进程控制块内设置一个队列指引元,它指出在队列中跟随着它的下一个进程的进程控制块内队列指引元的位置。
双向链接方法是在每个进程控制块内设置两个指引元,其中一个指出队列中该进程的上一个进程的进程控制块内队列指引元的位置,另一个指出队列中该进程的下一个进程的进程控制块的队列指引元的位置。
队列标志
为了标志和识别一个队列,系统为每一个队列设置一个队列标志,单向链接时,队列标志指引元指向队列中第一个进程的队列指引元的位置 ;双向链接时,队列标志的前向指引元指向队列中第一个进程的前向队列指引元的位置 ; 队列标志的后向指引元指向队列中最后一个进程的后向队列指引元的位置图 2-17 进程控制块的链接队列指引元
0
队列指引元
0
0前向后向
( a) 单向连接 ( b) 双向连接队列管理
一个进程从一个所在的队列中退出的工作称为出队,相反,一个进程排入到一个指定的队列中的工作称为入队。处理器调度中负责入队和出队工作的功能模块称为队列管理模块,简称队列管理。
图 2-18 操作系统的队列管理和状态转换示意图处理器指派提交完成超时事件 1等待队列事件 2等待队列事件 n等待队列就绪队列
……
等待事件 1
等待事件 2
等待事件 n
事件 1
出现事件 2
出现事件 n
出现双向链接
假设采用双向链接,每个进程有两个队列指引元,用来指示该进程在队列中的位置,其中第一个队列指引元为前向指引元,第二个为后向指引元根据一个进程排在一个队列中的情况,前
(后 )向指引元的内容规定如下:
l情况 1:它是队列之首 。 此时,它的前向指引元为 0,而后向指引元指出它的下一个进程的后向指引元位置 。
l情况 2:它是队列之尾 。 此时,它的后向指引元为 0,而它的前向指引元指出它的上一个进程的前向指引元位置 。
l情况 3:它的前后均有进程 。 此时,前 (后 )
向指引元指出它的上 (下 )一个进程的前 (后 )
向指引元位置 。
进程的出队举例
考虑一个进程的出队,假设进程 Q在某个队列中,它的前面是进程 P,后面是进程 R。 进程 Q
出队过程为:把 Q的前向指引元的内容送到 R
的前向指引元中,把 Q的后向指引元的内容送到 P的后向指引元中 。 于是 P的后向指引元指向 R,而 R的前向指引元指向 P,Q就从队列中退出 。 类似的可实现队首进程,队尾进程的出队,或者让一个进程加入到队列中去 。
表格法组织 PCB的
此外,用来组织 PCB的方法为表格法 。 把所有进程的 PCB都组织在一个线性表中,
进程调度时需要查找整个 PCB表 ;也可以把相同状态进程的 PCB组织在一个线性表中,系统有多个线性表,这样可缩短查表时间 。
2.3.4 进程的控制
2.3.4.1进程的创建进程的创建来源于以下四个事件:
l提交一个批处理作业 。
l在终端上交互式的登录 。
l操作系统创建一个服务进程 。
l存在的进程孵化 ( spawn) 新的进程 。
进程孵化
当一个用户作业被接受进入系统后,可能要创建一个或多个进程来完成这个作业;一个进程在请求某种服务时,也可能要创建一个或多个进程来为之服务 。 例如,当一个进程要求读卡片上的一段数据时,可能创建一个卡片输入机管理进程 。
有的系统把,孵化,用父子进程关系来表示,当一个进程创建另一个进程时,生成进程称父进程
(Parent Process),被生成进程称子进程 (Child
Process),即一个父进程可以创建子进程,从而形成树形的结构,如 Unix就是这样,当父进程创建了子进程后,子进程就继承了父进程的全部资源,父子进程常常要相互通信和协作,当子进程结束时,又必须要求父进程对其作某些善后处理 。
进程的创建过程如下描述:
l 在主进程表中增加一项,并从 PCB池中取一个空白 PCB。
l 为新进程的进程映像中的所有成分分配地址空间 。 对于进程孵化操作还需要传递环境变量,构造共享地址空间 。
l 为新进程分配资源,除内存空间外,还有其它各种资源 。
l 查找辅存,找到进程正文段并装到正文区 。
l 初始化进程控制块,为新进程分配一个唯一的进程标识符,初始化 PSW。
l 加入某一就绪进程队列,或直接将进程投入运行 。
l 通知操作系统的某些模块,如记账程序,性能监控程序 。
2.3.4.2 进程上下文切换
进程的切换就是让处于运行态的进程中断运行,让出处理器,这时要做一次进程上下文切换、即保存老进程状态而装入被保护了的新进程的状态,以便新进程运行。
进程切换的步骤如下:
l保存被中断进程的处理器现场信息 。
l修改被中断进程的进程控制块的有关信息,
如进程状态等 。
l把被中断进程的进程控制块加入有关队列 。
l选择下一个占有处理器运行的进程 。
l修改被选中进程的进程控制块的有关信息 。
l根据被选中进程设置操作系统用到的地址转换和存储保护信息 。
l根一据被选中进程恢复处理器现场 。
模式切换 与 上下文切换当中断发生的时候,暂时中断正在执行的用户进程,把进程从用户状态切换到内核状态,去执行操作系统例行程序以获得服务,这就是一次 模式切换,注意,此时仍在该进程的上下文中执行,仅仅模式变了。内核在被中断了的进程的上下文中对这个中断事件作处理,即使该中断可能不是此进程引起的。另一点要注意的是被中断的进程可以是正在用户态下执行的,
也可以是正在核心态下执行的,内核都要保留足够信息以便在后来能恢复被中断了的进程执行。内核在核心态下对中断事件进行处理,决不会再产生或调度一个特殊进程来处理中断事件。
模式切换的步骤如下:
l 保存被中断进程的处理器现场信息 。
l 根据中断号置程序计数器 。
l 把用户状态切换到内核状态,
以便执行中断处理程序 。
CPU模式切换不同于进程上下文切换
显然模式切换不同于进程切换,它并不引起进程状态的变化,在大多数操作系统中,它也不一定引起进程的切换,在完成了中断调用之后,
完全可以再通过一次逆向的模式切换来继续执行用户进程。
Unix中进程上下文切换和模式切换
Unix中存在两类进程:系统进程和用户进程,系统进程在核心态下执行操作系统代码,用户进程在用户态下执行用户程序 。 用户进程因中断和系统调用进入内核态,系统进程开始执行,这两个进程 (用户进程和系统进程 )使用同一个 PCB,所以实质上是一个进程 。 但是这两个进程所执行的程序不同,映射到不同物理地址空间,使用不同堆栈 。 一个系统进程的地址空间中包含所有的系统核心程序和各进程的进程数据区,所以,各进程的系统进程除数据区不同外,其余部分全相同,
但各进程的用户进程部分则各不相同 。 图 2- 19是
Unix中进程上下文切换和模式切换示意 。 其中,
1为进程在用户态下执行,2为进程在核心态下执行,3进程为就绪态,4进程为等待态 。 任何时刻
Unix中进程上下文切换和模式切换
一个处理器仅能执行一个进程,所以至多有一个进程可以处在状态 1或状态 2,这两个状态相应于用户态和核心态 。 在多道程序设计系统中,
有许多进程在并发执行,如果两个以上进程同时执行系统调用,并要求在核心态下执行,则有可能破坏核心数据结构中的信息 。 通过禁止任意的上下文切换和控制中断的响应,就能保证数据的一致性 。 图中示意,仅当进程从,核心态运行,状态转为,在内存中等待,状态时,内核才允许上下文切换 。 在核心态下的进程不能被其他进程抢占,或者说进程在核心态下执行时不允许上下文切换 。 由于内核处于不可抢占态,
所以内核可保持其数据结构的完整和一致 。
图 2-19 进程上下文切换和模式切换核心态运行 2
系统调用或中断 (隐含模式切换 ) 模式切换用户态运行 1
等待状态 4
就绪状态 3
发生事件唤醒调度进程 调度进程中断、
中断返回允许的上下文切换切换
2.3.4.3 进程的阻塞和唤醒进程唤醒的步骤如下:
l从相应的等待进程队列中取出进程控制块 。
l修改进程控制块的有关信息,如进程状态等 。
l把修改后进程控制块加入有关就绪进程队列 。
2.3.4.4 进程的撤销进程撤销的主要原因包括:
l进程正常运行结束 。
l进程执行了非法指令 。
l进程在常态下执行了特权指令 。
l 进程运行时间超越了分配给它的最大时间段 。
l 进程等待时间超越了所设定的最大等待时间 。
l 进程申请的内存超过了系统所能提供最大量 。
l 越界错误 。
进程撤销的主要原因包括:
l 对共享内存区的非法使用 。
l 算术错误,如除零和操作数溢出 。
l 严重的输入输出错误 。
l 操作员或操作系统干预 。
l 父进程撤销其子进程 。
l 父进程撤销 。
l 操作系统终止 。
撤销原语终止进程,具体步骤如下:
l根据撤销进程标识号,从相应队列中找到它的 PCB;
l将该进程拥有的资源归还给父进程或操作系统;
l若该进程拥有子进程,应先撤销它的所有子孙进程,以防它们脱离控制;
l撤销进程出队,将它的 PCB归还到 PCB
池 。
2.3.5 进程管理的实现进程管理一些程序模块:
l队列管理模块:负责进程队列的入队和出队工作 。
l 进程调度程序:负责选择下一个占有处理器运行的进程 。
l资源分配程序:负责分配资源给进程 。
l上下文切换程序:负责进程切换和模式切换 。
l 进程控制原语:提供了若干基本操作以管理和控制进程 。
如:建立进程原语,撤销进程原语,阻塞进程原语,唤醒进程原语,挂起进程原语,解除挂起进程原语,改变进程优先数原语,修改进程状态原语等等 。
l 进程通信原语:提供了若干基本操作以便进程间通信 。
如,P操作原语,V操作原语,管程原语,send原语,
receive原语等等 。
l 其他 。
2.3.5.2 进程管理的实现模型
1) 非进程内核模型
2) OS功能在用户进程内执行的实现模型
3) OS功能由进程实现的模型
>
2.3.6 实例研究 ——Unix SVR4
的进程管理
Unix SVR4的进程管理的实现采用基于用户进程的实现模型,大多数操作系统功能在用户进程的环境中执行,因此它需要在用户模式和内核模式切换 。 Unix SVR4允许两类进程:用户进程和系统进程 。 系统进程在内核模式下执行,完成操作系统的一些重要功能,如内存分配和进程对换 。
而用户进程在用户模式下执行用户程序,
在内核模式下执行操作系统的代码,系统调用,中断和异常将引起模式切换 。
2.3.6.1 Unix SVR4的进程状态进程状态包括:
lUser running:在用户模式下运行 。
lKernel running:在内核模式下运行 。
lPreempted:当一个进程从内核模式返回用户模式时,发生了进程切换后处于的就绪状态 。
lReady to run,in memory:就绪状态,在内存 。
lAsleep in memory:等待状态,在内存 。
lReady to run,swapped:就绪状态,被对换出内存,
不能被调度执行 。
lSleeping,swapped:睡眠状态,被对换出内存 。
lZombie:终止状态:进程已不存在,留下状态码和有关信息使父进程收集 。
图 2-24 Unix SVR4进程状态及其转换
Ready to run,
in memory
User
running
Kernel
running
Preempted
Ready to run,
swapped
Created
Asleep
in memory
Sleep,
swappedZombie
Runturn to user
Interrupt
System call
Return
Preempt
Reschedule
Process
Exit
Interrupt,
Interrupt return
Enough
Memory
No enough
Memory
Wakeup Wakeup
Swap out
Swap in
Swap out
Sleep
Fork
Unix进程树
Unix操作系统中有两个固定的进程,0号进程是 swap进程,在系统自举时被创建;
1号进程是 init进程,由 0号进程孵化而创建 。 系统中的其他进程都是 1号进程的子进程,当一个交互式用户登录到系统中时,
1号进程为这个用户创建一个用户进程,
用户进程在执行具体应用是进一步的创建子进程,从而构成一棵进程树 。
2.3.6.2 Unix SVR4的进程描述
UNIX的进程由三部分组成:
proc结构、数据段和正文段,它们合称为进程映像,Unix中把进程定义为映像的执行。其中,PCB
由基本控制块 proc结构和扩充控制块 user结构两部
分组成。
在 proc结构里存放着关于一个进程的最基本,最必需的信息,因此它常驻内存;
在 user结构里存放着只有进程运行时才用到的数据和状态信息,为了节省内存空间,
当进程暂时不在处理机上运行时,就把它放在磁盘上的对换区中,进程的 user结构总和进程的数据段一起,在主存和磁盘对换区之间换进 /换出 。
图 2-25 Unix 进程组成
process
structure
user
structure
kernel
stack
text
structure
stack
data
text
System data structure
user space
Swappable process image
Resident tables
系统中维持一张名叫 proc的进程表,共有 50个表目,每个表目为一个 proc结构,供一个进程使用,
因此,UNIX中最多同时可存在 50
个进程 。 创建进程时,在 proc表中找一个空表目,以建立起相应于该进程的 proc结构 。
进程映像 内容
lproc结构 包括进程标识符,父进程标识符,进程用户标识符,进程状态,等待的事件,调度优先数,进程的大小,
指向 user结构和进程存储区 (text/data/stack)
的指针,有关进程执行时间 /核心资源使用 /用户设置示警信号等的计时器,就绪队列指针等 。
luser结构 包括现场保护,内存管理,系统调用,文件管理,文件读写,时间信息,映象位置,用户标识,用户组标识,
用户打开文件表,各种标志等 。
进程映像 内容
l系统数据结构 进程系统数据区,通常称作
ppda,它位于数据段的前面,进程 proc结构中的
P-addr指向这个区域的首址 。 该区共有 1024个字节,由两块内容组成:最前面的 289个字节为进程的扩充控制块 user结构,剩下的 734个字节为核心栈,当进程运行在核心态时,这里是它的工作区,用来保存过程调用和中断访问时用到的地址和参数 。
l用户数据区 通常存放程序运行时用到的数据,
如果进程运行的程序是非共享的,那么这个程序也放于此地 。
l用户栈区 当进程运行在用户态时,这里是它的工作区 。
进程映像 内容
l text结构 正文段在磁盘上和主存中的位置和大小,访问正文段进程数,在主存中访问正文段进程数,标志信息,地址转换信息 。
l 由于共享正文段在进程映象中的特殊性,为了便于对它们的管理,
UNIX系统在内存中设置了一张正文段表 。 该表共有 40个表目,每一个表目都是一个 text结构,用来记录一个共享正文段的属性 ( 磁盘和主存中的位置,尺寸,共享的进程数等,正文段文件节点指针 ),有时也把这种结构称为正文段控制 ( 信息 ) 块 。 这是可以被多个进程共享的可重入程序和常数,如果一个进程的程序是不被共享的,那么它的映象中就不出现这一部分 。 若一个进程有共享正文段,那么当把该进程的非常驻内存部分调入内存时,应该关注共享正文段是否也在内存,如果发现不在内存,则要将它调入;当把该进程的非常驻内存部分调出内存时,同样要关注它的共享正文段目前被共享的情况,只要还有一个别的共享进程的映象全部在内存,那么这个共享正文段就不得调出去 。 如果一个进程有共享正文段,那么该共享正文段在正文段表里一定有一个 text结构与之相对应,而在该进程的基本控制块 proc
里,p-textp就指向这一个 text结构 。 综上所述,在 UNIX进程映象的三个组成部分中,proc,user和 text这三个数据结构是最为重要的角色 。
进程映像 内容
l进程区域表 系统为每个进程建立一张进程区域表 PPRT( Per Process Region Table) 由存储管理系统使用,它定义了物理地址与虚拟地址之间的对应关系,还定义了进程对存储区域的访问权限 。 其中含有正文段,数据段和堆栈的区域表的指针和各区域的逻辑起始地址 ;区域表中含有该区域属性 (正文 /数据,可否共享 )
的信息和页表的指针 ;而每个页表中含有相应区域的页面在内存的起始地址 。
图 2-26 proc结构,user结构和 text结构关系
2.3.6.3 Unix SVR4的进程创建
Unix SVR4通过系统调用 fork( )来创建一个子进程 当父进程调用 pid=fork( )后,操作系统执行下面的操作:
l为子进程分配一个进程表 。
l为子进程分配一个进程标识符 。
l 复制父进程的进程映像,但不复制共享内存区 。
l 增加父进程所打开文件的计数,表示新进程也在使用这些文件 。
l把子进程置为 Ready to Run状态 。
l返回子进程的标识符给父进程,把 0值返回给子进程 。
以上的操作都是父进程在内核模式下完成的,然后父进程还应执行以下的操作之一,
以完成进程的指派:
l 继续呆在父进程中 。 此时把进程控制切换到父进程的用户模式,在 fork()点继续向下运行,而子进程进入 Ready to Run状态 。
l把进程控制传递到子进程,子进程在
fork()点继续向下运行,而父进程进入
Ready to Run状态 。
l 把进程控制传递到其他进程,父进程和子进程进入 Ready to Run状态 。
Unix如何用 fork( )来创建子进程的程序:
/*spawn the process*/
main( ) {
int pid; /*process-id*/
printf(“Just one process so far\n”);
printf(“Calling fork … \n);
pid=fork( ); /*Craat new process*/
if(pid==o)
printf (“I am the child process\n);
else if(pid>0)
printf(“I am the parent process,child has pid d%\n”,pid);
else
printf(“fork returned error code,no child\n”);
}
Fork( )调用前
Fork( )调用后子进程父进程父进程
Printf(“I am the child,…,)
Printf(“I am the
parent,…,)
1子进程是父进程的复制品,完全一样
2 父子进程的指令执行点都在 fork( )语句之后的那个语句
3 fork( )没有参数,但返回值 pid不一样,子进程中 pid为 0;父进程中 pid为非 0正整数 (即子进程的内部标识号 )
4 父子进程可根据不同 pid值执行不同程序,完成不同工作
2.3.7 实例研究,Linux进程管理
2.3.7.1 进程和进程状态
Linux的进程概念与传统操作系统中的进程概念完全一致,它目前不支持线程概念,进程是操作系统调度的最小单位 。
进程和任务
用户态称进程
核心态称任务实质上是一个实体
Linux的进程状态有 6种:
lTASK_RUNNING:正在运行或准备运行的进程 。
lTASK_INTERRUPTIBLE:处于等待队列中的进程,一旦资源可用时被唤醒,也可以由其他进程通过信号
( SIGNAL) 或定时中断唤醒 。
lTASK_UNINTERRUPTIBLE:处于等待队列中的进程,
一旦资源可用时被唤醒,也不可以由其他进程通过信号
( SIGNAL) 或定时中断唤醒 。
lTASK_ZOMBIE:进程运行结束但是尚未消亡时处于的状态 。
lTASK_STOPPED:进程被暂停,正在等待其他进程发出的唤醒信号 。
lTASK_SWAPPING:页面被交换出内存的进程 。
运行和就绪是一个队列
图 2-27 Linux的进程状态转换
TASK_RUNNING
TASK_UNINTERRUPTIBLE TASK_INTERRUPTIBLE
TASK_STOPPED TASK_ZOMBIE
占有 CPU
schedulle() 时间片到 申请资源未果,调用
interruptible_sleep_on()
申请资源未果,
调用 sleep_on()
申请资源可用后
wake_up()
申请资源可用,
收到信号,wake_up()、
wake_up_interruptible()
do_fork()
do_exit()跟踪系统调用,执行 syscall_trace(),
sys_exit(),schedulle()
收到 SIG_KILL或 SIG_CONT
后,执行 wake_up()
慢中断与快中断
有三点差别,
*寄存器保护
*屏蔽中断事件
*是否返回被中断的进程
Linux创建进程
创建进程的系统调用 sys_fork()和 sys_clone都通过调用 do_fork()函数来完成进程的创建 。 在 do_fork()
函数中,它首先分配进程控制块 task_struct的内存和进程所需的堆栈,并监测系统是否可以增加新的进程;然后拷贝当前进程的内容,并对一些数据成员进行初始化;再为进程的运行做准备;最后返回生成的新进程的进程标识号 ( pid) 。 如果进程是根据 sys_clone()产生的,那么它的进程标识号就是当前进程的进程标识号,并且对于进程控制块中的一些成员指针并不进行复制,而仅仅把这些成员指针的计数 count增加 1。 这样,父子进程可以有效地共享资源 。
进程终止
进程终止的系统调用 sys_exit()通过调用
do_exit()函数实现 。 函数 do_exit()首先释放进程占用的大部分资源,然后进入小
TASK_ZOMBIE状态,调用 exit_notify()
通知小 。
2.3.7.2 进程控制块
Linux的进程控制块由结构
struct task_struct描述,包括在 /include/linux/sched.h中 。
它的构成简述如下:
Linux进程控制块
调度用数据成员
State 进程状态
Flags 进程状态标记
Priority 进程优先数
rt_priority 实时进程优先数
Counter 时间片
Policy 调度策略 ( 0基于优先权的时间片轮转,1基于先进先出的实时调度,2基于优先数的实时调度 )
Linux进程控制块
信号处理
Signal 记录进程接收到的信号,共 32位,
每位对应一种信号
Blocked 进程接收到信号的屏蔽位
Sig 信号对应的处理函数
Linux进程控制块
进程队列指针
next_task,prev_task 双向链接指针
next_run,prev_run 就绪队列双向链接指针
p_opptr,p_pptr,p_cptr,p_ysptr,
p_osptr 分别指向原始父进程,父进程,
子进程,新老兄弟进程
Linux进程控制块
进程标识
uid,gid 用户标识和组标识
Group 允许进程同时拥有的一组用户组号
euid,egid 有效的 uid和 gid,用于系统安全考虑
fsuid,fsgid 文件系统的 uid和 gid
suid,sgid 系统调用改变 uid和 gid时,用于存放真正的 uid和 gid
pid,pgrp,session 进程标识号,组标识号,
session标识号
Leader 是否 session的主管
Linux进程控制块
时间数据成员
Timeout 指出进程间隔多久被重新唤醒
it_real_value,it_real_incr 用于时间片计时
real_timer 一种定时器结构
it_virt_value,it_virt_incr 进程用户态执行时间的软件定时
it_prof_value,it_prof_incr 进程执行时间的软件定时
utime,stime,cutime,cstime,start_time
进程在用户态,内核态的运行时间,所有层次进程在用户态,内核态的运行时间,创建进程的时间
Linux进程控制块
信号量
Semundo 进程每次操作信号量的 undo操作
Semsleeping 信号量对应的等待队列
上下文
Ldt 进程关于段式存储管理的局部描述符指针
Tss 通用寄存器
saved_kernel_stack 为 MSDOS仿真程序保存的堆栈指针
saved_kernel_page 内核堆栈基地址
文件系统
Fs 保存进程与 VFS的关系信息
Files 系统打开文件表
link_count 文件链的数目
Linux进程控制块
内存数据成员
世的 mm_struct结构
Swappable 指示页面是否可以换出
swap_address 换出用的地址
min_flt,maj_flt 该进程累计的缺页次数
Nswap 该进程累计换出的页面数
cmin_flt,cmaj_flt 该进程及其所有子进程累计的缺页次数
Cnswap 该进程及其所有子进程累计换出的页面数
swap_cnt 下一次循环最多可以换出的页数
Linux进程控制块
SMP支持
Processor 进程正在使用的 cpu
last_processor 进程上一次使用的 cpu
lock_depth 上下文切换时系统内核锁的深度
其他
used_math 是否使用浮点运算器
Comm 进程对应的可执行文件的文件名
Rlim 系统使用资源的限制
Errno 错误号
Debugreg 调试寄存器
Linux进程控制块
exec_domain,Personality 与运行 iBCS2标准程序有关
Binfmt 指向全局执行文件格式结构,包括
a.out,script,elf,java
exit_code,exit_signal 返回代码,出错信名
Dumpable 出错时是否能够进行 memory dump
did_exec 用于区分新老程序代码
tty_old_pgrp 进程显示终端所在的组标识
Tty 指向进程所在的终端信息
wait_chldexit 在进程结束需要等待子进程时处于的等待队列,
2.4 线程及其实现
2.4.1 引入多线程技术的动机
单线程 ( 结构 ) 进程 ( Single Threaded
Process)
多线程 ( 结构 ) 进程 ( Multiple Threaded
process)
单线程结构的进程,给并发程序设计效率带来了一系列新的问题,主要表现在:
l进程切换的开销大,频繁的进程调度将耗费大量处理器时间 。
l进程之间通信的代价大,每次通信均要涉及通信进程之间以及通信进程与操作系统之间的切换 。
l进程之间的并发性粒度较粗,并发度不高,过多的进程切换和通信使得细粒度的并发得不偿失 。
l不适合并行计算和分布并行计算的要求 。 对于多处理器和分布式的计算环境来说,进程之间大量频繁的通信和切换,会大大降低并行度 。
l不适合客户 /服务器计算的要求 。 对于 C/S结构来说,那些需要频繁输入输出并同时大量计算的服务器进程 (如数据库服务器,事务监督程序 )很难体现效率 。
线程的概念
如果说操作系统中引入进程的目的是为了使多个程序并发执行,以改善资源使用率和提高系统效率,那么,在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时空开销,
使得并发粒度更细,并发性更好 。 这里解决问题的基本思路是:把进程的两项任务分离开来,
单独处理,并由进程和线程分别完成 。 进程作为系统资源分配和保护的独立单位,它不需要频繁地切换 ;线程作为系统调度和分派的基本单位,会被频繁地调度和切换,在这种指导思想下,产生了线程的概念 。
MS-DOS支持单用户进程,进程是单线程的;
传统的 Unix支持多用户进程,每个进程也是单线程的 。
很多著名的操作系统都支持多线程 ( 结构 ) 进程,如,Solaris,Mach,SVR4,OS/390、
OS/2,WindowNT,Chorus等;
JAVA的运行引擎则是单进程多线程的例子 。 许多计算机公司都推出了自己的线程接口规范,
如 Solaris Thread接口规范,OS/2 Thread接口规范,Windows NT Thread接口规范等; IEEE也推出了多线程程序设计标准 POSIX 1003.4a,可以相信多线程技术在程序设计中将会被越来越广泛地采用 。
2.4.2 多线程环境中的进程与线程
2.4.2.1 多线程环境中的进程概念
图 2-28 单线程进程的内存布局和运行进程控制块进 程用户地址空间用户堆栈系统堆栈管理者执行序 列单线程进程 (模型 )
用户地址空间进程控制块用户堆栈系统堆栈图 2-29 管理和执行相分离的进程模型用户堆栈系统堆栈执行控制进 程进程控制块用户地址空间共 享执行序列管理者执行序列用户堆栈系统堆栈执行控制图 2-30 多线程进程的内存布局多线程进程 (模型 )
…
用户地址空间进程控制块线程控制块系统堆栈用户堆栈线程 1
线程控制块系统堆栈用户堆栈线程 N
多线程环境中进程的定义:
进程是操作系统中进行保护和资源分配的独立单位 。 它具有:
l一个虚拟地址空间,用来容纳进程的映像;
l对处理器,其他 (通信的 )进程,文件和
I/O资源等的存取保护机制 。
2.4.2.2 多线程环境中的线程概念线程则是指进程中的一条独立执行路径 ( 控制流 ),每个进程内允许包含多个并行执行的路径 ( 控制流 ),这就是多线程 。 线程是系统进行处理器调度的基本单位,同一个进程中的所有线程共享进程获得的主存空间和资源,但不拥有资源 。 线程具有:
l一个线程执行状态 ( 运行,就绪,… ) ;
l当线程不运行时,有一个受保护的线程上下文,
用于存储现场信息 。 所以,线程也可被看作是执行在进程内的一个独立的程序计数器 ;
l一个执行堆栈
l一个容纳局部变量的主存存储区 。
线程 具有以下特性:
l并行性:同一进程的多个线程可在一个 /多个处理器上并发或并行地运行 。
l共享性:同一个进程中的所有线程共享进程获得的主存空间和一切资源,因而,线程需要同步和通信机制 。
l动态性:线程也是程序在相应数据集上的一次执行,由创建而产生,至撤销而消亡,有其生命周期 。
进程支撑线程运行,为线程提供地址空间和各种资源,它封装了管理信息,包括对指令代码,全局数据和 I/O状态数据等共享部分的管理 。 线程封装了执行信息,具有并发性,包括对 CPU寄存器,执行栈
( 用户栈,内核栈 ) 和局部变量,过程调用参数,返回值等线程私有部分的管理 。
图 2-31 线程的内存布局进 程地址空间线程
1
共 享线 程空 间线程
2
线程
n
线程又称轻量进程 (Light weight Process)
因为,它运行在进程的上下文中,并使用进程的资源和环境 。 注意,系统调度的基本单位是线程而不是进程,
所以,每当创建一个进程时,至少要同时为该进程创建一个线程,否则该进程无法被调度执行 。
2.4.2.3 线程的状态
线程的关键状态有:运行,就绪和阻塞 。 另外,
线程的状态转换也类似于进程 。 挂起状态对线程是没有意义的,如果一个进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去 。
当处于运行态的线程阻塞时,对于某些线程实现机制,所在进程也转换为阻塞态,即使这个进程存在另外一个处于就绪态的线程;对于另一些线程实现机制,如果存在另外一个处于就绪态的线程,则调度该线程处于运行状态,否则进程才转换为阻塞态 。 显然前一种做法欠妥,
丢失了多线程机制的优越性,降低了系统的灵活性 。
2.4.2.4 线程管理和线程库
基本的线程控制原语有:
l孵化 ( Spawn),又称创建线程 。 当一个新进程被生成后,该进程的一个线程也就被创建 。 此后,该进程中的一个线程可以孵化同一进程中的其它线程,并为新线程提供指令计数器和变量 。 一个新线程还将被分配寄存器上下文和堆栈空间,并将其加入就绪队列 。
l封锁 ( Block),又称线程阻塞或等待 。 当一个线程等待一个事件时,将变成阻塞态,保护它的用户寄存器,程序计数器和堆栈指针等现场 。 处理器现在就可以转向执行其它就绪线程 。
l活化 ( Unblock),又称恢复线程 。 当被阻塞线程等待的事件发生时,线程变成就绪态或相应状态 。
l结束 ( Finish),又称撤销线程 。 当一个线程正常完成时,
便回收它占有的寄存器和堆栈等资源,撤销线程 TCB。 当一个线程运行出现异常时,允许强行撤销一个线程 。
线程库
很多基于多线程的操作系统和语言都提供了线程库,如 Mach的 C-threads和 Java线程库,供应用程序共享,支持应用程序创建,调度,和管理用户级线程的运行 。 线程库实质上是多线程应用程序的开发和运行支撑环境 。 一般地说,
线程库至少应提供以下功能的过程调用:孵化,
封锁,活化,结束,通信,同步,调度等 。 每个线程库应提供给用户级的 API编程使用 。
2.4.2.5 并发多线程程序设计的优点
l快速线程切换 。 进程具有独立的虚地址空间,以进程为单位进行任务调度,系统必须交换地址空间,切换时间长,而在同一进程中的多线程共享同一地址空间,因而,能使线程快速切换 。
l减少 ( 系统 ) 管理开销 。 对多个进程的管理 ( 创建,调度,终止等 ) 系统开销大,
如响应客户请求建立一个新的服务进程的服务器应用中,创建的开销比较显著 。 面对创建,终止线程,虽也有系统开销,但要比进程小得多 。
并发多线程程序设计的优点
l( 线程 ) 通信易于实现 。 为了实现协作,进程或线程间需要交换数据 。 对于自动共享同一地址空间的各线程来说,所有全局数据均可自由访问,不需什么特殊设施就能实现数据共享 。
而进程通信则相当复杂,必须借助诸如通信机制,消息缓冲,管道机制等设施,而且还要调用内核功能,才能实现 。
l并行程度提高 。 许多多任务操作系统限制用户能拥有的最多进程数目,如 Unix一般为 50个,
这对许多并发应用来说是不够的 。 而对多线程技术来说,一般可达几千个,基本上不存在线程数目的限制 。
并发多线程程序设计的优点
l( 线程 ) 通信易于实现 。 为了实现协作,进程或线程间需要交换数据 。 对于自动共享同一地址空间的各线程来说,所有全局数据均可自由访问,不需什么特殊设施就能实现数据共享 。
而进程通信则相当复杂,必须借助诸如通信机制,消息缓冲,管道机制等设施,而且还要调用内核功能,才能实现 。
l并行程度提高 。 许多多任务操作系统限制用户能拥有的最多进程数目,如 Unix一般为 50个,
这对许多并发应用来说是不够的 。 而对多线程技术来说,一般可达几千个,基本上不存在线程数目的限制 。
并发多线程程序设计的优点
l 节省内存空间 。 多线程合用进程地址空间,而不同进程独占地址空间,使用不经济 。
由于队列管理和处理器调度是以线程为单位的,
因此,多数涉及执行的状态信息被维护在线程组数据结构中 。 然而,存在一些影响到一个进程中的所有线程的活动,操作系统必须在进程级进行管理 。 挂起 ( Suspension) 意味着将主存中的地址空间对换到盘上,因为,在一个进程中的所有线程共享同一地址空间,此时,所有线程也必须进入挂起状态 。 相似地,终止一个进程时,所有线程应被终止 。
2.4.2.6 多线程技术的应用
l前台和后台工作 。 如在一个电子表格软件中,一个线程执行显示菜单和读入用户输入,同时,另一个线程执行用户命令和修改电子表格 。
lC/S应用模式 。 局域网文件 ( 网络 ) 服务器处理多个用户文件 ( 任务 ) 请求时,创建多个线程,若该服务器是多
CPU的,则同一进程中的多线程可同时运行在不同 CPU上 。
l加快执行速度 。 一个多线程进程在计算一批数据的同时,
读入设备 ( 网络,终端,打印机,硬盘 ) 上的下一批数据,
而这分别由两个线程实现 。
l设计用户接口 。 每当用户要求执行一个动作时,就建立一个独立线程来完成这项动作 。 当用户要求有多个动作时,
就由多个线程来实现,窗口系统应有一个线程专门处理鼠标的动作 。 例如,GUI中,后台进行屏幕输出或真正计算;
同时,要求对用户输入 ( 鼠标 ) 作出反映 。 有了多线程,
可用处理 GUI输入线程和后台计算线程,便能实现这一功能 。
2.4.3 线程的实现从实现的角度看,线程可以分成用户级线程 ULT(如,Java和 Informix)
和内核级线程 KLT(如 OS/2)。也有一些系统 (如,Solaris)提供了混合式线程,同时支持两种线程实现。
图 2-32给出了各种线程实现方法用户空间线程库
P
内核空间
2)用户级线程用户空间
P
内核空间
1)内核级线程用户空间线程库
P P
内核空间
3)混合式线程
ULT KLT ProcessP
2.4.3.1内核级线程
在纯内核级线程设施中,线程管理的所有工作由操作系统内核来做 。 内核专门提供了一个 KLT(Kernel Level Threads)应用程序设计接口 ( API),供开发者使用,应用程序区不需要有线程管理的代码 。 Windows NT 和 OS/2都是采用这种方法的例子 。
任何应用都可以被程序设计成多个线程,
当提交给操作系统执行时,内核为它创建一个进程和一个线程,线程在执行中可以通过内核创建线程和原语来创建其他线程,
这个应用的所有线程均在一个进程中获得支持 。 内核要为整个进程及进程中的单个线程维护现场信息,所以,应在内核中建立和维护 PCB及 TCB,内核的调度是在线程的基础上进行的 。
有内核级线程主要优点,
首先,在多个处理器上,内核能够同时调度同一进程中多个线程并行执行;
其次,若进程中的一个线程被阻塞了,内核能调度同一进程的其它线程占有处理器运行 。
最后,由于内核线程仅有很小的数据结构和堆栈,KLT的切换也不需要改变内存信息,切换比较快,内核自身也可以用多线程技术实现,
从而,能提高系统的执行速度和效率 。
KLT的主要缺点是:
应用程序线程在用户态运行,而线程调度和管理在内核实现,在同一进程中,控制权从一个线程传送到另一个线程时需要用户态 -内核态 -用户态的模式切换,
系统开销较大 。
2.4.3.2 用户级线程
纯 ULT(User Level Threads)设施中,线程管理的全部工作都由应用程序来做,内核是不知道线程的存在的 。 用户级多线程由线程库来实现,
任何应用程序均需通过线程库进行程序设计,
再与线程库连接后运行来实现多线程 。 线程库是一个 ULT管理的例行程序包,它包含了建立
/撤销线程的代码,在线程间传送消息和数据的代码,调度线程执行的代码,以及保护和恢复线程上下文的代码,实质上线程库是线程的运行支撑环境 。
线程“孵化”过程
当一个应用程序提交给系统后,系统为它建立一个由内核管理的进程,该应用程序在线程库环境下开始运行时,只有一个由线程库为进程建立的线程 。 首先运行这个线程,当应用进程处于运行状态时,线程通过调用线程库中的,孵化,过程,可以孵化出运行在同一进程中的新线程,步骤如下:通过过程调用把控制权传送给这个,孵化,过程,线程库为新线程创建一个
TCB数据结构,并置为就绪态,由线程库按一定的调度算法把控制权传送给进程中处于就绪态的一个线程 。
当控制权传送到线程库时,当前线程的现场信息被保存,而当控制权由库传送给线程时,便恢复它的现场信息 。 现场信息主要包括:用户寄存器内容,程序指令计数器,堆栈指针 。
线程调度和进程调度之间的关系
假设进程 B正在执行它的线程 3,则可能出现下列情况:
l正在执行的进程 B的线程 3发出了一个封锁 B的系统调用,例如,做了一个 I/O操作,通知内核进行 I/O并将进程 B置为等待状态,按照由线程库所维护的数据结构,
进程 B的线程 3仍然处在运行态 。 十分重要的是线程 3并不实际地在一个处理器上运行,而是可理解为在线程库的运行态中 。 这时,进程 B为等待态,但线程为线程库运行态 。
l一个时钟中断传送控制给内核,内核中止当前时间片用完的进程 B,并把它放入就绪队列,切换到另一个就绪进程,此时,按由线程库维护的数据结构,进程 B的线程 3仍处于运行态 。 这时,进程 B己处于就绪态,但线程为线程库运行态 。
上述两种情况中,当内核切换控制权返回到进程 B时,便恢复执行线程 3。注意到当正在执行线程库中的代码时,一个进程也有可能由于时间片用完或被更高优先级的进程剥夺而被中断。当中断发生时,一个进程可能正处在从一个线程切换到另一个线程的过程中间;当一个进程恢复时,继续在线程库中执行,完成线程切换,并传送控制权给进程中的一个新线程。
ULT优点:
l线程切换不需要内核特权方式,因为,所有线程管理数据结构均在单个进程的用户空间中,管理线程切换的线程库也在用户地址空间运行,因而,进程不要切换到内核方式来做线程管理 。 这就节省了模式切换的开销,也节省了内核的宝贵资源 。
l按应用特定需要来调度,一种应用可能从简单轮转调度算法得益,同时,另一种应用可能从优先级调度算法获得好处 。 在不干扰 OS调度的情况下,根据应用需要可以裁剪调度算法,也就是说,线程库的线程调度算法与操作系统的进程调度算法是无关的 。
lULT能运行在任何 OS上,内核支持 ULT方面不需要做任何改变 。 线程库是可以被所有应用共享的应用级实用程序,许多当代操作系统和语言均提供了线程库,传统 Unix
并不支持多线程,但已有了多个基于 Unix的用户线程库 。
ULT有二个明显的缺点:
l 在传统的基于进程操作系统中,大多数系统调用将阻塞进程,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞 。
l 在纯 ULT中,多线程应用不能利用多重处理的优点 。 内核在一段时间里,分配一个进程仅占用一个 CPU,因而进程中仅有一个线程能执行 。 因此尽管多道程序设计能够明显地能加快应用处理速度,也具备了在一个进程中进行多线程设计的能力,但我们不可能得益于并发地执行一部分代码 。
克服上述问题的方法有两种一是用多进程并发程序设计来代替多线程并发程序设计,这种方法事实上放弃了多线程带来的所有优点 。 第二种方法是采用 jacketing技术来解决阻塞线程的问题 。 主要思想是把阻塞式的系统调用改造成非阻塞式的,当线程调用系统调用,
首先调用 jacketing实用例程,来检查资源使用情况,以决定是否执行系统调用或传递控制权给另一个线程 。
2.4.3.3 混合式线程
在混合系统中,内核支持 KLT多线程的建立,调度和管理,同时,也提供线程库,允许用户应用程序建立,调度和管理 ULT。
一个应用程序的多个 ULT映射成一些 KLT,
程序员可按应用需要和机器配置调整 KLT
数目,以达到较好效果 。
混合式中,一个应用中的多个线程能同时在多处理器上并行运行,且阻塞一个线程时并不需要封锁整个进程 。 如果设计得当的话,则混合式多线程机制能够结合了两者优点,并舍去它们的缺点 。
2.4.4 实例研究,Solaris的进程与线程
2.1.1.1 Solaris中的进程与线程概念
Solaris采用了 4个与进程和线程有关的概念,用来支持完全可抢占,SMP,和核心多线程结构:
l 进程 ( Process),通常的 Unix进程,它包含用户的地址空间和 PCB。
l 用户级线程 ( User-Level Threads),通过线程库在用户地址空间中实现,对操作系统来讲是不可见的,用户级线程 ( ULT) 是应用程序并行机制的接口 。
l轻量进程 ( Light Weight Process),一个 LWP可看作是
ULT和 KLT之间的映射,每个 LWP支持一个或多个 ULT并映射到一个 KLT上 。 LWP是核心独立调度的单位,它可以在多个处理器上并行执行 。
l内核级线程 ( Kernel-Level Threads),即 KLT,它们是能被调度和指派到处理器上去运行的基本实体 。
Solaris的线程实现分为二个层次:
用户层和核心层,用户层在用户线程库中实现;
核心层在操作系统内核中实现 。 处于两个层次中的线程分别叫用户级线程和内核级线程 。
ULT是一个代表对应线程的数据结构,它是纯用户级的概念,占用用户空间资源,对核心是透明的 。 ULT和 KLT不是一一对应,通过轻量级进程 LWP来映射两者之间的联系 。 一个进程可以设计为一个或多个 LWP,在一个 LWP上又可以开发多个 ULT,LWP与 ULT一样共享进程的资源 。 KLT和 LWP是一一对应的,一个 ULT要通过核心 KLT和 LWP二级调度后才真正占有处理器运行 。
有了 LWP,就可以在用户级实现 ULT,每个进程可以创建几十个 ULT,而又不占用核心资源 。 由于 ULT共享用户空间,因此当 LWP在一个进程的不同 ULT间切换时,
仅是数据结构的切换,其时间开销远低于两个 KLT间的切换时间 。 同样线程间的同步也独立于核心的同步体系,在用户空间中独立实现,而不陷入内核 。 核心能看见的是 LWP,而 ULT是透明的 。
多线程的程序员接口包括二个层次:
一层为线程接口,提供给用户,以编写多线程应用程序,这一层由动态连接库引用 LWP实现,线程库在 LWP之上调度线程,同步也在线程库实现 。 第二层为 LWP接口,提供给线程库,以管理 LWP。 这一层由核心通过 KLT实现 。 LWP通过这些接口访问核心资源 。
多线程的两个程序员接口是类似的,线程接口的切换代价较小,特别适用于解决逻辑并行性问题;而
LWP接口则适用于那些需要在多个处理器进行并行计算的问题。如果程序设计得当的话,混合应用两种程序员接口可以带来更大的灵活性和优越性。
图 2-33 Solaris线程的使用硬件线程库
L PULT KLT LWP Processor
L L L LL L L L L
PPPPP
用户内核进程 1 进程 2 进程 3 进程 4 进程 5
图中,进程 1是传统的单线程进程,当应用不需要并发运行时,可以采用这种结构 。
进程 2是一个纯的 ULT应用,所有的 ULT由单个内核线程支撑,因而,某一时刻仅有一个线程可运行 。 对于那些在程序设计时要表示并发而不真正需要多线程并行执行的应用来说,这种方式是有用的 。
进程 3是多线程与多 LWP的对应,Solaris允许应用程序开发多个 ULT工作在小于或等于 ULT个数的 LWP上 。
这样应用可以定义进程在内核级上的某种程度的并行 。
进程 4的线程与 LWP是一对一地捆扎起来的,这种结构使得内核并行机制完全对应用可见,这在线程常常因阻塞而被挂起时是有用的 。
进程 5包括多 ULT映射到多 LWP上,以及 ULT与 LWP
的一一捆绑,并且还有一个 LWP捆在单个处理器上 。
2.4.4.2 Solaris的进程结构
在 Solaris中已经有了进程,KLT和 ULT,为什么还要引入 LWP?我们先来讨论这个问题 。 最主要的原因是各种应用的需求,有些应用逻辑并行性程度高,有些应用物理并行性要求高 。 像窗口系统是典型的逻辑并行性程度高的应用,同时在屏幕上开出了多个窗口,窗口切换很频繁,但一个时刻仅有少数窗口处于活跃状态 。 我们可以用一组 ULT来表达窗口系统,用一个或很少几个 LWP
来支持这一组 ULT,可根据活跃窗口数目调正
LWP的个数 。 由于 ULT是由用户线程库实现的,
他们的管理不涉及内核 ;内核仅要管理与 ULT对应的一个或几个 LWP,系统开销小,窗口系统的效率高 。
大规模并行计算是物理并行性要求高的应用,可以把数组按列划分给不同的
ULT线程处理,如果每个 CPU对应一个
LWP,而一个 LWP对应多个 ULT,那么,
CPU有很多时间化在线程切换上 。 这种情况下,最好把列分给少量 ULT,而一个 ULT和一个 LWP绑定,以减少线程切换次数,提高并行计算的效率 。
Solaris中进程 (包括不同数目的轻量进程 ),进程结构如图 2-34
一内存分配表进程标识符用户标识符信号分配表文件描述符轻进程标识符优先数信号掩码寄存器堆栈
……
轻进程标识符优先数信号掩码寄存器堆栈
……
轻量进程 1 轻量进程 2
LWP的数据结构包括:
l轻量进程标识符标识了轻量进程
l 优先数定义了轻量进程执行的优先数
l 信号掩码定义了内核能够接受的信号
l寄存器域用于存放轻量进程让出处理器时的现场信息
l 轻量进程的内核栈包括每个调用层次的系统调用的参数,
返回值和出错码
l 交替的信号堆栈
l 用户,或用户和系统共同的虚时间警示
l 用户时间和系统处理器使用
l 资源使用和预定义数据
l 指向对应内核线程的指针
l指向进程结构的指针
轻量进程实际上不是进程,只是一个内核支持的 ULT,因此它有一个内核堆栈,都有一个对应的 KLT支持 。 LWT被独立调度,
进程中的 LWP共享进程地址空间和资源,
一个进程由于有多个 LWP,才有可能分得多个 CPU,才能实现物理上的并行性 。
ULT的数据结构包括:
l线程标识符标识了线程
l优先数定义了线程执行的优先数
l 信号掩码定义了能够接受的信号
l 寄存器域用于存放线程让出处理器时的现场信息
l 堆栈用于存放线程运行数据
l 线程局部存储器用于存放线程局部数据
KLT的数据结构 (每个 LWP对应一个 ) 是由内核创建的线程,每个 KLT有一个堆栈和一个数据结构,包括:
l 内核寄存器数据保存区
l 优先级和调度信息
l KLT的队列指针和堆栈指针
l 相关 LWP的指针及信息
l 进程数据结构,包括:
l 与进程相关的 KLT
l 进程地址空间指针
l 用户权限表
l 信号处理程序清单
l 与用户执行有关信息
Solaris支持实时类进程,其内核是可抢占的,它的内核函数和过程全部用线程来实现,所以,Solaris的内核是 KLT的集合 。 这些 KLT分两种,一种是负责执行一个指定的内核函数 ;另一种用来支持和运行 LWP,KLT是独立被调度和分配到 CPU上去执行 。
2.4.4.3 Solaris的线程状态继续 剥夺停止 睡眠
Sleeping
睡眠态
Runnable
可运行态
Active
活跃态
Stopped
停止态停止 唤醒指派停止指派唤醒 停止
Stopped
停止态
Running
运行态
Blocked
阻塞态
Runnable
可运行态时间片到或剥夺 唤醒停止阻塞系统调用继续
LWP
ULT
用户级线程的执行是由线程库管理的,让我们首先考虑非捆绑的线程,即多个 ULT可共享 LWP,线程可能处于四个状态之一:可运行,活跃,睡眠和停止 。 处在活跃状态的一个 ULT是指目前分配到 LWP上,并且在对应的内核线程 KLT执行时,它被执行 。 出现许多事件会导致 ULT离开活跃态,让我们考虑一个活跃的 ULT正在执行,下面的事件可能发生:
l同步:有一个 ULT调用了一条同步原语与其它线程协调活动 。 于是它就进入睡眠态,直到同步条件满足后,这个
ULT才被放入可运行队列 。
l挂起:任何线程可挂起它线程 ;一个线程也可挂起自己并进入停止状态 。 直到别的线程发出一个让挂起线程继续运行的请求,再把该线程移入可运行队列 。
l剥夺:一个活跃线程在执行时,另一个更高优先级的线程变为可运行状态,如果处于活跃状态的运行线程优先级较低,它就要被剥夺并放入可运行状态,而更高优先级的线程被分配到可用的 LWP上执行 。
l让位:若运行线程执行了 thread_yield( ) 库命令,库中的线程调度程序将查看是否有另一个可运行线程,它与当前执行的线程具有相同的优先级,则把执行线程放入可运行队列,另一个可运行线程分配到可用 LWP上 ;否则原线程继续运行 。
图 2-35还显示了 LWP的状态转换图我们可以把它看作是 ULT活跃状态的详尽描述,因为当一个 ULT处于活跃状态时只能捆绑到一个 LWP上 。 只有当 LWP处于运行状态时,一个处于活跃状态的 ULT才真正的处于活跃状态并执行 。 当一个处于活跃状态的 ULT调用一个阻塞的系统调用时,
则它对应的 LWP进入阻塞状态,这个 ULT
将继续处于活跃状态并继续与相应的 LWP
绑定,直到线程库显式地做出变动 。
Solaris提供了以下线程操作供用户线程程序设计。
thread_create()创建一个新的线程
thread_setconcurrency()置并发程度 ( 即 LWP的数目 )
thread _exit()终止当前进程,收回线程库分配给它的资源
thread_wait()阻塞当前线程,直到有关线程退出
thread_get_id()获得线程标识符
thread_sigsetmask(),thread_sigprocmask()置线程信号掩码
Thread_kill()生成一个送给指定线程的信号
Thread_stop()停止一个线程的执行
Thread_priority()置一个线程的优先数
2.4.5 实例研究,Windows 2000的进程与线程
2.4.5.1 Windows 2000中的进程与线程概念
Windows2000包括三个层次的执行对象:进程,
线程和作业 。 其中
作业是 Windows2000是共享一组配额限制和安全性限制的进程的集合 ;
进程是相应于一个应用的实体,它拥有自己的资源,如主存,打开的文件;
线程是顺序执行的工作调度单位,它可以被中断,使 CPU能转向另一线程执行 。
Windows 2000进程设计目标是提供对不同操作系统环境的支持,具有:多任务
(多进程 ),多线程,支持 SMP,采用了 C/S模型,能在任何可用 CPU上运行的特点 。 由内核提供的进程结构和服务相对来说简单,适用,
其重要的特性如下:
l作业,进程和线程是用对象来实现的 。
l 一个可执行的进程可以包含一个或多个线程 。
l进程或线程两者均有内在的同步设施 。
Windows 2000核心态结构:
核心态组件运行在核心地址空间中,同括以下几个部分:
1 执行体
2 核心
3 设备驱动程序
4 图形引擎
5 硬件抽象层
Windows采用 OO技术实现系统
对象,
对象类型,
属性和方法,
封装,继承,
消息
Windows 2000对象分类:
1 内核对象
2 执行体对象
执行体有一个对象管理器对象结构:
对象头
对象体
Windows 2000中进程作为对象来管理
进程对象,它具有属性和共性方法
进程结构图 2-36 进程以及控制和使用的资源
Object Table
Virtual Address Space Description
Thread x
File y
Section z
…
Process
Access
Token
Available Objects
Handle 1
Handle 2
Handle 3
当一个用户首次注册时,Windows2000为用户建立一个访问令牌 Access Token,它包括安全标识和进程凭证 。 这个用户建立的每一个进程均有这个访问令牌的拷贝 。 内核使用访问令牌来验证用户是否具有存取安全对象或在系统上和安全对象上执行受限功能的能力 。 访问令牌还控制了进程能否改变自己的属性,在这种情况下,进程没有获得它的访问令牌的句柄,进程若想打开它,安全系统首先决定这是否允许,进而决定进程能否改变自己的属性 。
与进程有关的还有一组当前分配给这个进程的虚拟地址空间块,进程不能直接改它,必须依靠为进程提供内存分配服务的虚存管理例程 。
进程包括一个对象表,其中包括了这个进程与其它使用资源之间的联系,通过对象句柄便可对某资源进行引用,存取令牌控制进程是否能改变其自身属性。图 2-
35包含了一个线程对象,这个线程可以访问一个文件对象和一个共享内存区对象,有一系列分给进程的虚拟地址空间块。
在 Window2000中,对象 (object)是对象类的实例,对象类是实现系统功能的操作和私有变量的封装体;句柄则是对打开了的对象实例 的引用。
2.4.5.2 进程对象
进程是由一个通用结构的对象来表示的 。 每个进程由属性和封装了的若干可以执行的动作和服务所定义 。 当接受到适当消息时,进程就执行一个服务,只能通过传递消息给提供服务的进程对象来调用这一服务 。 用户使用进程对象类 ( 或类型 ) 来建立一个新进程,对进程来说,
这个对象类被定义作为一个能够生成新的对象实例的模板,并且在建立对象实例时,属性将被赋值 。
每一个进程都由一个执行体进程 ( EPROCESS)
块表示 。 下面给出了 EPROCESS的结构,它不仅包括进程的许多属性,还包括并指向许多其它相关的属性,如每个进程都有一个或多个执行体线程 ( ETHREAD) 块表示的线程 。 除了进程环境块 ( PEB) 存在于进程地址空间中以外,EPROCESS块及其相关的其它数据结构存在于系统空间中 。 另外,WIN32子系统进程
CSRSS为执行 WIN32程序的进程保持一个平行的结构,该结构存在于 WIN32子系统的核心态部分 WIN32K.SYS,在线程第一次调用在核心态实现的 WIN32 USER或 GDI函数时被创建 。
内核进程块进程标识符父进程标识符退出状态创建和退出次数指向下一个进程的指引元配额块内存管理信息异常端口调试程序端口主访问令牌指引元局柄表指引元进程环境块映像文件名映像基址进程特权级
WIN32进程块指引元
EPROCESS块中的有关项目的内容如下:
l内核进程块 KRPROCESS:公共调度程序对象头,指向进程页面目录的指针,线程调度默认的基本优先级,时间片,相似性掩码,用于进程中线程的总内核和用户时间 。
l进程标识符等:操作系统中唯一的进程标识符,父进程标识符,运行映像的名称,进程正在运行的窗口位置 。
l配额块:限制非页交换区,页交换区和页面文件的使用,进程能使用的 CPU时间 。
l虚拟地址空间描述符 VAD:一系列的数据结构,描述了存在于进程地址空间的状态 。
l工作集信息:指向工作集列表的指针,当前的,峰值的,最小的和最大的工作集大小,上次裁剪时间,页错误计数,内存优先级,交换出标志,页错误历史纪录 。
l虚拟内存信息:当前和峰值虚拟值,页面文件的使用,用于进程页面目录的硬件页表入口 。
l异常 /调试端口:当进程的一个线程发生异常或引起调试事件时,进程管理程序发送消息的进程间通信通道 。
l访问令牌:指明谁建立的对象,谁能存取对象,谁被拒绝存取该对象 。
l句柄表:整个进程的句柄表地址 。
l 进程环境块 PEB:映像基址,模块列表,线程本地存储数据,代码页数据,临界区域超时,
堆栈的数量,大小,进程堆栈指针,GDI共享的句柄表,操作系统版本号信息,映像版本号信息,映像进程相似性掩码 。
lWIN32子系统进程块,WIN32子系统的核心组件需要的进程细节 。
操作系统还提供了一组用于进程的 WIN32函数:
l CreateProcess:使用调用程序的安全标识,创建新的进程和线程 。
l CreateProcessAsUser:使用交替的安全标识,
创建新的进程和线程,然后执行指定的 EXE。
lOpenProcess:返回指定进程对象的句柄 。
l ExitProcess:退出当前进程 。
lTerminateProcess:终止进程 。
l FlushInstructionCache:清空另一个进程的指令高速缓存 。
lGetProcessTimes:得到另一个进程的时间信息,描述进程在用户态和核心态所用的时间 。
lGetExitCodeProcess:返回另一个进程的退出代码,指出关闭这个进程的方法和原因 。
lGetCommandLine:返回传递给进程的命令行字符串 。
lGetCurrentProcessID:返回当前进程的 ID。
lGetProcessVersion:返回指定进程希望运行的 Windows
的主要和次要版本信息 。
lGetStartupInfo,返回在 CreateProcess 时 指 定 的
STARTUPINFO结构的内容 。
lGetEnvironmentStrings:返回环境块的地址 。
lGetEnvironmentVariable:返回一个指定的环境变量 。
lGetProcessShutdownParameters:取当前进程的关闭优先级和重试次数 。
lSetProcessShutdownParameters,置当前进程的关闭优先级和重试次数 。
调用 CreateProcess函数创建一个 WIN32进程。
创建的 WIN32进程的过程在操作系统的 3个部分中分阶段完成,这 三 个 部 分 是,WIN32 客户方的
KERNEL32.DLL,Windows2000执行体和 WIN32子系统进程 CSRSS。 具体步骤如下:
l打开将在进程中被执行的映像文件 (,EXE) 。
l创建 Windows2000执行体进程对象 。
l创建初始线程 ( 堆栈,描述表,执行体线程对象 ) 。
l通知 WIN32子系统已经创建了一个新的进程,以便它可以设置新的进程和线程 。
l 启 动 初 始 线 程 的 执 行 ( 除 非 指 定 了
CREATE_SUSPENDED标志 ) 。
l在新进程和线程的描述表中,完成地址空间的初始化,
加载所需的 DLL,并开始程序的执行
2.4.5.3 线程对象
ETHRED块中的有关项目的内容如下:
l创建和退出时间:线程的创建和退出时间 。
l进程识别信息:进程标识符和指向 EPROCESS的指引元 。
l线程启动地址:线程启动例程的地址 。
lLPC消息信息:线程正在等待的消息 ID和消息地址 。
l挂起的 I/O请求:挂起的 I/O请求数据包列表 。
l调度程序头信息:指向标准的内核调度程序对象 。
l执行时间:在用户态运行的时间总计和在核心态运行的时间总计 。
l内核堆栈信息指引元:内核堆栈的栈底和栈顶信息 。
l系 统 服 务 表 指 引 元,指 向 系 统 服 务 表 的 指 针 。
l调度信息:基本的和当前的优先级,时间片,相似性掩码,首选处理器,调度状态,冻结计数,挂起计数 。
图 2-37 ETHRED块的结构内核线程块创建和退出时间进程标识符指向 EPROCESS的指引元线程启动地址主访问令牌指引元模拟信息
LPC消息信息定时器信息挂起的 I/O请求调度程序头信息用户态时间总计核心态时间总计内核堆栈信息指引元系统服务表指引元线程调度信息陷阱帧线程本地存储数组同步信息搁置的 APC列表定时器块和等待块线程正在等待的对象列表线程环境块指引元操作系统提供一组用于线程的 WIN32函数:
lCreateThread:创建新线程 。
lCreateRemoteThread:在另一个进程创建线程 。
lExitThread:退出当前线程 。
lTerminateThread:终止线程 。
lGetExitCodeThread:返回另一个线程的退出代码 。
lGetThreadTimes:返回另一个线程的定时信息 。
lGetThreadSelectorEntry:返回另一个线程的描述符表入口 。
lGetThreadContext:返回线程的 CPU寄存器 。
lSetThreadContext:更改线程的 CPU寄存器 。
调用 CreateThread函数创建一个 WIN32线程的具体步骤如下:
l在进程地址空间为线程创建用户态堆栈 。
l初始化线程的硬件描述表 。
l 调用 NtCreateThread创建处于挂起状态的执行体线程对象 。 包括:增加进程对象中的线程计数,创建并初始化执行体线程块,为新线程生成线程 ID,从非页交换区分配线程的内核堆栈,设置 TEB,设置线程起始地址和用户指定的 WIN32 起始地址,调用 KeInitializeThread 设置
KTHREAD块,调用任何在系统范围内注册的线程创建注册例程,把线程访问令牌设置为进程访问令牌并检查调用程序是否有权创建线程 。
l通知 WIN32子系统已经创建了一个新的线程,以便它可以设置新的进程和线程 。
l线程句柄和 ID被返回到调用程序 。
l 除非调用程序用 CREATE_SUSPEND标识设置创建线程,
否则线程将被恢复以便调度执行 。
线程是 Windows2000操作系统的最终调度实体,
如图 2-38所示,它可能处于以下 6个状态之一:
l 就绪态 ——可被调度去执行的状态,微内核的调度程序维护所有就绪线程队列,并按优先级次序调度 。
l准备态 ——已被选中下一个在一个特定处理器上运行 。
此线程处于该状态等待直到该处理器可用 。 如果准备态线程的优先级足够高,则可以从正在运行的线程手中抢占处理器,否则将等待直到运行线程等待或时间片用完 。
l运行态 ——每当微内核执行进程或线程切换时,准备态线程进入运行态,并开始执行,直到它被剥夺,或用完时间片,或阻塞,或终止为止 。 在前两种情况下,线程进入到就绪态 。
l等待态 ——线程进入等待态是由于以下原因:
1) 出现了一个阻塞事件 ( 如,I/O) ; 2) 等待同步信号; 3) 环境子系统要求线程挂起自己 。
当等待条件满足时,且所有资源可用,线程就进入就绪态态 。
l过渡态 ——一个线程完成等待后准备运行,但这时资源不可用,就进入过渡态,例如,线程的堆栈被调出内存 。 当资源可用时,过渡态线程进入就绪态 。
l终止态 ——线程可被自己、其它线程、或父进程终止。一旦结束工作完成后,线程就可从系统中移去,或者保留下来以备将来初始化再用。
图 2-38 Windows2000线程状态资源可用事件完成但资源不可用事件完成资源可用阻塞挂起终止可运行
Running
运行态不可运行
Standby
准备态
Waiting
等待态
Ready
就绪态
Terminated
中止态
Transition
过渡态选中切换抢占或时间片到
2.4.5.4 作业对象
作业对象是一个可命名的,保护,共享的对象,它能够控制与作业有关的进程属性 。 作业对象的基本功能是允许系统将进程组看作是一个单元,对其进行管理和操作 。 有些时候,作业对象可以补偿 NT4在结构化进程树方面的缺陷 。 作业对象也为所有与作业有关的进程和所有与作业有关但已被终止的进程纪录基本的账号信息 。
作业对象包含一些对每一个与该作业有关的进程的强制限制
用户也能够在作业中的进程上设置安全性限制
用户也能够在作业中的进程上设置用户接口限制
一个进程只能属于一个作业,一旦进程建立,它与作业的联系便不能中断;所有由进程创建的进程和它们的后代也和同样的作业相联系 。 在作业对象上的操作会影响与作业对象相联系的所有进程 。
有关作业对象的 WIN32函数包括:
lCreateJobObject:创建作业对象 。
lOpen JobObject:通过名称打开现有的作业对象 。
lAssignProcessToJobObject:添加一个进程到作业 。
lTerminateJobObject:终止作业中的所有进程 。
lSetInformationToJobObject:设置限制 。
lQueryInformationToJobObject:获取有关作业的信息,
如,CPU时间,页错误技术,进程的数目,进程 ID列表,
配额或限制,安全限制等 。
2.5 处理机调度
2.5.1 处理机调度的层次
l 高级调度 (High Level Scheduling):又称作业调度,长程调度 (Long-term Scheduling)。 它将按照系统预定的调度策略决定把后备队列作业中的哪些作业调入主存,为它们创建进程并启动它们运行 。 在批处理操作系统中,作业首先进入系统在辅存上的后备作业队列等候调度,
因此,作业调度是必须的 。 在纯粹的分时或实时操作系统中,通常不需要配备作业调度 。
l中级调度 (Medium Level Scheduling):又称平衡负载调度,中程调度 (Medium-term
Scheduling)。 它决定主存储器中所能容纳的进程数,这些进程将允许参与竞争处理器资源 。 而有些暂时不能运行的进程被调出主存,这时这个进程处于挂起状态,当进程具备了运行条件,
且主存又有空闲区域时,再由中级调度决定把一部分这样的进程重新调回主存工作 。 中级调度根据存储资源量和进程的当前状态来决定辅存和主存中的进程的对换 。
l 低级调度 (Low Level Scheduling):又称进程调度,短程调度 (Short_term Scheduling)。 它的主要功能是按照某种原则决定就绪队列中的哪个进程或内核级线程能获得处理器,并将处理机出让给它进行工作 。
图 2-39 调度的层次中级调度新建态挂起就绪态挂起等待态高级调度低级调度运行态就绪态等待态终止态图 2-40 处理器调度与进程状态转换高级调度中级调度中级调度低级调度运行态就绪态终止态新建态挂起就绪态中级调度挂起等待态等待态高级调度高级调度
2.5.2 高级调度
对于分时操作系统来说,高级调度决定:
1)是否接受一个终端用户的连接;
2)一个程序能否被计算机系统接纳并构成进程;
3)一个新建态的进程是否能够加入就绪进程队列。有的分时操作系统虽没有配置高级调度程序,
但上述的 中级调度
在多道批处理操作系统中,作业是用户要求计算机系统完成的一项相对独立的工作 。 高级调度的功能是按照某种原则从后备作业队列中选取作业进入主存,
并为作业做好运行前的准备工作和作业完成后的善后工作 。 当然一个程序能否被计算机系统接纳并构成可运行进程也作高级调度的一项任务 。 图 2-41给出了批处理操作系统的调度模型 。
中级调度处理器低级调度高级调度完成超时挂起就绪队列挂起等待队列等待队列就绪队列等待事件交互式用户事件出现后备作业队列中级调度
2.5.3 中级调度
很多操作系统为了提高内存利用率和作业吞吐量,专门引进了中级调度 。 中级调度决定那些进程被允许参与竞争处理器资源,
起到短期调整系统负荷的作用 。 它所使用的方法是通过把一些进程换出主存,从而使之进入,挂起,状态,不参与进程调度,
起到滑系统的作用 。 有关中级调度的详细内容参见第三章中有关,进程挂起,小节 。
2.5.4 低级调度
它的主要功能是按照某种原则把处理器分配给就绪进程或内核级线程 。 进程调度程序是操作系统最为核心的部分,进程调度策略的优劣直接影响到整个系统的性能 。
2.5.5 选择调度算法的原则
调度算法所要达到的目标
l资源利用率 ——使得 CPU或其他资源的使用率尽可能高且能够并行工作 。
l响应时间 ——使交互式用户的响应时间尽可能短,或尽快处理实时任务 。
l周转时间 ——批处理用户从作业提交给系统开始到作业完成获得结果为止这段时间间隔称作业周转时间,应该使作业周转时间或作业平均周转时间尽可能短 。
l吞吐量 ——使得单位时间处理的作业数尽可能多 。
l公平性 ——确保每个用户每个进程获得合理的 CPU份额或其他资源份额 。
作业周转时间
如果作业 i提交给系统的时刻是 ts,完成时刻是 tf,那么该作业的周转时间 ti为:
ti = tf - ts
实际上,它是作业在系统里的等待时间与运行时间之和 。
平均作业周转时间
从操作系统来说,为了提高系统的性能,
要让若干个用户的平均作业周转时间和平均带权周转时间最小 。
平均作业周转时间 T = (Σ ti) / n
作业带权周转时间 和 平均作业带权周转时间
如果作业 i的周转时间为 ti,所需运行时间为 tk,则称 wi=ti /tk为该作业的带权周转时间 。 因为,ti是等待时间与运行时间之和,故带权周转时间总大于 1。
平均作业带权周转时间 W = (Σ wi) / n
用平均作业周转时间来衡量对同一作业流施行不同作业调度算法时,它们呈现的调度性能;用平均作业带权周转时间来衡量对不同作业流施行同一作业调度算法时,
它们呈现的调度性能 。 这两个数值均越小越好 。
2.6 批处理作业的管理与调度
2.6.1 作业和进程的关系
l作业 (JOB)是用户提交给操作系统计算的一个独立任务 。
l一般每个作业必须经过若干个相对独立又相互关连的顺序加工步骤才能得到结果,其中,每一个加工步骤称一个作业步 (Job Step),往往上一个作业步的输出是下一个作业步的输入 。
l作业由用户组织,作业步由用户指定,一个作业从提交给系统,直到运行结束获得结果,要经过提交,收容,执行和完成四个阶段 。 当收容态作业被作业调度程序选中进入主存并投入运行时,操作系统将为此用户作业生成相应用户根进程或第一个作业步进程 。 进程是对系统中已提交完毕的任务 (程序 )的执行过程,它在,运行,” 就绪,” 等待,等多个状态的交替之中,在 CPU上推进,最终完成一个程序的任务 。 用户根进程或作业步进程在执行过程中可生成作业步子进程,当然子进程还可以生成其它的子进程,这些进程并发执行,高效地协作完成用户作业的任务 。 在多道程序设计环境中,由于多个作业可同时被投入运行,因此,并发系统中,某一时刻并发执行的进程是相当多的 (如 Unix中有几十个 ),操作系统要负责众多进程的协调和管理 。 后面将要引进线程概念,一个进程中还可以孵化出多个线程,由线程并发执行来完成作业任务 。
可以看出作业和进程之间的主要关系:
作业是任务实体,进程是完成任务的执行实体 ;没有作业任务,进程无事可干,
没有进程,作业任务没法完成 。 作业概念更多地用在批处理操作系统,而进程则可以用在各种多道程序设计系统 。
2.6.2 批处理作业的管理
多道批处理操作系统采用脱机控制方式,
它提供一个作业控制语言,用户使用作业控制语言书写作业说明书,它是按规定格式书写的一个文件,把用户对系统的各种请求和对作业的控制要求集中描述,
并与程序和数据一起提交给系统 (管理员 )。 计算机系统成批接受用户作业输入,
把它们放到输入井,然后在操作系统的管理和控制下执行 。
作业控制块
多道批处理操作系统具有独立的作业管理模块,
为了有效管理作业,必须像进程管理一样为每一个作业建立作业控制块 ( JCB) 。 JCB通常是在批作业进入系统时,由 Spooling系统建立的,
它是作业存在于系统的标志,作业撤离时,JCB
也被撤销 。 JCB的主要内容从用户作业说明书中获得,包括:作业情况 (用户名,作业名,语言名等 ),资源需求 (估计 CPU运行时间,最迟截止期,
主存量,设备类型 /台数,文件数和数据量,函数库 /实用程序等 ),资源使用情况 (进入系统时间,
开始运行时间,己运行时间等 ),作业控制 (优先数,控制方式,操作顺序,出错处理等 )。
作业在它的整个生命周期中将顺序地处于以下四个状态:
l 输入状态:此时作业的信息正在从输入设备上预输入 。
l 后备状态:此时作业预输入结束但尚未被选中执行 。
l 执行状态:作业已经被选中并构成进程去竞争处理器资源以获得运行 。
l 完成状态:作业已经运行结束,正在等待缓输出 。
多道批处理操作系统的处理机调度至少应该包括作业调度和进程调度两个层次
多道批处理操作系统的处理机调度至少应该包括作业调度和进程调度两个层次 。
作业调度属于高级调度层次,处于后备状态的作业在系统资源满足的前提下可以被选中从而进入内存计算 。 而只有处于执行状态的作业才真正构成进程获得计算的机会 。
作业调度选中了一个作业且把它装入主存储器时就为该作业创建一个用户进程 。 这些进程将在进程调度的控制下占有处理器运行 。 为了充分利用处理器,往往可以把多个作业同时装入主存储器,这样就会同时有多个用户进程,这些进程都要竞争处理器 。
进入计算机系统的作业只有经过两级调度后才能占用处理器。第一级是作业调度,使作业进入主存储器;第二级是处理器调度,使作业进程占用处理器。作业调度与处理器调度的配合能实现多道作业的同时执行,作业调度与进程调度的关系如图 2-42。
图 2-42 作业调度与进程调度的关系执行状态运行就绪 等待输入状态后备状态完成状态进程调度中级调度缓输出作业调度预输入完成
2.6.4 作业调度算法
2.1.1.1 先来先服务算法
先来先服务 (First Come,First Served)算法是按照作业进入系统的先后次序来挑选作业,
先进入系统的作业优先被挑选。
这种算法容易实现,但效率不高,只顾及到作业等候时间,而没考虑作业要求服务时间的长短 。 显然这不利于短作业而优待了长作业,或者说有利于 CPU繁忙型作业不利于 I/O繁忙型作业 。 有时为了等待长作业的执行,而使短作业的周转时间变得很大 。 从而平均周转时间也变大 。
例如,下面三个作业同时到达系统并立即进入调度:
作业名 所需 CPU时间作业 1 28
作业 2 9
作业 3 3
现采用 FCFS算法进行调度,那么,三个作业的周转时间分别为,28,37和 40,因此,平均作业周转时间 T = (28+37+40)/3
= 35
若这三个作业提交顺序改为作业 2,1,3,
平均作业周转时间缩短为约 29。
如果三个作业提交顺序改为作业 3,2,1,
则平均作业周转时间缩短为约 18。
由此可以看出,FCFS调度算法的平均作业周转时间与作业提交的顺序有关 。
2.6.4.2 最短作业优先算法
最短作业优先 (Shortest Job First )算法是以进入系统的作业所要求的 CPU时间为标准,总是选取估计计算时间最短的作业投入运行 。 这一算法也易于实现,但效率也不高,它的主要弱点是忽视了作业等待时间 。 由于系统不断地接受新作业,而作业调度又总是选择计算时间短的作业投入运行,因此,使进入系统时间早但计算时间长的作业等待时间过长,
会出现饥饿的现象 。
例如,若有以下四个作业同时到达系统并立即进入调度:
作业名 所需 CPU时间作业 1 9
作业 2 4
作业 3 10
作业 4 8
假设系统中没有其他作业,现对它们实施 SJF调度算法,
SJF的作业调度顺序为作业 2,4,1,3,
平 均 作 业 周 转 时 间 T = (4+12+21+31)/4
= 17
平均带权作业周转时间 W =
(4/4+12/8+21/9+31/10)/4 = 1.98
如果对它们施行 FCFS调度算法,
平均作业周转时间 T = (9+13+23+31)/4 = 19
平均带权作业周转时间 W =
(9/9+13/4+23/10+31/8)/4 = 2.51
由此可见,SJF的平均作业周转时间比
FCFS要小,故它的调度性能比 FCFS好 。
但实现 SJF调度算法需要知道作业所需运行时间,否则调度就没有依据,作业运行时间只知道估计值,要精确知道一个作业的运行时间是办不到的 。
SJF算法进一步讨论
SJF算法既可以是非抢占式的,也可以是抢占式的 。 当一个作业正在执行时,一个新作业进入就绪状态,如果新作业需要的 CPU时间比当前正在执行的作业剩余下来还需的 CPU时间短,
抡占式短作业优先算法强行赶走当前正在执行作业,这种方式也叫最短剩余时间优先算法
(Shortest Remaining Time First)算法 。 此算法不但适用于 JOB调度,同样也适用于进程调度 。
下面来看一个例子,假如现有四个就绪作业其到达系统和所需 CPU时间如下:
一个例子,假如现有四个就绪作业其到达系统和所需 CPU时间如下:
作业名 到达系统时间 CPU时间 (毫秒 )
--------------------------------------------
Job1 0 8
Job2 1 4
Job3 2 9
Job4 3 5
Job1从 0开始执行,这时系统就绪队列仅有一个作业。 Job2在时间 1到达,而 Job1的剩余时间 (7毫秒 )大于 JOB2所需时间 (4毫秒 ),所以,Job1被剥夺,Job2被调度执行。这个例子的平均等待时间是 ((10-1)+(1-1)+(17-2)+(5-
3))/4=26/4=6.5毫秒。如果采用非抢占式 SJF
调度,那么,平均等待时间是 7.75毫秒。
P1 P2 P4 P1 P3
0丫 1丫 5丫 10
丫
17
丫
26
丫
2.6.4.3响应比最高者优先 (HRN)算法
先来服务算法与最短作业优先算法都是比较片面的调度算法。先来先服务算法只考虑作业的等候时间而忽视了作业的计算时问,而最短作业优先算法恰好与之相反,它只考虑用户估计的作业计算时间而忽视了作业的等待时间。响应比最高者优先算法是介乎这两种算法之间的一种折衷的算法,既考虑作业等待时间,又考虑作业的运行时间,这样既照顾了短作业又不使长作业的等待时间过长,改进了调度性能。
响应比定义
我们把作业进入系统后的等待时间与估计运行时间之比称作响应比,现定义;
响应比 =已等待时间 /估计计算时间显然,计算时间短的作业容易得到较高的响应比,因为,这时分母较小,使得 HRN较高,因此本算法是优待短作业的 。 但是,如果一个长作业在系统中等待的时间足够长后,由于,分子足够大,使得 HRN较大,那么它也将获得足够高的响应比,从而可以被选中执行,不至于长时间地等待下去,饥饿的现象不会发生 。
例如,若有以下四个作业先后到达系统进入调度:
作业名 到达时间 所需 CPU时间作业 1 0 20
作业 2 5 15
作业 3 1 5
作业 4 15 10
假设系统中没有其他作业,现对它们实施
SJF的作业调度顺序为作业 1,3,4,2,
平均作业周转时间 T =
(20+15+20+45)/4 = 25
平均带权作业周转时间 W =
(20/20+15/5+25/10+45/15)/4 = 2.25
如果对它们施行 FCFS调度算法,
平均作业周转时间 T =
(20+30+30+35)/4 = 38.75
平均带权作业周转时间 W =
(20/20+30/15+30/5+35/10)/4 = 3.13
如果对这个作业流执行 HRN调度算法
l 开始时只有作业 1,作业 1被选中,执行时间 20;
l 作业 1执行完毕后,响应比依次为 15/15,10/5、
5/10,作业 3被选中,执行时间 5;
l 作业 3执行完毕后,响应比依次为 20/15,10/10,
作业 2被选中,执行时间 15;
l 作业 2执行完毕后,作业 4被选中,执行时间 10;
平均作业周转时间 T = (20+15+35+35)/4 =
26.25
平均带权作业周转时间 W =
(20/20+15/5+35/15+35/10)/4 = 2.42
2.6.4.4 优先数法
这种算法是根据确定的优先数来选取作业,每次总是选择优先数高的作业。
一种是由用户自己提出作业的优先数 。 有的用户为了自己的作业尽快的被系统选中就设法提高自己作业的优先数,
这时系统可以规定优先数越高则需付出的计算机使用费就越多,以作限制 。
另一种是由系统综合考虑有关因素来确定用户作业的优先数 。
例如,根据作业的缓急程度;作业的类型;作业计算时间的长短,等待时间的多少,资源申请情况等来确定优先数 。
确定优先数时各因素的比例应根据系统设计目标来分析这些因素在系统中的地位而决定 。 上述确定优先数的方法称静态优先数法;如果在作业运行过程中,根据实际情况和作业发生的事件动态的改变其优先数,这称之为动态优先数法 。
2.6.4.5 分类调度算法
分类调度算法预先按一定的原则把作业划分成若干类,以达到均衡使用操作系统资源和兼顾大小作业的目的 。 分类原则包括作业计算时间,对内存的需求,
对外围设备的需求等 。 作业调度时还可以为每类作业设置优先级,从而照顾到同类作业中的轻重缓急 。
2.6.4.6用磁带与不用磁带的作业搭配
这种算法将需要使用磁带机的作业分开来 。 在作业调度时,把使用磁带机的作业和不使用磁带机的作业搭配挑选 。 在不使用磁带机的作业执行时,可预先通知操作员将下一批作业要用的磁带预先装上,这样可使要用磁带机的作业在执行时省去等待装磁带的时间 。 显然这对缩短系统的平均周转时间是有益的 。
2.7 进程调度
2.7.1 进程调度的功能进程调度负责动态地把处理器分配给进程或内核级线程。因此,它又叫处理器调度或低级调度。操作系统中实现进程调度的程序称为进程调度程序,或分派程序 (Dispatcher)。进程调度算法多数适用于线程调度,下面着重介绍进程调度。
何时发生 CPU调度呢?
有四种情况都会发生 CPU调度,当一个进程从运行态切换成等待态时 ; 当一个进程从运行态切换成就绪态时 ; 当一个进程从等待态切换成就绪态时和当一个进程中止时。
进程调度的主要功能是:
l 记住进程的状态 。 这个信息一般记录在一个进程的进程控制块内 。
l 决定某个进程什么时候获得处理器,以及占用多长时间 。
l 把处理器分配给进程 。 即进行进程上下文切换,把选中进程的进程控制块内有关现场的信息;如程序状态字,通用寄存器等内容送入处理器相应的寄存器中,从而让它占用处理器运行 。
l 收回处理器 。 将处理器有关寄存器内容送入该进程的进程控制块内的相应单元,从而使该进程让出处理器 。
进程调度有两种基本方式,
非抢占式和抢占式。
非抢占式进程调度方式中,一旦某个高优先级的进程上有了 CPU,它就一直运行下去,直到其自身原因 (结束或等待 )主动出让 CPU时,才调度另一个高优先级进程运行。
抢占式进程调度方式中,任何时刻严格按高优先级进程在 CPU上运行的原则进行进程 /线程调度,每当一个高优先级进程运行期间,系统中又有更高优先级进程就绪,进程调度将迫使当前运行进程出让 CPU。
可以把两种基本方式组合起来,
形成折衷方式,
实现思路如下:把就绪进程分成不同优先权的几个队列,如按系统进程和用户进程分成高低优先级两队,队列内采用非抢占式,但队列间采用抢占式 。 仅当高优先级队列空时,才能调度低优先级队列 ;而低优先级队列进程运行时,高优先级队列中若有进程就绪,可以抢占处理器 。
2.7.2 进程调度算法
2.7.2.1 先来先服务算法
先来先服务算法是按照进程进入就绪队列的先后次序来分配处理器 。 先进入就绪队列的进程优先被挑选,运行进程一旦占有处理器将一直运行下去直到运行结束或阻塞 。 这种算法容易实现,但效率不高,显然不利于 I/O频繁的进程 。
2.7.2.2 时间片轮转调度
轮转法调度也称之为时间片调度,具体做法是调度程序每次把 CPU分配给就绪队列首进程使用一个时间片,例如
100ms,就绪队列中的每个进程轮流地运行一个这样的时间片。当这个时间片结束时,就强迫一个进程让出处理器,
让它排列到就绪队列的尾部,等候下一轮调度。
这种调度策略可以防止那些很少使用外围设备的进程过长的占用处理器而使得要使用外围设备的那些进程没有机会去启动外围设备 。
间隔时钟
基本轮转法
改进 轮转法
轮转法调度是一种剥夺式调度,系统耗费在进程切换上的开销比较大,这个开销与时间片的大小很有关系 。 如果时间片取值太小,以致于大多数进程都不可能在一个时间片内运行完毕,切换就会频繁,系统开销显著增大,所以,从系统效率来看,时间片取大一点好 。 另一方面,时间片长度较大,那么随着就绪队列里进程数目的增加,
轮转一次的总时间增大,亦即对每个进程的响应速度放慢了 。 为了满足用户对响应时间的要求,
要么限制就绪队列中的进程数量,要么采用动态时间片法,根据当前负载状况,及时调整时间片的大小 。 所以,时间片大小的确定要从进程个数,切换开销,系统效率和响应时间等方面考虑 。
2.7.2.3 优先权调度
可以有以下几种考虑,使用外围设备频繁者优先数大,这样有利于提高效率;重要算题程序的进程优先数大,这样有利于用户;进入计算机时间长的进程优先数大,这样有利于缩短作业完成的时间;交互式用户的进程优先数大,这样有利于终端用户的响应时间等等,以上采用了静态优先数法 。
效率高性能好的进程调度可采用动态优先数法,基本原则是,① 根据进程占有 CPU时间多少来决定,当一个进程占有 CPU时间愈长,那么,在它被阻塞之后再次获得调度的优先级就越低,反之,进程获得调度的可能性越大 ;② 根据进程等待 CPU时间多少来决定,当一个进程在就绪队列中等待时间愈长,那么,在它被阻塞之后再次获得调度的优先级就越高,反之,进程获得调度的可能性越小 。
2.7.2.4 多级反馈队列调度
这种方法又称反馈循环队列或多队列策略 。 其主要思想是将就绪进程分为两级或多级,系统相应建立两个或多个就绪进程队列,较高优先级的队列一般分配给较短的时间片 。 处理器调度每次先从高级的就绪进程队列中选取可占有处理器的进程,只有在选不到时,才从较低级的就绪进程队列中选取 。
图 2-44 一个三级调度策略低级就绪队列高级就绪队列 中级就绪队列等待磁盘磁带等待其他外设运行选中,时间片 500ms超过时间片启动磁盘磁带启动其他外设选中,时间片 200ms选中,时间片 100ms
2.7.2.5 保证调度算法
一种完全不同的调度算法是向用户做出明确的性能保证,然后去实现它 。 一种很实际并很容易实现的保证是:当你工作时己有 n个用户登录在系统,则你将获得 CPU处理能力的 1/n。 类似的,如果在一个有 n个进程运行的用户系统中,每个进程将获得 CPU处理能力的 1/n。
为了实现所作的保证,系统必须跟踪各个进程自创建以来已经使用了多少 CPU
时间 。 然后它计算各个进程应获得的
CPU时间,即自创建以来的时间除以 n。
由于各个进程实际获得的 CPU时间已知,
所以很容易计算出实际获得的 CPU时间和应获得的 CPU时间之比,于是调度将转向比率最低的进程 。
2.7.2.6 彩票调度算法
其基本思想是:为进程发放针对系统各种资源
( 如 CPU时间 ) 的彩票 。 当调度程序需要做出决策时,随机选择一张彩票,持有该彩票的进程将获得系统资源 。 对于 CPU调度,系统可能每秒钟抽 50次彩票,每次中奖者可以获得 20ms的运行时间 。
在此种情况下,所有的进程都是平等的,它们有相同的运行机会 。 如果某些进程需要更多的机会,
就可以被给予更多的额外彩票,以增加其中奖机会 。 如果发出 100张彩票,某一个进程拥有 20张,
它就有 20%的中奖概率,它也将获得大约 20%的
CPU时间 。
彩票调度法有几点有趣的特性 。 彩票调度的反映非常迅速,例如,如果一个新进程创建并得到了一些彩票,则在下次抽奖时,它中奖的机会就立即与其持有的彩票成正比 。
如果愿意的话,合作的进程可以交换彩票 。 例如,一个客户进程向服务器进程发送一条消息并阻塞,它可以把所持有的彩票全部交给服务器进程,以增加后者下一次被选中运行的机会;当服务器进程完成响应服务后,它又将彩票交还给客户进程使其能够再次运行;实际上,
在没有客户时,服务器进程根本不需要彩票 。
彩票调度还可以用来解决其他算法难以解决的问题 。 例如,一个视频服务器,其中有若干个在不同的视频下将视频信息传送给各自的客户,假设它分别需要 10,20和
25帧 /秒的传输速度,则分别给这些进程分配 10,20和 25
张彩票,它们将自动按照正确的比率分配 CPU资源 。
2.7.3 实时调度
2.7.3.1 实时操作系统的特性
实时系统是那些时间因素非常关键的系统 。 例如,
计算机的一个或多个外设发出信号,计算机必须在一段固定时间内做出适当的反应 。 一个实例是,
计算机用 CD-ROM放 VCD时,从驱动器中获得的二进制数据必须在很短时间转化成视频和音频信号,如果转换的时间太长,图像显示和声音都会失真 。 其他的实时系统还包括监控系统,自动驾驶系统,安全控制系统等等,在这些系统中,迟到的响应即使正确,也和没有响应一样糟糕 。
硬实时系统和软实时系统
实时系统通常分为硬实时 ( hard real time) 系统和软实时 ( soft real time) 系统 。 前者意味着存在必须满足的时间限制;后者意味着偶尔超过时间限制时可以容忍的 。 这两种系统中,
实时性的获得时通过将程序分成很多进程,而每个进程的行为都预先可知,这些进程处理周期通常都很短,往往在一秒钟内就运行结束,
当检测到一个外部事件时,调度程序按满足他们最后期限的方式调度这些进程 。
周期性和非周期性事件
实时系统要响应的事件可以进一步划分为周期性 ( 每个一段固定的时间发生 ) 事件和非周期性 ( 在不可预测的时间发生 ) 事件 。 一个系统可能必须响应多个周期的事件流,根据每个事件需要的处理时间,系统可能根本来不及处理所有事件 。 例如,有 m个周期性事件,事件 i的周期为 Pi,其中每个事件需要 Ci秒的 CPU时间来处理,则只有满足以下条件:
C1/P1 + C2/P2 + … + Cm/Pm ≤ 1
时,才可能处理所有的负载 。 满足该条件的实时系统称作可时刻调度的 ( schedulable) 。
举例来说,一个软实时系统处理三个事件流,其周期分别为 100ms,200ms和 500ms,
如果事件处理时间分别为 50ms,30ms和
100ms,则这个系统是可调度的,因为
0.5 + 0.15 + 0.2 ≤ 1
如果加入周期为 1秒的第 4个事件,则只要其处理时间不超过 150ms,该系统仍将时可调度的 。 当然,这个运算的隐含条件是进程切换的时间足够小,可以忽略 。
2.7.3.2 实时调度算法
1) 单比率调度算法单比率调度事先为每个进程分配一个与事件发生频率成正比的优先数 。 例如,周期为 20ms的进程优先数为 50,周期为
100ms的进程优先数为 10,运行时调度程序总是调度优先数最高的就绪进程,
并采取抢占式分配策略 。 可以证明概算法是最优的 。
2) 限期调度算法限期调度算法的基本思想是:当一个事件发生时,
对应的进程就被加入就绪进程队列 。 该就绪队列按照截止期限排序,对于一个周期性事件,
其截止期限即为事件下一次发生的时间 。 该调度算法首先运行队首进程,即截止时间最近的那个进程 。
3) 最少裕度法最少裕度法的基本思想是:首先计算各个进程的富裕时间,即裕度 ( laxity),然后选择裕度最少的进程执行 。
2.7.4 多处理器调度
一些计算机系统包括多个处理器,目前应用较多的,
较为流行的多处理器系统有:
l松散耦合多处理器系统:如 cluster,它包括一组独立的处理器,每个处理其拥有自己的主存和 I/O
通道 。
l紧密耦合多处理器系统:它包括一组处理器,共享主存和外设 。
因此操作系统的调度程序必须考虑多粒处理器的调度 。 显然,单个处理器的调度和多粒处理器的调度有一定的区别,现代操作系统往往采用进程调度与线程调度相结合的方式来完成多处理器调度 。
2.7.4.1 同步的粒度
同步的粒度,就是系统中多个进程之间同步的频率,它是刻画多处理系统特征和描述进程并发度的一个重要指标 。 一般来说,我们可以根据进程或线程之间同步的周期 ( 即每间隔多少条指令发生一次同步事件 ),把同步的粒度划分成以下 5个层次:
l细粒度 ( fine-grained),同步周期小于 20条指令 。
这是一类非常复杂的对并行操作的使用,类似于多指令并行执行 。 它属于超高并行度的应用,目前有很多不同的解决方案,本书将不涉及这些解决方案,
有兴趣的可以参见有关资料 。
l中粒度 ( medium-grained),同步周期为 20-200条指令 。 此类应用适合用多线程技术实现,即一个进程包括多个线程,多线程并发或并行执行,以降低操作系统在切换和通信上的代价 。
l粗粒度 ( coarse-grained),同步周期为 200-2000条指令 。 此类应用可以用多进程并发程序设计来实现 。
l超粗粒度 ( very coarse-grained),同步周期为 2000
条指令以上 。 由于进程之间的交互是非常不频繁,
因此这一类应用可以在分布式环境中通过网络实现并发执行 。
l独立 ( independent),进程或线程之间不存在同步 。
对于那些具有独立并行性的进程来说,多处理器环境将得到比多道程序系统更快的响应 。 考虑到信息和文件的共享问题,在多数情况下,共享主存的多处理器系统将比那些需要通过分布式处理实现共享的多处理器系统的效率更高 。
对于那些具有粗粒度和超粗粒度并行性的进程来说,并发进程可以得益于多处理器环境 。 如果进程之间交互不频繁的话,分布式系统就可以提供很好的支持,而对于进程之间交互频繁的情况,多处理器系统的效率更高 。
无论是有独立并行性的进程,还是具有粗粒度和超粗粒度并行性的进程,在多处理器环境中的调度原则和多道程序系统并没有太大的区别 。 但在多处理器环境中,一个应用的多个线程之间交互非常频繁,针对一个线程的调度策略可能影响到整个应用的性能 。 因此在多处理器环境中,我们主要关注的是线程的调度 。
2.7.4.2 多处理器调度的设计要点
多处理器调度的设计要点之一是如何把进程分配给处理器。
多处理器调度的设计要点之二石川
>
2.7.4.3 多处理器的调度 算法
1) 负载共享调度算法
2) 群调度算法
3) 处理器专派调度算法
4) 动态调度算法
>
群调度算法中为应用进程分配 CPU时间
方法一 面向应用进程平均分配
方法二 面向所有进程平均分配
2.7.5 实例研究 ——传统 Unix调度算法
传统 Unix的进程调度采用多级反馈队列,对于每一个优先级队列采用时间片调度策略 。 系统遵从 1秒抢占的原则,
即一个进程运行了 1秒还没有阻塞或完成的话,它将被抢占 。 优先数根据进程类型和执行情况确定,计算公式:
Pj(i) = Basej + CPUj(i-1)/2 + nicej
CPUj(i) = Uj(i)/2 + CPUj(i-1)/2
其中:
l Pj(i):进程 j的优先数,时间间隔为 i;取值越小,优先级越高
l Basej:进程 j的基本优先数
l Uj(i):进程 j在时间间隔 i内的处理器使用情况
l CPUj(i):进程 j在时间间隔 i内的处理器使用情况的指数加权平均数
l nicej:用户控制的调节因子
3 用户进程在下列情况下重新计算优先数:发生中断和时钟中断 ;对优先数 >100的进程,每秒计算一次
4 计算公式:
p-pri=min(127,100+p-cpu/16+p-nice)
p-nice用户控制因子,设成 0~20或 0~-20
p-CPU是反映进程运行状况的参数,定时修改它
5 结论
Unix早期版本调度算法
1 采用动态优先数进程调度策略,数值越小,优先权越高
2 系统进程使用 sleep(chan,pri)进入等待放弃 CPU。 Chan为等待事件,pri为优先数供唤醒用,分为 -100,-90,-50,1,40,90。
每个进程的优先数每秒计算一次,并随后做出调度决定 。 基础优先数用作把所有进程划分到固定的优先级队列中,CPUj 和
nicej受到限制以防止进程迁移出分配给它的由基础优先数确定的队列 。 这些队列用作优化访问块设备或允许操作系统快速响应系统调用 。 根据有利于 I/O设备有效使用的原则,进程队列按优先级从高到低排列:
l 对换
l 块设备控制
l 文件操纵
l 字符设备控制
l 用户进程
2.7.6 实例研究 —Unix SVR4调度算法
Unix SVR4对调度算法的主要修改包括:
l提供了基于静态优先数的抢占式调度,包括 3类优先级层次,160
个优先数 。
l引入了抢占点 。 由于 Unix的基本内核不是抢占式的,它将被划分划分成一些处理步骤,如果不发生中断的话,这些处理步骤将一直运行直到结束 。 在这些处理步骤之间,存在着抢占点,称为 safe
place,此时内核可以安全地中断处理过程并调度新进程 。 每个 safe
place被定义成临界区,从而保证内核数据结构通过信号量上锁并被一致地修改 。
在 Unix SVR4中,每一个进程必须被分配一个优先数,从而属于一类优先级层次 。 优先级和优先数的划分如下:
l实时优先级层次 ( 优先数为 159-100),这一优先级层次的进程先于内核优先级层次和分时优先级层次的进程运行,并能利用抢占点抢占内核进程和用户进程 。
l内核优先级层次 ( 优先数为 99-60),这一优先级层次的进程先于分时优先级层次进程但迟于实时优先级层次进程运行 。
l分时优先级层次 ( 优先数为 59-0),最低的优先级层次,一般用于非实时的用户应用 。
Unix SVR4的进程调度参见图 2-45,它事实上还是一个多级反馈队列,每一个优先数都对应于一个就绪进程队列,而每一个进程队列中的进程按照时间片方式调度。位向量 dqactmap用来标志每一个优先数就绪进程队列是否为空。当一个运行进程由于阻塞、时间片或抢占让出处理器,调度程序首先查找 dqactmap已发现一个较高优先级的非空队列,然后指派进程占有处理器运行。另外,
当执行到一个定义的抢占点,内核程序将检查一个叫作 kprunrun的标志位,如果发现有高优先级的实时进程处于就绪状态,就执行抢占。
图 2-45 Unix SVR4的就绪进程队列
dqactmap
dispq 159,.....,.....159 2 1 0
0,.....,.....1 1 1 0
P
P
P P
P
P
2.7.7实例研究 —Windows NT调度算法
Windows NT的设计目标有两个一是向单个用户提供交互式的计算,
环境,
二是支持各种服务器 ( server) 程序 。
Windows NT的调度是基于内核级线程的,
它支持抢占式调度,包括多个优先数层次,在某些层次线程的优先数是固定的,
在另一些层次线程的优先数将根据执行的情况动态调整 。 它的调度策略是一个多级反馈队列,每一个优先数都对应于一个就绪队列,而每一个进程队列中的进程按照时间片方式调度 。
1 概述
线程调度算法
Kernel’s Dispatcher
线程调度触发事件
2 线程调度 API
3 线程优先级
16个实时线程优先级
15个可变线程优先级
1个系统线程优先级
4 中断优先级与线程优先级关系
5 线程时间配额
6 线程调度数据结构
调度器就绪队列
就绪位图
空闲位图
内核自旋锁
7 调度策略
主动切换
抢先
时间配额用完
结束
8 SMP上线程调度
亲合关系
线程首选 CPU和第二 CPU
就绪线程局运行 CPU选择
为特定 CPU调度线程
Windows NT有两个优先级层次:
l实时优先级层次 ( 优先数为 31-16),用于通信任务和实时任务 。 当一个线程被赋予一个实时优先数,在执行过程中这一优先数是不可变的 。 NT支持优先数驱动的抢占式调度,一旦一个就绪线程的实时优先数比运行线程高,它将抢占处理器运行 。
l 可变优先级层次 ( 优先数为 15-0),用于用户提交的交互式任务 。 具有这一层次优先数的线程,可以根据执行过程中的具体情况动态地调整优先数,但是 15这个优先数是不能被突破的 。
图 2-46 Windows NT的线程优先级最高 (31)
实时优先级层次最低 (16)
最高 (15)
最低 (0)
可变优先级层次一个线程如果被赋予可变优先数,那么它的优先数调整服从下面规则:
l 线程所属的进程对象有一个进程基本优先数,取值范围从 0到 15。
l 线程对象有一个线程基本优先数,取值范围从 -2到 2。
l 线程的初始优先数为进程基本优先数加上线程基本优先数,但必须在 0到 15的范围内 。
l 线程的动态优先数必须在初始优先数到
15的范围内 。
当运行线程用完了时间片,调度程序将降低它的优先数,
而当运行进程因为 I/O阻塞后,调度程序则将提高它的优先数 。 并且优先数的提高与等待的 I/O设备有关,等待交互式外围设备 ( 如键盘和显示器 ) 时,优先数的提高幅度大于等待其他类型的外围设备 ( 如磁盘 ),显然这种优先数调整方式有利于交互式任务 。
当 NT运行在单个处理器上时,最高优先级的线程将运行直到它结束,阻塞或被抢占 。 而当 NT运行在 N个处理器上时,N-1个处理器上将运行 N-1个最高优先级的线程,
其他线程将共享剩下的一个处理器 。 值得指出的是,当线程的处理器亲和属性 ( processor affinity attribute) 指定了线程执行的处理器时,虽然系统有可用的处理器,
但指定处理器被更高优先级的线程占用,此时该线程必须等待,而可用处理器将被调度给优先级较低的就绪线程 。
处理器管理 是操作系统的重要组成部分,它负责管理,调度和分派计算机系统的重要资源处理器,并控制程序的执行 。 由于处理器管理是操作系统中最内核的组成部分,任何程序的执行都必须真正占有处理器,因此处理器管理直接影响系统的性能 。
2.1 中央处理器
2.1.1 单处理器系统和多处理器系统目前计算机系统可以分作以下四类:
l单指令流单数据流 ( SISD),一个处理器在一个存储器中的数据上执行单条指令流 。
l单指令流多数据流 ( SIMD),单条指令流控制多个处理单元同时执行,每个处理单元包括处理器和相关的数据存储,一条指令事实上控制了不同的处理器对不同的数据进行了操作 。
向量机和阵列机是这类计算机系统的代表 。
l多指令流单数据流 ( MISD),一个数据流被传送给一组处理器,通过这一组处理器上的不同指令操作最终得到处理结果 。
l多指令流多数据流 ( MIMD),多个处理器对各自不同的数据集同时执行不同的指令流 。
紧密耦合 MIMD系统可以分为主从式系统和对称式系统 (SMP)两类。
主从式系统的基本思想是:在一个特别的处理器上运行操作系统内核,其他处理器上则运行用户程序和操作系统例行程序,内核负责分配和调度各个处理器,
并向其它程序提供各种服务 ( 如输入输出 ) 。 这种方式实现简单,但是主处理器的崩溃会导致整个系统的崩溃,并且极可能在主处理器形成性能瓶颈 。
对称式多处理器系统 ( SMP) 中,操作系统内核可以运行在任意一个处理器上,每个处理器都可以自我调度运行的进程和线程,并且操作系统内核也被设计成多进程或多线程,内核的各个部分可以并行执行 。
对称多处理机 (Symmetric Multiprocessor,SMP)是迄今开发出的最成功的并行机,有一种 SMP机最多可支持 64个处理器,多个处理器之间采用共享主存储器 。 SMP机有对称性,单一地址空间,低通信延迟和一致的高速缓存等特点,具有高可靠性,可扩充性,易伸缩性 。 这一系统中任何 CPU可访问任何存储单元及 I/O设备; CPU间通信代价很低,而并行度较高;由于共享存储器中只要保存一个操作系统和数据库副本,既有利于动态负载平衡,又有利于保证数据的完整性和一致性 。 Dec Alpha
Server,HP9000/T600,IBMRS600/40,Sun Ultra
Enterprise 6000,SGI Power Challenge XL都 `是 SMP机,
主要用于在线数据服务,数据库和数据仓库等应用 。
2.1.2 寄存器
l通用寄存器 可由程序设计者指定许多功能,如存放操作数或用作寻址寄存器 。
l数据寄存器 它们作为内存数据的高速缓存,可以被系统程序和用户程序直接使用并进行计算 。 用以存放操作数 。
l 地址寄存器 用于指明内存地址 。 如索引寄存器,段寄存器 ( 基址 /限长 ),堆栈指针寄存器等等 。
lI/O地址寄存器 用于指定 I/O设备 。
lI/O缓冲寄存器 用于处理器和 I/O设备交换数据 。
l 控制寄存器 用于存放处理器的控制和状态信息,包括程序计数器和指令寄存器,中断寄存器以及用于存储器和 I/O模块控制的寄存器也属于这一类 。 此外还有:
存放将被访问的存储单元地址的存储器地址寄存器和存放从存储器读出或欲写入的数据的存储器数据寄存器 。
l程序状态字寄存器也属于 CPU 。
2.1.3 机器指令计算机机器指令的集合称指令系统,它反映了一台机器的功能和处理能力,可以分为以下四类:
l 数据处理类指令:用于执行算术和逻辑运算 。
l 控制类指令:如转移,用于改变执行指令序列 。
l 寄存器数据交换类指令:用于在处理器的寄存器和存储器之间交换数据 。
l I/O类指令:用于启动外围设备,让主存和设备交换数据 。
2.1.4 特权指令
在多道程序设计环境中,从资源管理和控制程序执行的角度出发,把指令系统中的指令分作两 类,
特权指令 (Privileged Instructions)和非特权指令 。
所谓特权指令是指那些只能在特态下才能正常执行的,提供给操作系统的核心程序使用的指令,
如启动输入输出设备,设置时钟,控制中断屏蔽位,清内存,建立存储键,加载 PSW,…,等 。
一般用户在目态下运行,只能执行非特权指令,
否则会导致非法执行特权指令而产生中断 。 只有操作系统才能执行全部指令 ( 特权指令和非特权指令 ) 。
2.1.5 处理器状态
处理器状态又称为处理器的运行模式,
有些系统把处理器状态划分为核心状态,管理状态和用户状态,而大多数系统把处理器状态简单的划分为管理状态 ( 又称特权状态,系统模式,特态或管态 ) 和用户状态 ( 又称目标状态,用户模式,常态或目态 ) 。
当处理器处于管理状态时,可以执行全部指令,使用所有资源,并具有改变处理器状态的能力;当处理器处于用户状态时,只能执行非特权指令 。 没有硬件支持的多运行模式会引起系统严重后果,如 MS-DOS
是为 Intel8088结构配的操作系统,它没有双模式,可能发生用户把数据写到操作系统区,或几个用户同时使用一台设备 。
PDP系列计算机具有两个处理器状态,用户态和核心态 。
Pentium的处理器状态有四种,支持 4个保护级别,
0级权限最高,3级权限最 `低 。 一种典型的应用是把 4个保护级别依次设定为:
l 0级为操作系统内核级 。 处理 I/O,存储管理,
和其他关键操作 。
l 1级为系统调用处理程序级 。 用户程序可以通过调用这里的过程执行系统调用,但是只有一些特定的和受保护的过程可以被调用 。
l 2级为共享库过程级 。 它可以被很多正在运行的程序共享,用户程序可以调用这些过程,都去它们的数据,但是不能修改它们 。
l 3级为用户程序级 。 它受到的保护最少 。
下面两类情况会导致从用户态向核心态转换,
一是程序请求操作系统服务,执行一条系统调用;
二是程序运行时,产生了一个中断事件,中断处理程序进行工作 。
Unix系统上进程执行在两个状态之下:用户态和核心态
用户态下的进程执行一个系统调用时,进程的执行态从用户态变为核心态,由操作系统执行并试图为用户的请求服务 。
Unix两个处理器状态之间的差别是:用户态下的进程能存取自己的指令和数据,但不能存取内核指令和数据,也不能存取其它进程的指令和数据 。 然而,核心态下的进程能够存取内核和用户地址 。
用户态下的进程不能执行特权指令,这是系统用以保证安全性的措施之一 。 注意到内核是为用户进程工作的,它不是与用户进程平行运行的软件,而是作为用户进程的一部分 。
例如,Shell通过系统调用读用户终端,
在执行读操作时,由用户态转入核心态 。 于是,正在为该 Shell进程执行的内核软件对终端的操作进行控制,并把键入的字符返回给 Shell,此时,处理器又从核心态转回用户态,然后,
Shell在用户态下继续执行,对字符流作分析和解释完成规定的操作,而解释执行过程中又允许请求引用其它系统调用 。
2.1.6 程序状态字寄存器
程序状态字 PSW是 CPU中的特殊寄存器,用来控制指令的执行顺序并且保留和指示与程序有关的系统状态的集合 。 它的主要作用是方便地实现程序状态的保护和恢复 。 一般来说,程序状态字寄存器包括以下几类内容:
l 程序基本状态 。 包括,1) 程序计数器:指明下一条执行的指令地址; 2) 条件码:表示指令执行的结果状态; 3) 处理器状态位:指明当前的处理器状态,如目态或管态,运行或等待 。
l中断码 。 保存程序执行时当前发生的中断事件 。
l中断屏蔽位 。 指明程序执行中发生中断事件时,
是否响应出现的中断事件 。
IBM360/370系列计算机的程序状态字基本格式如图 2-1所示:
8位系统屏蔽
XX X X XXXX X X XXXXXX
4位 CMWP字段 4位程序屏蔽
4位保护键 16位中断码字段指令长和条件码
24位指令地址图 2-1 IBM 370系统程序状态字的其本控制格式
l 系统屏蔽位 8位 (0-7位 ) 0-7依次为通道 0-6和外中断屏蔽位 。
l 保护键 4位 (6-11位 ) 当没有设置存储器保护时,该 4位为 0当设置存储器保护时,PSW中的这四位保护键与欲访问的存储区的存储键相匹配,否则指令不能执行 。
l CMWP位 (12-15位 ) 依次为 PSW基本
/扩充控制方式位,开 /关中断位,运行
/等待位,目态 /特态位 。
l中断码 16位 中断码字段与中断事件对应,
记录当前产生的中断源 。
l 指令长度字段 2位 (32-33位 01/10/11分别表示半字长指令,整字长指令和一字半长指令 。
l条件码 2位 (34-35位 )
l 程序屏蔽 4位 (36-39位 ) 表示允许 (为 1) 或禁止 (为 0) 程序性中断,自左向右各位体对应的程序性事件是:定点溢出,十进溢出,
阶 下 溢,39 位备用 。
l 指令地址 24位 (40-63位 )
Pentium的程序状态字由标志寄存器
EFLAGS和指令指针寄存器 EIP组成标志可划分为三组:
状态标志算术运算指令使用 OF(溢出标志 ),SF(符号标志 ),ZF(结果为零标志 ),AF(辅助进位标志 ),CF(进位标志 ),PF(奇偶校验标志 );串扫描,串比较,循环指令使用 ZF通知其操作结束 。
控制标志
DF(方向标志 )控制串指令操作,设定 DF
为 1,使得串指令自动减量,即从高地址向低地址处理串操作; DF为 0时,串指令自动增量 。 VM(虚拟 86方式标志 )为 1时,从保护模式进入虚拟 8086模式 。 TF(步进标志 )
为 1时,使处理机执行单步操作 。 IF(陷阱标志 )为 1时,允许响应中断,否则关中断 。
系统标志 。
IOPL(I/O特权级标志 ),NT(嵌套任务标志 )和 RF(恢复标志 ),被用于保护模式 。
指令指针寄存器 EIP的低 16位称为 IP,存放下一条顺序执行的指令相对于当前代码段开始地址的一个偏移地址,IP可当作一个单元使用,这在某些情况下是很有用的 。
2.2 中断技术
2.2.1 中断的概念中断 是指程序执行过程中,当发生某个事件时,中止 CPU上现行程序的运行,引出处理该事件的程序执行的过程。
采用中断技术实现 CPU和 I/O设备交换信息可使
CPU与 I/O并行工作。在计算机运行过程中,除了会遇到 I/O中断外,还有许多事件会发生,如硬件故障、电源掉电、人机联系和程序出错、
请求操作系统服务等,这些事件必须及时加以处理。此外,在实时系统,如生产自动控制系统中,必须及时将传感器传来的温度、距离、
压力、湿度等变化信息送给计算机,计算机则暂停当前工作,转去处理和解决异常情况。所以,为了提高系统效率,处理突发事件,满足实时要求,中断概念被提出来了。
引起中断的事件称为中断源
在不同的硬件结构中,通常有不同的中断源和不同的中断装置,但它们有一个共性,即:当中断事件发生后,中断装置能改变处理器内操作执行的顺序,可见中断是现代操作系统实现并发性的基础之一 。
2.2.2 中断源的分类从中断事件的性质来说,可以分成强迫性中断事件和自愿性中断事件两大类,
强迫性中断事件 不是正在运行的程序所期待的,
而是由于某种事故或外部请求信息所引起的 。
这类中断事件大致有以下几种:
l处理器中断事件 。 例如电源故障,主存储器出错等 。
l程序性中断事件 。 例如定点溢出,除数为 0,
地址越界等 。
l外部中断事件 。 例如时钟的定时中断,控制台发控制信息等 。
l输入输出中断事件 。 例如设备出错,传输结束等 。
自愿性中断事件 是正在运行的程序所期待的事件。这种事件是由于执行了一条访管指令而引起的,
它表示正在运行的程序对操作系统有某种需求,一旦机器执行这一中断时,便自愿停止现行程序而转入访管中断处理程序处理。
图 2-2表示出了上述的两类中断事件运行程序中断处理程序中断装置处理器中断事件程序性中断事件外二部中断事件输入输出中断事件运行程序中断处理程序中断装置访管指令中断源可以分成两类,
一类是不可屏蔽中断,如电源掉电,
CPU不能禁止响应;
另一类是可屏蔽中断,通常 PSW的一些位指示某个可屏蔽中断源是否禁止,
CPU可根据该中断源是否被屏蔽来确定是否给予响应。
有些机器中断源并不分类,但按中断来源的不同,分为两类:中断和捕俘,依据中断优先级的高低排成中断级
中断指由 CPU以外产生的事件引起的中断,如 I/O中断、时钟中断、外中断 ;
捕俘又称陷入,指 CPU内部事件或运行程序执行中产生的事件引起的中断,如电源故障、程序故障和访管指令。
图 2-3一种典型的中断级机器故障中断 高优先级低优先级时钟中断磁盘中断网络设备中断软件中断终端设备中断
2.2.3 中断装置计算机系统都采用硬件和软件结合的方法实现中断处理 。 一般说,中断装置主要做以下三件事:
l发现中断源,提出中断请求 。 当发现多个中断源时,它将根据规定的优先级,先后发出中断请求 。
l保护现场,将处理器中某些寄存器内的信息存放于内存储器,使得中断处理程序运行时,不会破坏被中断程序的有用信息,
以便在中断处理结束后它能够继续运行 。
l启动处理中断事件的程序 。
中断 响应 过程
微型计算机采用堆栈保存被中断程序的状态信息,中断响应时,把现行寄存器内容压进堆栈,并接受中断处理程序的中断向量地址和有关信息,这就引出了中断处理程序 。
返回原程序时,只要把栈顶内容弹出送入现行寄存器 。
IP
CS
PSW
现行寄存器新 IP
新 CS
老 IP
老 CS
老 PSW少新栈顶主存新 PSW
2.2.4 中断事件的处理
2.2.4.1 中断响应和中断处理程序中断处理程序主要做以下四项工作:
l保护末被硬件保护的一些必需的处理状态 。 例如,
将通用寄存器的内容送入主存储器,从而使中断处理程序在运行中可以使用通用寄存器 。
l识别各个中断源,即分析产生中断的原因 。
l处理发生的中断事件 。 中断处理程序将根据不同的中断源,进行各种处理操作 。 有简单的操作,如置一个特征标志;也有相当复杂的操作,如重新启动磁带机倒带并执行重读操作 。
l恢复正常操作 。 恢复正常操作一般有几种情况:恢复中断前的程序按断点执行;重新启动一个新的程序或者甚至重新启动操作系统 。
2.2.4.2 处理器中断事件的处理
这种事件是由硬件的故障而产生,
排除这种故障必须进行人工干预 。
中断处理能做的工作一般是保护现场,防止故障曼延,报告操作员并提供故障信息以便维修和校正,以及对程序中所造成的破坏进行估价和恢复 。 下面列举一些处理器中断事件的处理办法 。
2.2.4.3 程序性中断事件的处理
处理程序性中断事件有两种处理办法
中断续元处理
中断续元处理需要的设施调试语句中断续元入口表
中断续元处理过程
>
2.2.4.4 自愿中断事件的处理
这类中断是由于系统程序或用户程序执行访管指令 (例如,Unix中的 trap指令,
MS-DOS 中的 int 指令,IBM 中的
supervisor指令等 )而引起的,它表示运行的程序对操作系统功能的调用,所以也称系统调用,可以看作是机器指令的一种扩充 。
访管指令包括操作码和访管参数两部分,前者表示这条指令是访管指令,
后者表示具体的访管要求。
硬件在执行访管指令时,把访管参数作为中断字并入程序状态字,同时将它送入主存指定单元,然后转向操作系统处理。操作系统分析访管参数,
进行合法性检查后按照访管参数的要求进行相应的处理。
系统调用共性处理流程,
系统调用机制本质上通过特殊硬指令和中断系统来实现,不同机器系统调用命令的格式和功能号的解释不尽相同,共性处理流程如下:
l用户程序执行 n号系统调用
l 通过中断系统进入访管中断处理,保护现场,按功能号跳转
l通过系统调用入口表找相应功能入口地址
l执行相应例行程序,结束后正常情况返回系统调用的下一条指令执行
2.2.4.5 外部中断事件的处理
1) 时钟中断事件的处理
时钟是操作系统进行调度工作的重要工具
绝对时钟
间隔时钟
操作系统有关时钟的任务不同,但一般包括以下内容:
l维护绝对日期和时间 ;
l防止进程的运行时间超出其允许值,发现陷入死循环的进程 ;
l对使用 CPU的用户进程记帐 ;
l处理进程的间隔时钟 (闹钟 );
l对系统的功能或部件提供监视定时器 。
2)控制台中断事件的处理
操作员可以利用控制台开关请求操作系统工作,当使用控制台开关后,就产生一个控制台中断事件通知操作系统 。 操作系统处理这种中断就如同接受一条操作命令一样,转向处理操作命令的程序执行 。
2.2.5 中断的优先级和多重中断
2.2.5.1 中断的优先级
中断装置按照预定的顺序来响应,这个预定的顺序称为中断的优先级,中断装置首先响应优先级高的中断事件 。
如何决定 中断源的优先顺序根据某个中断源或中断级若得不到及时响应,造成计算机出错的严重性程度而定 。
中断系统如何按予先规定的优先顺序响应呢?
可以使用硬件和软件两种办法 。
前者根据排定的优先次序做一个硬件链式排队器,当有高一级的中断事件产生时,
应该封住比它优先级低的所有中断源;
后者编写一个查询程序,依据优先级次序,
自高到低进行查询,一旦发现有一个中断请求,便转入该中断事件处理程序入口 。
1BM360/370系统的中断优先级由高到低的顺序是,
机器校验中断;自愿访管中断;程序性中断;外部中断;输入输出中断;重新启动中断 。
注意,中断的优先级只是表示中断装置响应中断的次序,而并不表示处理它的先后顺序 。
2.2.5.2 中断的屏蔽
主机可以允许或禁止某类中断的响应。如主机可以允许或禁止所有的输入输出中断、外部中断、
机器校验中断以及某些程序中断
有些中断是不能被禁止的,例如,计算机中的自愿访管中断就不能被禁止
主机是否允许某类中断;由当前程序状态字中的某些位来决定 。 一般,当屏蔽位为 1时,主机允许相应的中断,当屏蔽位为 0时,相应中断被禁止 。 按照屏蔽位的标志,可能禁止某一类内的全部中断,也可能有选择地禁止某一类内的部分中断 。
2.2.5.3 多重中断事件的处理
在一个计算机系统运行过程中,由于中断可能同时出现,或者虽不同时出现但却被硬件同时发现,或者出现在其它中断正在进行处理期间,这时 CPU又响应了这个新的中断事件,于是暂时停止正在运行的中断处理程序,转去执行新的中断处理程序,这就叫多重中断 (又称中断嵌套 )
对于多个不同类型的中断将区别不同情况作如下处理:
l 在运行 —个中断处理程序时,往往屏蔽某些中断;例如,在运行处理 I/O中断的例行程序时,可以屏蔽外部中断或其它 I/O中断 。
l 如前所述,中断可以分优先级,对于有些必须处理且优先级更高的中断源,采用屏蔽方法有时可能是不妥的,因此,在中断系统中往往允许在运行某些中断例行程序时,仍然可以响应中断,这时,系统应负责保护被中断的中断处理例行程序的现场 (有的计算机中断系统对断点的保存是在中断周期内,由中断隐指令实现,对用户是透明的 ),然后,再转向处理新中断的例行程序,以便处理结束时有可能返回原来的中断处理例行程序继续运行 。 操作系统必须预先作出规定,哪些中断类型允许嵌套?
嵌套的最大级数? 嵌套的级数视系统规模而定,
一般不超过三重为宜,因为过多重的 ‘ 嵌套 ’
将会增加不必要的系统开销 。
l在运行中断处理例行程序时,如果出现任何程序性中断源,一般情况下,表明这时中断处理程序有错误,应立即响应并进行处理 。
Unix中断和捕俘的多重中断
中断优先级共 6级,优先级从高到低为:
机器故障、时钟、磁盘 I/O、网络设备、
终端和软件中断
紧急中断事件优先级高,用户程序运行时中断事件优先级最低,可见任何中断事件都能中断用户程序运行
Unix多重中断 的实现
内核开辟中断现场保护区 -内核堆栈,总把当前被中断程序的现场信息压进堆栈 。 例如,若一个用户程序发出了一条系统调用,于是该用户程序被暂停,用户程序现场信息压进堆栈 。
在此系统调用处理期间,磁盘 I/O中断发生,
这时系统调用处理程序被中断,系统调用处理程序现埸信息压入堆栈 。 在磁盘 I/O中断处理程序处理期间,又发生了时钟中断,这时磁盘
I/O中断处理程序被中断,现场信息进入堆栈,
时钟中断处理程序开始工作 。 返回时逐级上推内核堆栈,依次逐级返回 。
2.2.6 实例研究,Windows 2000的中断处理
2.2.6.1 Windows 2000的中断处理概述
在 Pentium上 Windows中断的实现中,使用了陷阱,中断和异常等术语 。 中断和异常是把处理器转向正常控制流之外的代码的操作系统情况 。
中断是异步事件,可能随时发生,与处理器正在执行的内容无关 。
中断 主要由 I/O设备,处理器时钟或定时器,
以及软件等产生,可以启用或禁用 。
异常 是同步事件,它是某一个特定指令执行的结果 。 异常的一些例子是内存访问错误,调试指令,除数为零 。 内核也将系统服务调用视作异常,在技术也可以把它作为系统陷阱 。
陷阱 是指这样一种机制,当中断或异常发生时,它俘获正在执行的进程,把它从用户态切换到核心态,并将控制权交给内核的陷阱处理程序 。 不难看出,陷阱机制相当于本节前面所讨论的中断响应和中断处理机构 。
图 2-5 Windows2000的陷阱调度中断服务例程中断服务例程中断服务例程异常调度器虚存管理器的页面管理器中断调度器系统服务调度器异常调度器陷阱处理程序异常帧虚拟地址异常硬件异常软件异常系统服务调用中断陷阱处理程序
它在保存机器状态期间,暂时禁用中断 。 同时,创建一个,陷阱帧,来保存被中断线程的执行状态,当内核处理完中断或异常后恢复线程执行前,通过陷阱帧中保存的信息恢复处理器现场 。
陷阱处理程序本身可以解决一些问题,如一些虚拟地址异常,但在大多数情况下,陷阱处理程序只是确定发生的情况,并把控制转交给其它的内核或执行体模块 。
例如,如果情况是设备中断产生的,陷阱处理程序把控制转交给设备驱动程序提供给该设备的中断服务例程
ISR(Interrupt Service Routine);如果情况是调用系统服务产生的,陷阱处理程序把控制转交给执行体中的系统服务代码;其它异常由内核自身的异常调度器响应 。
2.2.6.2 Windows 2000的中断调度
1) 中断类型和优先级
中断请求级 IRQL(Interrupt Request Level)的标准集
IRQL将按照优先级排列中断,并按照优先级顺序服务中断,较高优先级中断抢占较低优先级中断服务
这一组内核维护的 IRQL是可以移植的,如果处理器具有特殊的与中断相关的特性 ( 如第二时钟 ),则可以增加可移植的 IRQL
图 2-6 x86体系结构 Windows的中断请求级系统关闭高31
掉电30
处理器间的中断29
时钟28
配置文件设备 n
………
设备 1
Dispatch/DPC2
APC1
低0
硬件中断软件中断正常的线程执行
Windows的中断屏蔽
每一个处理器都有一个 IRQL设置,其值随着操作系统代码的执行而改变,决定了该处理器可以接收哪些中断 。 IRQL也被用于同步访问核心数据结构 。 当核心态线程运行时,它可以提高或降低处理器的 IRQL。
如果中断源高于当前的 IRQL设置,则响应中断;否则该中断将被屏蔽,处理器不会响应该中断,直到一个正在执行的线程降低了 IRQL。
图 2-7 Windows的中断屏蔽高掉电处理器间的中断时钟配置文件设备 n
………
设备 1
在处理器 A上被屏蔽的中断
Dispatch/DPC
APC
低
IRQL=时钟处理器 A
在处理器 B上被屏蔽的中断
IRQL= Dispatch/DPC
处理器 B
2)中断处理
当中断产生时,陷阱处理程序将保存计算机运行程序的状态,然后禁用中断并调用中断调度程序 。 中断调度程序立刻提高处理器的 IRQL到中断源的级别,以使得在中断服务过程中屏蔽等于和低于当前中断源级别的其他中断 。 然后,重新启用中断,以使高优先级的中断仍然能够得到服务 。
中断分配表 IDT(Interrupt Dispatch Table)
Windows2000使用中断分配表
IDT(Interrupt Dispatch Table)来查找处理特定中断的例程。中断源的 IRQL作为表的索引,表的入口指向中断处理例程图 2-8 Windows的中断服务高掉电处理器间的中断时钟设备 n
…
…
…
设备 1
② 中断调度程序接收到中断源的 IRQL,
用作查询 IDT的索引
Dispatch/DPC
APC
低
① 有中断产生
…
…
…
线程调度程序 / DPC处理程序
( 无 )
系统关闭例程系统调电例程处理器间中断处理程序时钟处理程序设备 n ISR
设备 1 ISR
APC处理程序
③ 中断调度程序跟随该指针,
调用相应的处理程序
在 x86系统中,IDT是处理器控制区
PCR(Processing Control Region)指向的硬件结构,有的系统用软件实现 。
PCR和它的扩展 ——处理器控制块
PRCB包括了系统中各种处理器状态信息 。 内核和硬件抽象层 HAL使用该信息来执行体系结构特定的操作和机器特定的操作 。 这些信息包括:
当前运行线程,选定下一个运行的线程,处理器的中断级等等 。
中断服务例程执行结束处理
在中断服务例程执行之后,中断调度程序将降低处理器的 IRQL
到该中断发生前的级别,然后加载保存的机器状态 。 被中断的线程将从它停止的位置继续执行 。
多处理器系统中断处理
每个处理器都有单独的 IDT,这样,不同的处理器就可以运行不同的中断服务例程 ISR。 在多处理器系统中,每个处理器都可以收到时钟中断,但只有一个处理器在响应该中断时更新系统时钟 。
然而所有处理器都使用该中断来测量线程的时间片并在时间片结束后启动线程调度 。 同样的,某些系统配置可能要求特殊的处理器处理某一设备中断 。
中断对象
大多数处理中断的例程都在内核中,例如:内核更新时钟时间,在电源级中断产生时关闭系统 。 然而,键盘,I/O设备和磁盘驱动器等外部设备也会产生许多中断,这些设备的种类很多,变化很大,设备驱动程序需要一种方法来告诉内核:当设备中断发生时应调用哪个例程 。 为此,内核提供了一个可移植的机制 ——中断对象,它是一种内核控制对象,允许设备驱动程序注册其设备的 ISR。 中断对象包含内核所需的将设备
ISR和中断特定级相联系的所有信息,其中有,ISR地址,设备中断的 IRQL,以及与 ISR相联系的内核入口 。
当中断对象被初始化后,称为,调度代码,的一些汇编语言代码指令就会被存储在对象中 。 当中断发生时执行此代码,这个中断对象常驻代码调用真正的中断调度程序,给它传递一个指向中断对象的指针 。 中断对象包括了第二个调度程序例程所需要的信息,以便定位和正确使用设备驱动程序提供的 ISR。
连接 /分离一个中断对象
把 ISR与特殊中断级相关联称为连接一个中断对象,而从 IDT入口分离 ISR叫做断开一个中断对象 。 这些操作允许在设备驱动程序加载到系统时打开 ISR,在卸载设备驱动程序时关闭
ISR。 如果多个设备驱动程序创建多个中断对象并将它们连接到同一个 IDT入口,那么当中断在指定中断级上发生时,中断调度程序会调用每一个例程 。 这样就使得内核很容易地支持
,菊花链,配置,在这种构造中几个设备在相同的中断行上中断 。
使用中断对象的优点努
使用中断对象来注册 ISR,可以防止设备驱动程序直接随意中断硬件,并使设备驱动程序无需了解 IDT的任何细节,
从而有助于创建可移植的设备驱动程序 。
另外,通过使用中断对象,内核可以使
ISR与可能同 ISR共享数据的设备驱动程序的其他部分同步执行 。 进而,中断对象使内核更容易调用多个任何中断级的
ISR。
3)软件中断
Windows2000也为多种任务产生软件中断,他们包括:启动线程调度,处理定时器到时,在特定线程的描述表中异步执行一个过程,支持异步 I/O操作等 。
( 1)调度或延迟过程调用 DPC(Deferred
Procedure Call)中断
当一个线程不能继续执行时,内核应该直接调用调度程序实现描述符表切换 。 然而,
有时内核在深入多层代码内检测到应该进行重调度,最好的方法就是请求调度,但应延迟调度的产生直到内核完成当前的活动为止 。 使用 DPC软件中断是实现这种延迟的简单方法 。
当需要同步访问共享的内核结构时,内核总 是 将 处 理 器 的 IRQL 提 高 到
Dispatch/DPC级之上,这样就禁止了其他的软件中断和线程调度 。 当内核检测到调度应该发生时,它 将 请 求 一 个
Dispatch/DPC级中断;但是由于 IRQL等于或高于 Dispatch/DPC级,处理器将在检查期间保存该中断 。 当内核完成了当前活动后,它将 IRQL降低到 Dispatch/DPC级之下,于是调度中断就可以出现 。
除了延迟线程调度之外,内核在这个
IRQL上也处理延迟过程调用 DPC。 DPC
是执行系统任务的函数,该任务比当前任务次要,被称作 ’ 延迟函数 ’,因为,他们可能不立即执行 。 DPC为操作系统提供了在内核态下产生中断,并执行系统函数的能力 。 内核使用 DPC处理定时器到时并释放在定时器上等待的线程,在线程时间片结束后重调度处理器 。 设备驱动程序还可以通过 DPC完成延迟的 I/O请求 。
DPC由,DPC对象,表示,它也是一个内核控制对象 。 该对象对于用户态程序是不可见的,但对于设备驱动程序和其他系统代码是可见的 。 DPC对象包含的最重要信息是当内核处理 DPC中断时将调用的系统函数的地址 。 等待执行的 DPC例程被保存在叫做 DPC队列的内核管理队列中 。 为了调用一个 DPC,系统代码将调用内核来初始化 DPC对象,并把它放入 DPC队列中 。
将一个 DPC放入 DPC队列会促使内核请求一个在 Dispatch/DPC级的中断 。 因为通常 DPC是运行在较高 IR QL级的软件对它进行排队的,所以被请求中断直到内核降低 IRQL到 Dispatch/DPC级之下才出现,图 2-9描述了 DPC的处理 。
图 2-9 提交 DPC
高掉电
…
…
…
② 如果 IRQL降到比
Dispatch/DPC级低,
则 DPC中断发生 。
Dispatch/DPC
APC
低
① 定时器到时,内核排好 DPC队列,
准备释放等候在定时器上的所有线程,然后内核请求软件中断 。
…
…
…
调度程序
③ DPC中断之后,控制传送给 ( 线程 ) 调度程序
DPC
DPC DPC
④ 调度程序执行 DPC中的每一个 DPC例程,然后使队列变空 。 如果需要,调度程序还重新安排处理器
( 2)异步过程调用中断
异步过程调用 APC(Asynchronous procedure Call)
为用户程序 /系统代码提供了一种在特殊用户线程的描述表 ( 一个特殊的地址空间 ) 中执行代码的方法 。
APC由内核控制对象描述,称为 APC对象,等待执行的 APC在由内核管理的 APC队列中 。
APC队列和 DPC队列的不同之处在于,DPC队列是系统范围的,而 APC队列是属于每个线程的 。 当内核被要求对 APC排队时,内核将 APC
插入到将要执行 APC例程的线程的 APC队列中 。
内核依次请求 APC级的软件中断,并当线程最终开始运行时,执行 APC。
2.2.6.3 异常调度
异常是直接由运行程序的执行产生的情况 。
WIN32引入了结构化异常处理 (Structures
Exception Handling)工具,它允许应用程序在异常发生时可以得到控制 。 然后,应用程序可以固定这个状态并返回到异常发生的地方,从而终止引发异常的子例程的执行;也可以向系统声明不能识别异常,并继续搜寻能处理异常的异常处理程序 。
核心态异常
如果异常产生于核心态,异常调度程序将简单地调用一个例程来定位处理该异常的基于框架的异常处理程序 。 由于没有被处理的核心态异常是一种致命的操作系统错误,所以异常调度程序必须能找到异常处理程序 。
一些异常处理
内核俘获和处理某些对用户程序透明的异常,如调试断点异常,此时内核将调用调试程序来处理这个异常 。 少数异常可以被允许原封不动地过滤回用户态,如操作系统不处理的内存越界或算术异常 。 环境子系统能够建立
,基于框架的异常处理程序,来处理这些异常 。,基于框架的异常处理程序,是指与特殊过程激活相关的异常处理程序,当调用过程时,代表该过程激活的堆栈框架就会被推入堆栈,堆栈框架可以有一个或多个与它相关的异常处理程序,每个程序都保存在源程序的一个特定代码块内 。 当异常发生时,内核将查找与当前堆栈框架相关的异常处理程序,如果没有,内核将查找与前一个堆栈框架相关的异常处理程序,如此往复,如果还没有找到,内核将调用系统默认的异常处理程序 。
当异常发生时都将在内存产生一个事件链 。 硬件把控制交给陷阱处理程序,陷阱处理程序将创建一个陷阱框架 。 如果完成了异常处理,陷阱框架将允许系统从中断处继续运行 。 陷阱处理程序同时还要创建一个包含异常原因和其他有关信息的异常纪录 。
2.2.6.4 系统服务调度
INT 2E指令将引起一个系统陷阱用户态核心态系统服务调用 陷阱处理程序系统服务调度程序系统服务调度表
0
1
2
3
………
n
系统服务扩展系统服务 2
系统服务调度程序将校验正确的参数值,
并且将调用者的参数从线程的用户态堆栈复制到它的核心态堆栈中,然后执行系统服务 。 如果传递给系统服务的参数指向了在用户空间中的缓冲区,则在核心态代码访问用户缓冲区前,必须查明这些缓冲区的可访问性 。
系统服务表
每个线程都有一个指向系统服务表的指针 。
Windows2000有两个内置的系统服务表,第一个默认表定义了在 NTOSKRNL.EXE中实现的核心执行体系统服务;另一个包含了在 WIN32
子系统 WIN32K.SYS的核心态部分中实现的
WIN32 USER及 GDI服务 。 当 WIN32线程调用
WIN32 USER及 GDI服务时,线程系统服务表的地址将指向包含 WIN32 USER及 GDI的服务表 。
如图 2-11 所示,KERNEL32.DLL 中的
WIN32 WriteFile函数调用 NTDLL.DLL中的 NtWriteFile函数,它依次执行适当的指令以引发系统陷阱,传递代表 NtWriteFile
的系统服务号码 。 然后系统服务调度程序
( NTOSKRNL.EXE中的 KiSystemService
函数 ) 调用真正的 NtWriteFile来处理 I/O
请求 。 对于 WIN32 USER及 GDI函数,系统服务调度调用在 WIN32子系统可加载核心态部分 WIN32K.SYS中的函数 。
Windows200的系统服务调度调用
WriteFile()Win32应用程序调用 NtWriteFile
返回调用者
KERNEL32.DLL
中的 WriteFile
INT 2E
返回调用者
NTDLL.DLL
中的 NtWriteFile
调用 NtWriteFile
解除中断
NTOSKRNL.EXE中的
KiSystemService
执行操作返回调用者
NTOSKRNL.EXE
中的 NtWriteFile
调用 USER及 GDI
服务 应用程序
INT 2E
返回调用者
GDI32.DLL或
USER32.DLL
调用 WIN32例程解除中断
NTOSKRNL.EXE中的
KiSystemService
执行操作返回调用者
WIN32K.SYS
中的服务入口点用户态核心态
WIN32专用
WIN32专用所有子系统使用软件中断 软件中断
WIN32内核 API WIN32 USER及 GDI API
2.3 进程及其实现
2.3.1 进程的定义和性质
进程 由数据结构以及在其上执行的程序 (语句序列 )组成,是程序在这个数据集合上的运行过程,也是操作系统进行资源分配和保护的基本单位 。
进程的概念是操作系统中最基本,最重要的概念 。 它是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律而引进的一个新概念,所有的多道程序设计操作系统都建立在进程的基础上 。
操作系统专门引入进程的概念,从理论角度看,
是对正在运行的程序过程的抽象;从实现角度看,则是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序 。
进程 的 属性
l 结构性:进程包含了数据集合和运行于其上的程序 。
l 共享性:同一程序同时运行于不同数据集合上时,构成不同的进程 。 或者说,
多个不同的进程可以共享相同的程序 。
l 动态性:进程是程序在数据集合上的一次执行过程,是动态概念,同时,它还有生命周期,由创建而产生,由撤销而消亡;而程序是一组有序指令序列,
是静态概念,所以,程序作为一种系统资源是永久存在的 。
进程 的 属性
l独立性:进程既是系统中资源分配和保护的基本单位,也是系统调度的独立单位 (单线程进程 )。 凡是未建立进程的程序,都不能作为独立单位参与运行 。 通常,每个进程都可以各自独立的速度在
CPU上进行 。
l制约性:并发进程之间存在着制约关系,进程在进行的关键点上需要相互等待或互通消息,以保证程序执行的可再现性和计算结果的唯一性 。
进程 的 属性
l并发性:进程可以并发地执行 。 对于一个单处理器的系统来说,m个进程 P1,P2,…,Pm
是轮流占用处理器并发地执行 。 例如可能是这样进行的:,进程 P1执行了 nl条指令后让出处理器给 P2,P2执行了 n2条指令后让出处理器给
P3,…,Pm执行了 nm条指令后让出处理器给
P1,… 。 因此,进程的执行是可以被打断的,
或者说,进程执行完一条指令后在执行下一条指令前,可能被迫让出处理器,由其它若干个进程执行若干条指令后才能再次获得处理器而执行 。
操作系统中 为什么 要引入进程概念?
原因 1-它能较好地解决系统的,并发性,和刻划系统的,动态性,,它是并发程序设计的一种有力工具 。从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,则是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
原因 2-它能较好地解决系统的,共享性,
另一种称,可再用,程序由于它被调用过程中具有自身修改,在调用它的程序退出以前是不允许其它程序来调用它的。
,可再入,程序的是指能被多个程序同时调用的程序。,可再入”程序具有以下性质:它是纯代码的,即它在执行中自身不改变;调用它的各程序应提供工作区,因此,可再入的程序可以同时被几个程序调用。
举例
假定编译程序 P现在正在编译源程序甲,编译程序从 A点开始工作,当执行到 B点时需要将信息记到磁盘上,且程序 P在 B点等待磁盘传输 。 这时处理器空闲,为了提高系统效率,利用编译程序 P的
,可再入,性,可让编译程序 P再为源程序乙进行编译,仍从 A点开始工作 。 现在应怎样来描述编译程序 P的状态呢?称它为在 B点等待磁盘传输状态,
还是称它为正在从 A点开始执行的状态?编译程序 P
只有一个,但加工对象有甲,乙两个源程序,所以再以程序作为占用处理器的单位显然是不适合的了 。 为此,我们把编译程序 P,与服务对象联系起来,P为甲服务就说构成进程 P甲,P为乙服务则构成进程 P乙 。 这两个进程虽共享程序 P,
但它们可同时执行且彼此按各自的速度独立执行 。 现在我们可以说进程 P甲在 B
点处于等待磁盘传输,而进程 P乙正在从
A点开始执行 。 可见程序与计算 (程序的执行 )不再一一对应,延用程序概念不能描述这种共享性,因而,引入了新的概念 -进程 。
编译程序 P
(P 的入口,处理源程序乙 )
(P把源程序甲的信息记盘等磁盘完成 )
A
B
源程序甲 源程序乙
2.3.2 进程的状态和转换
2.1.1.1 三态模型
一个进程从创建而产生至撤销而消亡的整个生命周期,可以用一组状态加以刻划,为了便于管理进程,一般来说,按进程在执行过程中的不同状况至少定义三种不同的进程状态:
三种不同的进程状态:
l 运行态 ( running),占有处理器正在运行 。
l 就绪态 ( ready),具备运行条件,等待系统分配处理器以便运行 。
l 等待态 ( blocked),不具备运行条件,
正在等待某个事件的完成 。
图 2-12 进程三态模型及其状态转换运行态就绪态 等待态选中落选出现等待事件等待事件结束引起进程状态转换的具体原因:
l 运行态 —→ 等待态:等待使用资源;
如等待外设传输;等待人工干预 。
l 等待态 —→ 就绪态:资源得到满足;
如外设传输结束;人工干预完成 。
l 运行态 —→ 就绪态:运行时间片到;
出现有更高优先权进程 。
l 就绪态 —→ 运行态,CPU空闲时选择一个就绪进程 。
2.3.2.2 五态模型
进程五态模型及其转换 。
运行态就绪态 等待态选中落选出现等待事件等待事件结束新建态 终止态新建态
新建态对应于进程刚刚被创建的状态 。
创建一个进程要通过两个步骤,首先,是为一个新进程创建必要的管理信息,然后,让该进程进入就绪态 。 此时进程将处于新建态,它并没有被提交执行,而是在等待操作系统完成创建进程的必要操作 。 必须指出的是,操作系统有时将根据系统性能或主存容量的限制推迟新建态进程的提交 。
终止态
进程的终止也要通过两个步骤,首先,是等待操作系统进行善后,然后,退出主存。当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止态。进入终止态的进程以后不再执行,但依然临时保留在操作系统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。
进程状态转换的具体原因如下:
lNULL—→ 新建态:执行一个程序,创建一个子进程 。
l新建态 —→ 就绪态:当操作系统完成了进程创建的必要操作,并且当前系统的性能和虚拟内存的容量均允许 。
l运行态 —→ 终止态:当一个进程到达了自然结束点,
或是出现了无法克服的错误,或是被操作系统所终结,
或是被其他有终止权的进程所终结 。
l终止态 —→NULL,完成善后操作 。
l就绪态 —→ 终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程 。
l等待态 —→ 终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程 。
2.3.2.3 进程的挂起
为什么要有,挂起,状态?
事实上,可能出现这样一些情况,例如由于进程的不断创建,系统的资源已经不能满足进程运行的要求,这个时候就必须把某些进程挂起( suspend),对换到磁盘镜像区中,
暂时不参与进程调度,起到平滑系统操作负荷的目的。
l系统中的进程均处于等待状态,处理器空闲,此时需要把一些阻塞进程对换出去,以腾出足够的内存装入就绪进程运行 。
l进程竞争资源,导致系统资源不足,负荷过重,
此时需要挂起部分进程以调整系统负荷,保证系统的实时性或让系统正常运行 。
l 把一些定期执行的进程 ( 如审计程序,监控程序,
记账程序 ) 对换出去,以减轻系统负荷 。
l用户要求挂起自己的进程,以便根据中间执行情况和中间结果进行某些调试,检查和改正 。
l父进程要求挂起自己的后代进程,以进行某些检查和改正 。
l操作系统需要挂起某些进程,检查运行中资源使用情况,以改善系统性能 ;或当系统出现故障或某些功能受到破坏时,需要挂起某些进程以排除故障 。
图 2-14 具有挂起功能系统的进程状态及其转换挂起等待事件结束出现等待事件解除挂起 挂起落选选中运行态就绪态等待事件结束终止态新建态挂起就绪态解除挂起 挂起挂起等待态等待态提交提交具有挂起进程功能系统中的进程状态
。 在此类系统中,进程增加了两个新状态:挂起就绪态 ( ready suspend) 挂起就绪态表明了进程具备运行条件但目前在二级存储器中,只有当它被对换到主存才能被调度执行 。
挂起等待态 ( blocked suspend) 挂起等待态则表明了进程正在等待某一个事件且在二级存储器中 。
进程状态转换的具体原因如下:
l 等待态 —→ 挂起等待态:如果当前不存在就绪进程,
那么至少有一个等待态进程将被对换出去成为挂起等待态;
操作系统根据当前资源状况和性能要求,可以决定把等待态进程对换出去成为挂起等待态 。
l 挂起等待态 —→ 挂起就绪态:引起进程等待的事件发生之后,相应的挂起等待态进程将转换为挂起就绪态 。
l 挂起就绪态 —→ 就绪态:当内存中没有就绪态进程,或者挂起就绪态进程具有比就绪态进程更高的优先级,系统将把挂起就绪态进程转换成就绪态 。
l 就绪态 —→ 挂起就绪态:操作系统根据当前资源状况和性能要求,也可以决定把就绪态进程对换出去成为挂起就绪态 。
挂起进程具有如下特征:
l该进程不能立即被执行 。
l挂起进程可能会等待一个事件,但所等待的事件是独立于挂起条件的,事件结束并不能导致进程具备执行条件 。
l进程进入挂起状态是由于操作系统,父进程或进程本身阻止它的运行 。
l结束进程挂起状态的命令只能通过操作系统或父进程发出 。
2.3.3 进程的描述
2.3.3.1 操作系统的控制结构
操作系统的核心控制结构是进程结构,资源管理的数据结构将围绕进程结构展开。
操作系统的控制表分为四类,进程控制表,存储控制表,I/O
控制表和文件控制表 。
l进程控制表用来管理进程及其相关信息 。
l存储控制表用来管理一级 ( 主 ) 存储器和二级
( 虚拟 ) 存储器,主要内容包括:主存储器的分配信息,二级存储器的分配信息,存储保护和分区共享信息,虚拟存储器管理信息 。
lI/O控制表用来管理计算机系统的 I/O设备和通道,
主要内容包括,I/O设备和通道是否可用,I/O设备和通道的分配信息,I/O操作的状态和进展,I/O操作传输数据所在的主存区 。
l文件控制表用来管理文件,主要内容包括:被打开文件的信息,文件在主存储器和二级存储器中的位置信息,被打开文件的状态和其他属性信息。
图 2-15 操作系统控制表的通用结构
Memory
Devices
Files
Processes
Memory Tables
I/O Tables
File Tables
Primary Process Table
Process 1
……
Process N
Process 2
Process Image
Process 1
Image
……
Process N
Image
2.3.3.2 进程映像
操作系统中把进程物理实体和支持进程运行的环境合称为进程上下( context)。
当系统调度新进程占有处理器时,新老进程随之发生上下文切换。因此,进程的运行被认为是在上下文中执行。
操作系统进程上下文包括三个组成部分:
l用户级上下文:由用户程序块,用户数据块 ( 含共享数据块 ) 和用户堆栈组成的进程地址空间 。
l系统级上下文:包括进程的标识信息,
现场信息和控制信息,进程环境块,以及系统堆栈等组成的进程地址空间 。
l寄存器上下文:由程序状态字寄存器和各类控制寄存器,地址寄存器,通用寄存器组成 。
进程有四个要素组成:
控制块、
程序块、
数据块、
堆栈。
l进程程序块,即被执行的程序,规定了进程一次运行应完成的功能 。 通常它是纯代码,作为一种系统资源可被多个进程共享 。
l进程数据块,即程序运行时加工处理对象,
包括全局变量,局部变量和常量等的存放区以及开辟的工作区,常常为一个进程专用 。
l系统 /用户堆栈,每一个进程都将捆绑一个系统 /用户堆栈 。 用来解决过程调用或系统调用时的地址存储和参数传递 。
l进程控制块,每一个进程都将捆绑一个进程控制块,用来存储进程的标志信息,现场信息和控制信息 。 进程创建时,建立一个 PCB;进程撤销时,回收 PCB,它与进程一一对应 。
图 2-16 用户进程在虚拟内存中的组织进程标识信息进程现场信息进程控制信息用户堆栈共享地址空间用户私有地址空间
(代码、数据)
进程控制块
2.3.3.3 进程控制块
进程控制块 (Process Control
Block),是操作系统用于记录和刻划进程状态及有关信息的数据结构。也是操作系统掌握进程的唯一资料结构,它包括了进程执行时的情况,以及进程让出处理器后所处的状态、断点等信息。
进程控制块包含三类信息:
l标识信息 。 用于唯一地标识一个进程,常常分由用户使用的外部标识符和被系统使用的内部标识号 。 几乎所有操作系统中进程都被赋予一个唯一的,内部使用的数值型的进程号,操作系统的其他控制表可以通过进程号来交叉引用进程控制表 。 常用的标识信息有进程标识符,
父进程的标识符,用户进程名,用户组名等 。
l现场信息 。 用于保留一个进程在运行时存放在处理器现场中的各种信息,任何一个进程在让出处理器时必须把此时的处理器现场信息保存到进程控制块中,而当该进程重新恢复运行时也应恢复处理器现场 。 常用的现场信息包括通用寄存器的内容,控制寄存器 (如 PSW寄存器 )的内容,用户堆栈指针,系统堆栈指针等 。
进程控制块包含三类信息:
l 控制信息 。 用于管理和调度一个进程 。 常用的控制信息包括,1) 进程的调度相关信息,
如进程状态,等待事件和等待原因,进程优先级,队列指引元等; 2) 进程组成信息,如正文段指针,数据段指针 ;3) 进程间通信相关信息,
如消息队列指针,信号量等互斥和同步机制;
4) 进程在二级存储器内的地址; 5) CPU资源的占用和使用信息,如时向片余量,进程己占用 CPU的时间,进程己执行的时间总和,记帐信息 ;6) 进程特权信息,如在内存访问和处理器状态方面的特权 。 7) 资源清单,包括进程所需全部资源,已经分得的资源,如主存资源,I/O
设备,打开文件表等 。
2.3.3.4 进程管理
把处于同一状态 (例如就绪态 )的所有进程控制块链接在一起,这样的数据结构称为进程队列 (Process Queues),简称队列。
同一状态进程的 PCB既可按先来先到的原则排成队列 ;也可以按优先数或其它原则排成多个队列。例如,对于等待态的进程队列可以进一步细分,每一个进程按等待的原因进入相应的等待队列。
链接进程控制块的方法
单向链接方法是在每个进程控制块内设置一个队列指引元,它指出在队列中跟随着它的下一个进程的进程控制块内队列指引元的位置。
双向链接方法是在每个进程控制块内设置两个指引元,其中一个指出队列中该进程的上一个进程的进程控制块内队列指引元的位置,另一个指出队列中该进程的下一个进程的进程控制块的队列指引元的位置。
队列标志
为了标志和识别一个队列,系统为每一个队列设置一个队列标志,单向链接时,队列标志指引元指向队列中第一个进程的队列指引元的位置 ;双向链接时,队列标志的前向指引元指向队列中第一个进程的前向队列指引元的位置 ; 队列标志的后向指引元指向队列中最后一个进程的后向队列指引元的位置图 2-17 进程控制块的链接队列指引元
0
队列指引元
0
0前向后向
( a) 单向连接 ( b) 双向连接队列管理
一个进程从一个所在的队列中退出的工作称为出队,相反,一个进程排入到一个指定的队列中的工作称为入队。处理器调度中负责入队和出队工作的功能模块称为队列管理模块,简称队列管理。
图 2-18 操作系统的队列管理和状态转换示意图处理器指派提交完成超时事件 1等待队列事件 2等待队列事件 n等待队列就绪队列
……
等待事件 1
等待事件 2
等待事件 n
事件 1
出现事件 2
出现事件 n
出现双向链接
假设采用双向链接,每个进程有两个队列指引元,用来指示该进程在队列中的位置,其中第一个队列指引元为前向指引元,第二个为后向指引元根据一个进程排在一个队列中的情况,前
(后 )向指引元的内容规定如下:
l情况 1:它是队列之首 。 此时,它的前向指引元为 0,而后向指引元指出它的下一个进程的后向指引元位置 。
l情况 2:它是队列之尾 。 此时,它的后向指引元为 0,而它的前向指引元指出它的上一个进程的前向指引元位置 。
l情况 3:它的前后均有进程 。 此时,前 (后 )
向指引元指出它的上 (下 )一个进程的前 (后 )
向指引元位置 。
进程的出队举例
考虑一个进程的出队,假设进程 Q在某个队列中,它的前面是进程 P,后面是进程 R。 进程 Q
出队过程为:把 Q的前向指引元的内容送到 R
的前向指引元中,把 Q的后向指引元的内容送到 P的后向指引元中 。 于是 P的后向指引元指向 R,而 R的前向指引元指向 P,Q就从队列中退出 。 类似的可实现队首进程,队尾进程的出队,或者让一个进程加入到队列中去 。
表格法组织 PCB的
此外,用来组织 PCB的方法为表格法 。 把所有进程的 PCB都组织在一个线性表中,
进程调度时需要查找整个 PCB表 ;也可以把相同状态进程的 PCB组织在一个线性表中,系统有多个线性表,这样可缩短查表时间 。
2.3.4 进程的控制
2.3.4.1进程的创建进程的创建来源于以下四个事件:
l提交一个批处理作业 。
l在终端上交互式的登录 。
l操作系统创建一个服务进程 。
l存在的进程孵化 ( spawn) 新的进程 。
进程孵化
当一个用户作业被接受进入系统后,可能要创建一个或多个进程来完成这个作业;一个进程在请求某种服务时,也可能要创建一个或多个进程来为之服务 。 例如,当一个进程要求读卡片上的一段数据时,可能创建一个卡片输入机管理进程 。
有的系统把,孵化,用父子进程关系来表示,当一个进程创建另一个进程时,生成进程称父进程
(Parent Process),被生成进程称子进程 (Child
Process),即一个父进程可以创建子进程,从而形成树形的结构,如 Unix就是这样,当父进程创建了子进程后,子进程就继承了父进程的全部资源,父子进程常常要相互通信和协作,当子进程结束时,又必须要求父进程对其作某些善后处理 。
进程的创建过程如下描述:
l 在主进程表中增加一项,并从 PCB池中取一个空白 PCB。
l 为新进程的进程映像中的所有成分分配地址空间 。 对于进程孵化操作还需要传递环境变量,构造共享地址空间 。
l 为新进程分配资源,除内存空间外,还有其它各种资源 。
l 查找辅存,找到进程正文段并装到正文区 。
l 初始化进程控制块,为新进程分配一个唯一的进程标识符,初始化 PSW。
l 加入某一就绪进程队列,或直接将进程投入运行 。
l 通知操作系统的某些模块,如记账程序,性能监控程序 。
2.3.4.2 进程上下文切换
进程的切换就是让处于运行态的进程中断运行,让出处理器,这时要做一次进程上下文切换、即保存老进程状态而装入被保护了的新进程的状态,以便新进程运行。
进程切换的步骤如下:
l保存被中断进程的处理器现场信息 。
l修改被中断进程的进程控制块的有关信息,
如进程状态等 。
l把被中断进程的进程控制块加入有关队列 。
l选择下一个占有处理器运行的进程 。
l修改被选中进程的进程控制块的有关信息 。
l根据被选中进程设置操作系统用到的地址转换和存储保护信息 。
l根一据被选中进程恢复处理器现场 。
模式切换 与 上下文切换当中断发生的时候,暂时中断正在执行的用户进程,把进程从用户状态切换到内核状态,去执行操作系统例行程序以获得服务,这就是一次 模式切换,注意,此时仍在该进程的上下文中执行,仅仅模式变了。内核在被中断了的进程的上下文中对这个中断事件作处理,即使该中断可能不是此进程引起的。另一点要注意的是被中断的进程可以是正在用户态下执行的,
也可以是正在核心态下执行的,内核都要保留足够信息以便在后来能恢复被中断了的进程执行。内核在核心态下对中断事件进行处理,决不会再产生或调度一个特殊进程来处理中断事件。
模式切换的步骤如下:
l 保存被中断进程的处理器现场信息 。
l 根据中断号置程序计数器 。
l 把用户状态切换到内核状态,
以便执行中断处理程序 。
CPU模式切换不同于进程上下文切换
显然模式切换不同于进程切换,它并不引起进程状态的变化,在大多数操作系统中,它也不一定引起进程的切换,在完成了中断调用之后,
完全可以再通过一次逆向的模式切换来继续执行用户进程。
Unix中进程上下文切换和模式切换
Unix中存在两类进程:系统进程和用户进程,系统进程在核心态下执行操作系统代码,用户进程在用户态下执行用户程序 。 用户进程因中断和系统调用进入内核态,系统进程开始执行,这两个进程 (用户进程和系统进程 )使用同一个 PCB,所以实质上是一个进程 。 但是这两个进程所执行的程序不同,映射到不同物理地址空间,使用不同堆栈 。 一个系统进程的地址空间中包含所有的系统核心程序和各进程的进程数据区,所以,各进程的系统进程除数据区不同外,其余部分全相同,
但各进程的用户进程部分则各不相同 。 图 2- 19是
Unix中进程上下文切换和模式切换示意 。 其中,
1为进程在用户态下执行,2为进程在核心态下执行,3进程为就绪态,4进程为等待态 。 任何时刻
Unix中进程上下文切换和模式切换
一个处理器仅能执行一个进程,所以至多有一个进程可以处在状态 1或状态 2,这两个状态相应于用户态和核心态 。 在多道程序设计系统中,
有许多进程在并发执行,如果两个以上进程同时执行系统调用,并要求在核心态下执行,则有可能破坏核心数据结构中的信息 。 通过禁止任意的上下文切换和控制中断的响应,就能保证数据的一致性 。 图中示意,仅当进程从,核心态运行,状态转为,在内存中等待,状态时,内核才允许上下文切换 。 在核心态下的进程不能被其他进程抢占,或者说进程在核心态下执行时不允许上下文切换 。 由于内核处于不可抢占态,
所以内核可保持其数据结构的完整和一致 。
图 2-19 进程上下文切换和模式切换核心态运行 2
系统调用或中断 (隐含模式切换 ) 模式切换用户态运行 1
等待状态 4
就绪状态 3
发生事件唤醒调度进程 调度进程中断、
中断返回允许的上下文切换切换
2.3.4.3 进程的阻塞和唤醒进程唤醒的步骤如下:
l从相应的等待进程队列中取出进程控制块 。
l修改进程控制块的有关信息,如进程状态等 。
l把修改后进程控制块加入有关就绪进程队列 。
2.3.4.4 进程的撤销进程撤销的主要原因包括:
l进程正常运行结束 。
l进程执行了非法指令 。
l进程在常态下执行了特权指令 。
l 进程运行时间超越了分配给它的最大时间段 。
l 进程等待时间超越了所设定的最大等待时间 。
l 进程申请的内存超过了系统所能提供最大量 。
l 越界错误 。
进程撤销的主要原因包括:
l 对共享内存区的非法使用 。
l 算术错误,如除零和操作数溢出 。
l 严重的输入输出错误 。
l 操作员或操作系统干预 。
l 父进程撤销其子进程 。
l 父进程撤销 。
l 操作系统终止 。
撤销原语终止进程,具体步骤如下:
l根据撤销进程标识号,从相应队列中找到它的 PCB;
l将该进程拥有的资源归还给父进程或操作系统;
l若该进程拥有子进程,应先撤销它的所有子孙进程,以防它们脱离控制;
l撤销进程出队,将它的 PCB归还到 PCB
池 。
2.3.5 进程管理的实现进程管理一些程序模块:
l队列管理模块:负责进程队列的入队和出队工作 。
l 进程调度程序:负责选择下一个占有处理器运行的进程 。
l资源分配程序:负责分配资源给进程 。
l上下文切换程序:负责进程切换和模式切换 。
l 进程控制原语:提供了若干基本操作以管理和控制进程 。
如:建立进程原语,撤销进程原语,阻塞进程原语,唤醒进程原语,挂起进程原语,解除挂起进程原语,改变进程优先数原语,修改进程状态原语等等 。
l 进程通信原语:提供了若干基本操作以便进程间通信 。
如,P操作原语,V操作原语,管程原语,send原语,
receive原语等等 。
l 其他 。
2.3.5.2 进程管理的实现模型
1) 非进程内核模型
2) OS功能在用户进程内执行的实现模型
3) OS功能由进程实现的模型
>
2.3.6 实例研究 ——Unix SVR4
的进程管理
Unix SVR4的进程管理的实现采用基于用户进程的实现模型,大多数操作系统功能在用户进程的环境中执行,因此它需要在用户模式和内核模式切换 。 Unix SVR4允许两类进程:用户进程和系统进程 。 系统进程在内核模式下执行,完成操作系统的一些重要功能,如内存分配和进程对换 。
而用户进程在用户模式下执行用户程序,
在内核模式下执行操作系统的代码,系统调用,中断和异常将引起模式切换 。
2.3.6.1 Unix SVR4的进程状态进程状态包括:
lUser running:在用户模式下运行 。
lKernel running:在内核模式下运行 。
lPreempted:当一个进程从内核模式返回用户模式时,发生了进程切换后处于的就绪状态 。
lReady to run,in memory:就绪状态,在内存 。
lAsleep in memory:等待状态,在内存 。
lReady to run,swapped:就绪状态,被对换出内存,
不能被调度执行 。
lSleeping,swapped:睡眠状态,被对换出内存 。
lZombie:终止状态:进程已不存在,留下状态码和有关信息使父进程收集 。
图 2-24 Unix SVR4进程状态及其转换
Ready to run,
in memory
User
running
Kernel
running
Preempted
Ready to run,
swapped
Created
Asleep
in memory
Sleep,
swappedZombie
Runturn to user
Interrupt
System call
Return
Preempt
Reschedule
Process
Exit
Interrupt,
Interrupt return
Enough
Memory
No enough
Memory
Wakeup Wakeup
Swap out
Swap in
Swap out
Sleep
Fork
Unix进程树
Unix操作系统中有两个固定的进程,0号进程是 swap进程,在系统自举时被创建;
1号进程是 init进程,由 0号进程孵化而创建 。 系统中的其他进程都是 1号进程的子进程,当一个交互式用户登录到系统中时,
1号进程为这个用户创建一个用户进程,
用户进程在执行具体应用是进一步的创建子进程,从而构成一棵进程树 。
2.3.6.2 Unix SVR4的进程描述
UNIX的进程由三部分组成:
proc结构、数据段和正文段,它们合称为进程映像,Unix中把进程定义为映像的执行。其中,PCB
由基本控制块 proc结构和扩充控制块 user结构两部
分组成。
在 proc结构里存放着关于一个进程的最基本,最必需的信息,因此它常驻内存;
在 user结构里存放着只有进程运行时才用到的数据和状态信息,为了节省内存空间,
当进程暂时不在处理机上运行时,就把它放在磁盘上的对换区中,进程的 user结构总和进程的数据段一起,在主存和磁盘对换区之间换进 /换出 。
图 2-25 Unix 进程组成
process
structure
user
structure
kernel
stack
text
structure
stack
data
text
System data structure
user space
Swappable process image
Resident tables
系统中维持一张名叫 proc的进程表,共有 50个表目,每个表目为一个 proc结构,供一个进程使用,
因此,UNIX中最多同时可存在 50
个进程 。 创建进程时,在 proc表中找一个空表目,以建立起相应于该进程的 proc结构 。
进程映像 内容
lproc结构 包括进程标识符,父进程标识符,进程用户标识符,进程状态,等待的事件,调度优先数,进程的大小,
指向 user结构和进程存储区 (text/data/stack)
的指针,有关进程执行时间 /核心资源使用 /用户设置示警信号等的计时器,就绪队列指针等 。
luser结构 包括现场保护,内存管理,系统调用,文件管理,文件读写,时间信息,映象位置,用户标识,用户组标识,
用户打开文件表,各种标志等 。
进程映像 内容
l系统数据结构 进程系统数据区,通常称作
ppda,它位于数据段的前面,进程 proc结构中的
P-addr指向这个区域的首址 。 该区共有 1024个字节,由两块内容组成:最前面的 289个字节为进程的扩充控制块 user结构,剩下的 734个字节为核心栈,当进程运行在核心态时,这里是它的工作区,用来保存过程调用和中断访问时用到的地址和参数 。
l用户数据区 通常存放程序运行时用到的数据,
如果进程运行的程序是非共享的,那么这个程序也放于此地 。
l用户栈区 当进程运行在用户态时,这里是它的工作区 。
进程映像 内容
l text结构 正文段在磁盘上和主存中的位置和大小,访问正文段进程数,在主存中访问正文段进程数,标志信息,地址转换信息 。
l 由于共享正文段在进程映象中的特殊性,为了便于对它们的管理,
UNIX系统在内存中设置了一张正文段表 。 该表共有 40个表目,每一个表目都是一个 text结构,用来记录一个共享正文段的属性 ( 磁盘和主存中的位置,尺寸,共享的进程数等,正文段文件节点指针 ),有时也把这种结构称为正文段控制 ( 信息 ) 块 。 这是可以被多个进程共享的可重入程序和常数,如果一个进程的程序是不被共享的,那么它的映象中就不出现这一部分 。 若一个进程有共享正文段,那么当把该进程的非常驻内存部分调入内存时,应该关注共享正文段是否也在内存,如果发现不在内存,则要将它调入;当把该进程的非常驻内存部分调出内存时,同样要关注它的共享正文段目前被共享的情况,只要还有一个别的共享进程的映象全部在内存,那么这个共享正文段就不得调出去 。 如果一个进程有共享正文段,那么该共享正文段在正文段表里一定有一个 text结构与之相对应,而在该进程的基本控制块 proc
里,p-textp就指向这一个 text结构 。 综上所述,在 UNIX进程映象的三个组成部分中,proc,user和 text这三个数据结构是最为重要的角色 。
进程映像 内容
l进程区域表 系统为每个进程建立一张进程区域表 PPRT( Per Process Region Table) 由存储管理系统使用,它定义了物理地址与虚拟地址之间的对应关系,还定义了进程对存储区域的访问权限 。 其中含有正文段,数据段和堆栈的区域表的指针和各区域的逻辑起始地址 ;区域表中含有该区域属性 (正文 /数据,可否共享 )
的信息和页表的指针 ;而每个页表中含有相应区域的页面在内存的起始地址 。
图 2-26 proc结构,user结构和 text结构关系
2.3.6.3 Unix SVR4的进程创建
Unix SVR4通过系统调用 fork( )来创建一个子进程 当父进程调用 pid=fork( )后,操作系统执行下面的操作:
l为子进程分配一个进程表 。
l为子进程分配一个进程标识符 。
l 复制父进程的进程映像,但不复制共享内存区 。
l 增加父进程所打开文件的计数,表示新进程也在使用这些文件 。
l把子进程置为 Ready to Run状态 。
l返回子进程的标识符给父进程,把 0值返回给子进程 。
以上的操作都是父进程在内核模式下完成的,然后父进程还应执行以下的操作之一,
以完成进程的指派:
l 继续呆在父进程中 。 此时把进程控制切换到父进程的用户模式,在 fork()点继续向下运行,而子进程进入 Ready to Run状态 。
l把进程控制传递到子进程,子进程在
fork()点继续向下运行,而父进程进入
Ready to Run状态 。
l 把进程控制传递到其他进程,父进程和子进程进入 Ready to Run状态 。
Unix如何用 fork( )来创建子进程的程序:
/*spawn the process*/
main( ) {
int pid; /*process-id*/
printf(“Just one process so far\n”);
printf(“Calling fork … \n);
pid=fork( ); /*Craat new process*/
if(pid==o)
printf (“I am the child process\n);
else if(pid>0)
printf(“I am the parent process,child has pid d%\n”,pid);
else
printf(“fork returned error code,no child\n”);
}
Fork( )调用前
Fork( )调用后子进程父进程父进程
Printf(“I am the child,…,)
Printf(“I am the
parent,…,)
1子进程是父进程的复制品,完全一样
2 父子进程的指令执行点都在 fork( )语句之后的那个语句
3 fork( )没有参数,但返回值 pid不一样,子进程中 pid为 0;父进程中 pid为非 0正整数 (即子进程的内部标识号 )
4 父子进程可根据不同 pid值执行不同程序,完成不同工作
2.3.7 实例研究,Linux进程管理
2.3.7.1 进程和进程状态
Linux的进程概念与传统操作系统中的进程概念完全一致,它目前不支持线程概念,进程是操作系统调度的最小单位 。
进程和任务
用户态称进程
核心态称任务实质上是一个实体
Linux的进程状态有 6种:
lTASK_RUNNING:正在运行或准备运行的进程 。
lTASK_INTERRUPTIBLE:处于等待队列中的进程,一旦资源可用时被唤醒,也可以由其他进程通过信号
( SIGNAL) 或定时中断唤醒 。
lTASK_UNINTERRUPTIBLE:处于等待队列中的进程,
一旦资源可用时被唤醒,也不可以由其他进程通过信号
( SIGNAL) 或定时中断唤醒 。
lTASK_ZOMBIE:进程运行结束但是尚未消亡时处于的状态 。
lTASK_STOPPED:进程被暂停,正在等待其他进程发出的唤醒信号 。
lTASK_SWAPPING:页面被交换出内存的进程 。
运行和就绪是一个队列
图 2-27 Linux的进程状态转换
TASK_RUNNING
TASK_UNINTERRUPTIBLE TASK_INTERRUPTIBLE
TASK_STOPPED TASK_ZOMBIE
占有 CPU
schedulle() 时间片到 申请资源未果,调用
interruptible_sleep_on()
申请资源未果,
调用 sleep_on()
申请资源可用后
wake_up()
申请资源可用,
收到信号,wake_up()、
wake_up_interruptible()
do_fork()
do_exit()跟踪系统调用,执行 syscall_trace(),
sys_exit(),schedulle()
收到 SIG_KILL或 SIG_CONT
后,执行 wake_up()
慢中断与快中断
有三点差别,
*寄存器保护
*屏蔽中断事件
*是否返回被中断的进程
Linux创建进程
创建进程的系统调用 sys_fork()和 sys_clone都通过调用 do_fork()函数来完成进程的创建 。 在 do_fork()
函数中,它首先分配进程控制块 task_struct的内存和进程所需的堆栈,并监测系统是否可以增加新的进程;然后拷贝当前进程的内容,并对一些数据成员进行初始化;再为进程的运行做准备;最后返回生成的新进程的进程标识号 ( pid) 。 如果进程是根据 sys_clone()产生的,那么它的进程标识号就是当前进程的进程标识号,并且对于进程控制块中的一些成员指针并不进行复制,而仅仅把这些成员指针的计数 count增加 1。 这样,父子进程可以有效地共享资源 。
进程终止
进程终止的系统调用 sys_exit()通过调用
do_exit()函数实现 。 函数 do_exit()首先释放进程占用的大部分资源,然后进入小
TASK_ZOMBIE状态,调用 exit_notify()
通知小 。
2.3.7.2 进程控制块
Linux的进程控制块由结构
struct task_struct描述,包括在 /include/linux/sched.h中 。
它的构成简述如下:
Linux进程控制块
调度用数据成员
State 进程状态
Flags 进程状态标记
Priority 进程优先数
rt_priority 实时进程优先数
Counter 时间片
Policy 调度策略 ( 0基于优先权的时间片轮转,1基于先进先出的实时调度,2基于优先数的实时调度 )
Linux进程控制块
信号处理
Signal 记录进程接收到的信号,共 32位,
每位对应一种信号
Blocked 进程接收到信号的屏蔽位
Sig 信号对应的处理函数
Linux进程控制块
进程队列指针
next_task,prev_task 双向链接指针
next_run,prev_run 就绪队列双向链接指针
p_opptr,p_pptr,p_cptr,p_ysptr,
p_osptr 分别指向原始父进程,父进程,
子进程,新老兄弟进程
Linux进程控制块
进程标识
uid,gid 用户标识和组标识
Group 允许进程同时拥有的一组用户组号
euid,egid 有效的 uid和 gid,用于系统安全考虑
fsuid,fsgid 文件系统的 uid和 gid
suid,sgid 系统调用改变 uid和 gid时,用于存放真正的 uid和 gid
pid,pgrp,session 进程标识号,组标识号,
session标识号
Leader 是否 session的主管
Linux进程控制块
时间数据成员
Timeout 指出进程间隔多久被重新唤醒
it_real_value,it_real_incr 用于时间片计时
real_timer 一种定时器结构
it_virt_value,it_virt_incr 进程用户态执行时间的软件定时
it_prof_value,it_prof_incr 进程执行时间的软件定时
utime,stime,cutime,cstime,start_time
进程在用户态,内核态的运行时间,所有层次进程在用户态,内核态的运行时间,创建进程的时间
Linux进程控制块
信号量
Semundo 进程每次操作信号量的 undo操作
Semsleeping 信号量对应的等待队列
上下文
Ldt 进程关于段式存储管理的局部描述符指针
Tss 通用寄存器
saved_kernel_stack 为 MSDOS仿真程序保存的堆栈指针
saved_kernel_page 内核堆栈基地址
文件系统
Fs 保存进程与 VFS的关系信息
Files 系统打开文件表
link_count 文件链的数目
Linux进程控制块
内存数据成员
世的 mm_struct结构
Swappable 指示页面是否可以换出
swap_address 换出用的地址
min_flt,maj_flt 该进程累计的缺页次数
Nswap 该进程累计换出的页面数
cmin_flt,cmaj_flt 该进程及其所有子进程累计的缺页次数
Cnswap 该进程及其所有子进程累计换出的页面数
swap_cnt 下一次循环最多可以换出的页数
Linux进程控制块
SMP支持
Processor 进程正在使用的 cpu
last_processor 进程上一次使用的 cpu
lock_depth 上下文切换时系统内核锁的深度
其他
used_math 是否使用浮点运算器
Comm 进程对应的可执行文件的文件名
Rlim 系统使用资源的限制
Errno 错误号
Debugreg 调试寄存器
Linux进程控制块
exec_domain,Personality 与运行 iBCS2标准程序有关
Binfmt 指向全局执行文件格式结构,包括
a.out,script,elf,java
exit_code,exit_signal 返回代码,出错信名
Dumpable 出错时是否能够进行 memory dump
did_exec 用于区分新老程序代码
tty_old_pgrp 进程显示终端所在的组标识
Tty 指向进程所在的终端信息
wait_chldexit 在进程结束需要等待子进程时处于的等待队列,
2.4 线程及其实现
2.4.1 引入多线程技术的动机
单线程 ( 结构 ) 进程 ( Single Threaded
Process)
多线程 ( 结构 ) 进程 ( Multiple Threaded
process)
单线程结构的进程,给并发程序设计效率带来了一系列新的问题,主要表现在:
l进程切换的开销大,频繁的进程调度将耗费大量处理器时间 。
l进程之间通信的代价大,每次通信均要涉及通信进程之间以及通信进程与操作系统之间的切换 。
l进程之间的并发性粒度较粗,并发度不高,过多的进程切换和通信使得细粒度的并发得不偿失 。
l不适合并行计算和分布并行计算的要求 。 对于多处理器和分布式的计算环境来说,进程之间大量频繁的通信和切换,会大大降低并行度 。
l不适合客户 /服务器计算的要求 。 对于 C/S结构来说,那些需要频繁输入输出并同时大量计算的服务器进程 (如数据库服务器,事务监督程序 )很难体现效率 。
线程的概念
如果说操作系统中引入进程的目的是为了使多个程序并发执行,以改善资源使用率和提高系统效率,那么,在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时空开销,
使得并发粒度更细,并发性更好 。 这里解决问题的基本思路是:把进程的两项任务分离开来,
单独处理,并由进程和线程分别完成 。 进程作为系统资源分配和保护的独立单位,它不需要频繁地切换 ;线程作为系统调度和分派的基本单位,会被频繁地调度和切换,在这种指导思想下,产生了线程的概念 。
MS-DOS支持单用户进程,进程是单线程的;
传统的 Unix支持多用户进程,每个进程也是单线程的 。
很多著名的操作系统都支持多线程 ( 结构 ) 进程,如,Solaris,Mach,SVR4,OS/390、
OS/2,WindowNT,Chorus等;
JAVA的运行引擎则是单进程多线程的例子 。 许多计算机公司都推出了自己的线程接口规范,
如 Solaris Thread接口规范,OS/2 Thread接口规范,Windows NT Thread接口规范等; IEEE也推出了多线程程序设计标准 POSIX 1003.4a,可以相信多线程技术在程序设计中将会被越来越广泛地采用 。
2.4.2 多线程环境中的进程与线程
2.4.2.1 多线程环境中的进程概念
图 2-28 单线程进程的内存布局和运行进程控制块进 程用户地址空间用户堆栈系统堆栈管理者执行序 列单线程进程 (模型 )
用户地址空间进程控制块用户堆栈系统堆栈图 2-29 管理和执行相分离的进程模型用户堆栈系统堆栈执行控制进 程进程控制块用户地址空间共 享执行序列管理者执行序列用户堆栈系统堆栈执行控制图 2-30 多线程进程的内存布局多线程进程 (模型 )
…
用户地址空间进程控制块线程控制块系统堆栈用户堆栈线程 1
线程控制块系统堆栈用户堆栈线程 N
多线程环境中进程的定义:
进程是操作系统中进行保护和资源分配的独立单位 。 它具有:
l一个虚拟地址空间,用来容纳进程的映像;
l对处理器,其他 (通信的 )进程,文件和
I/O资源等的存取保护机制 。
2.4.2.2 多线程环境中的线程概念线程则是指进程中的一条独立执行路径 ( 控制流 ),每个进程内允许包含多个并行执行的路径 ( 控制流 ),这就是多线程 。 线程是系统进行处理器调度的基本单位,同一个进程中的所有线程共享进程获得的主存空间和资源,但不拥有资源 。 线程具有:
l一个线程执行状态 ( 运行,就绪,… ) ;
l当线程不运行时,有一个受保护的线程上下文,
用于存储现场信息 。 所以,线程也可被看作是执行在进程内的一个独立的程序计数器 ;
l一个执行堆栈
l一个容纳局部变量的主存存储区 。
线程 具有以下特性:
l并行性:同一进程的多个线程可在一个 /多个处理器上并发或并行地运行 。
l共享性:同一个进程中的所有线程共享进程获得的主存空间和一切资源,因而,线程需要同步和通信机制 。
l动态性:线程也是程序在相应数据集上的一次执行,由创建而产生,至撤销而消亡,有其生命周期 。
进程支撑线程运行,为线程提供地址空间和各种资源,它封装了管理信息,包括对指令代码,全局数据和 I/O状态数据等共享部分的管理 。 线程封装了执行信息,具有并发性,包括对 CPU寄存器,执行栈
( 用户栈,内核栈 ) 和局部变量,过程调用参数,返回值等线程私有部分的管理 。
图 2-31 线程的内存布局进 程地址空间线程
1
共 享线 程空 间线程
2
线程
n
线程又称轻量进程 (Light weight Process)
因为,它运行在进程的上下文中,并使用进程的资源和环境 。 注意,系统调度的基本单位是线程而不是进程,
所以,每当创建一个进程时,至少要同时为该进程创建一个线程,否则该进程无法被调度执行 。
2.4.2.3 线程的状态
线程的关键状态有:运行,就绪和阻塞 。 另外,
线程的状态转换也类似于进程 。 挂起状态对线程是没有意义的,如果一个进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去 。
当处于运行态的线程阻塞时,对于某些线程实现机制,所在进程也转换为阻塞态,即使这个进程存在另外一个处于就绪态的线程;对于另一些线程实现机制,如果存在另外一个处于就绪态的线程,则调度该线程处于运行状态,否则进程才转换为阻塞态 。 显然前一种做法欠妥,
丢失了多线程机制的优越性,降低了系统的灵活性 。
2.4.2.4 线程管理和线程库
基本的线程控制原语有:
l孵化 ( Spawn),又称创建线程 。 当一个新进程被生成后,该进程的一个线程也就被创建 。 此后,该进程中的一个线程可以孵化同一进程中的其它线程,并为新线程提供指令计数器和变量 。 一个新线程还将被分配寄存器上下文和堆栈空间,并将其加入就绪队列 。
l封锁 ( Block),又称线程阻塞或等待 。 当一个线程等待一个事件时,将变成阻塞态,保护它的用户寄存器,程序计数器和堆栈指针等现场 。 处理器现在就可以转向执行其它就绪线程 。
l活化 ( Unblock),又称恢复线程 。 当被阻塞线程等待的事件发生时,线程变成就绪态或相应状态 。
l结束 ( Finish),又称撤销线程 。 当一个线程正常完成时,
便回收它占有的寄存器和堆栈等资源,撤销线程 TCB。 当一个线程运行出现异常时,允许强行撤销一个线程 。
线程库
很多基于多线程的操作系统和语言都提供了线程库,如 Mach的 C-threads和 Java线程库,供应用程序共享,支持应用程序创建,调度,和管理用户级线程的运行 。 线程库实质上是多线程应用程序的开发和运行支撑环境 。 一般地说,
线程库至少应提供以下功能的过程调用:孵化,
封锁,活化,结束,通信,同步,调度等 。 每个线程库应提供给用户级的 API编程使用 。
2.4.2.5 并发多线程程序设计的优点
l快速线程切换 。 进程具有独立的虚地址空间,以进程为单位进行任务调度,系统必须交换地址空间,切换时间长,而在同一进程中的多线程共享同一地址空间,因而,能使线程快速切换 。
l减少 ( 系统 ) 管理开销 。 对多个进程的管理 ( 创建,调度,终止等 ) 系统开销大,
如响应客户请求建立一个新的服务进程的服务器应用中,创建的开销比较显著 。 面对创建,终止线程,虽也有系统开销,但要比进程小得多 。
并发多线程程序设计的优点
l( 线程 ) 通信易于实现 。 为了实现协作,进程或线程间需要交换数据 。 对于自动共享同一地址空间的各线程来说,所有全局数据均可自由访问,不需什么特殊设施就能实现数据共享 。
而进程通信则相当复杂,必须借助诸如通信机制,消息缓冲,管道机制等设施,而且还要调用内核功能,才能实现 。
l并行程度提高 。 许多多任务操作系统限制用户能拥有的最多进程数目,如 Unix一般为 50个,
这对许多并发应用来说是不够的 。 而对多线程技术来说,一般可达几千个,基本上不存在线程数目的限制 。
并发多线程程序设计的优点
l( 线程 ) 通信易于实现 。 为了实现协作,进程或线程间需要交换数据 。 对于自动共享同一地址空间的各线程来说,所有全局数据均可自由访问,不需什么特殊设施就能实现数据共享 。
而进程通信则相当复杂,必须借助诸如通信机制,消息缓冲,管道机制等设施,而且还要调用内核功能,才能实现 。
l并行程度提高 。 许多多任务操作系统限制用户能拥有的最多进程数目,如 Unix一般为 50个,
这对许多并发应用来说是不够的 。 而对多线程技术来说,一般可达几千个,基本上不存在线程数目的限制 。
并发多线程程序设计的优点
l 节省内存空间 。 多线程合用进程地址空间,而不同进程独占地址空间,使用不经济 。
由于队列管理和处理器调度是以线程为单位的,
因此,多数涉及执行的状态信息被维护在线程组数据结构中 。 然而,存在一些影响到一个进程中的所有线程的活动,操作系统必须在进程级进行管理 。 挂起 ( Suspension) 意味着将主存中的地址空间对换到盘上,因为,在一个进程中的所有线程共享同一地址空间,此时,所有线程也必须进入挂起状态 。 相似地,终止一个进程时,所有线程应被终止 。
2.4.2.6 多线程技术的应用
l前台和后台工作 。 如在一个电子表格软件中,一个线程执行显示菜单和读入用户输入,同时,另一个线程执行用户命令和修改电子表格 。
lC/S应用模式 。 局域网文件 ( 网络 ) 服务器处理多个用户文件 ( 任务 ) 请求时,创建多个线程,若该服务器是多
CPU的,则同一进程中的多线程可同时运行在不同 CPU上 。
l加快执行速度 。 一个多线程进程在计算一批数据的同时,
读入设备 ( 网络,终端,打印机,硬盘 ) 上的下一批数据,
而这分别由两个线程实现 。
l设计用户接口 。 每当用户要求执行一个动作时,就建立一个独立线程来完成这项动作 。 当用户要求有多个动作时,
就由多个线程来实现,窗口系统应有一个线程专门处理鼠标的动作 。 例如,GUI中,后台进行屏幕输出或真正计算;
同时,要求对用户输入 ( 鼠标 ) 作出反映 。 有了多线程,
可用处理 GUI输入线程和后台计算线程,便能实现这一功能 。
2.4.3 线程的实现从实现的角度看,线程可以分成用户级线程 ULT(如,Java和 Informix)
和内核级线程 KLT(如 OS/2)。也有一些系统 (如,Solaris)提供了混合式线程,同时支持两种线程实现。
图 2-32给出了各种线程实现方法用户空间线程库
P
内核空间
2)用户级线程用户空间
P
内核空间
1)内核级线程用户空间线程库
P P
内核空间
3)混合式线程
ULT KLT ProcessP
2.4.3.1内核级线程
在纯内核级线程设施中,线程管理的所有工作由操作系统内核来做 。 内核专门提供了一个 KLT(Kernel Level Threads)应用程序设计接口 ( API),供开发者使用,应用程序区不需要有线程管理的代码 。 Windows NT 和 OS/2都是采用这种方法的例子 。
任何应用都可以被程序设计成多个线程,
当提交给操作系统执行时,内核为它创建一个进程和一个线程,线程在执行中可以通过内核创建线程和原语来创建其他线程,
这个应用的所有线程均在一个进程中获得支持 。 内核要为整个进程及进程中的单个线程维护现场信息,所以,应在内核中建立和维护 PCB及 TCB,内核的调度是在线程的基础上进行的 。
有内核级线程主要优点,
首先,在多个处理器上,内核能够同时调度同一进程中多个线程并行执行;
其次,若进程中的一个线程被阻塞了,内核能调度同一进程的其它线程占有处理器运行 。
最后,由于内核线程仅有很小的数据结构和堆栈,KLT的切换也不需要改变内存信息,切换比较快,内核自身也可以用多线程技术实现,
从而,能提高系统的执行速度和效率 。
KLT的主要缺点是:
应用程序线程在用户态运行,而线程调度和管理在内核实现,在同一进程中,控制权从一个线程传送到另一个线程时需要用户态 -内核态 -用户态的模式切换,
系统开销较大 。
2.4.3.2 用户级线程
纯 ULT(User Level Threads)设施中,线程管理的全部工作都由应用程序来做,内核是不知道线程的存在的 。 用户级多线程由线程库来实现,
任何应用程序均需通过线程库进行程序设计,
再与线程库连接后运行来实现多线程 。 线程库是一个 ULT管理的例行程序包,它包含了建立
/撤销线程的代码,在线程间传送消息和数据的代码,调度线程执行的代码,以及保护和恢复线程上下文的代码,实质上线程库是线程的运行支撑环境 。
线程“孵化”过程
当一个应用程序提交给系统后,系统为它建立一个由内核管理的进程,该应用程序在线程库环境下开始运行时,只有一个由线程库为进程建立的线程 。 首先运行这个线程,当应用进程处于运行状态时,线程通过调用线程库中的,孵化,过程,可以孵化出运行在同一进程中的新线程,步骤如下:通过过程调用把控制权传送给这个,孵化,过程,线程库为新线程创建一个
TCB数据结构,并置为就绪态,由线程库按一定的调度算法把控制权传送给进程中处于就绪态的一个线程 。
当控制权传送到线程库时,当前线程的现场信息被保存,而当控制权由库传送给线程时,便恢复它的现场信息 。 现场信息主要包括:用户寄存器内容,程序指令计数器,堆栈指针 。
线程调度和进程调度之间的关系
假设进程 B正在执行它的线程 3,则可能出现下列情况:
l正在执行的进程 B的线程 3发出了一个封锁 B的系统调用,例如,做了一个 I/O操作,通知内核进行 I/O并将进程 B置为等待状态,按照由线程库所维护的数据结构,
进程 B的线程 3仍然处在运行态 。 十分重要的是线程 3并不实际地在一个处理器上运行,而是可理解为在线程库的运行态中 。 这时,进程 B为等待态,但线程为线程库运行态 。
l一个时钟中断传送控制给内核,内核中止当前时间片用完的进程 B,并把它放入就绪队列,切换到另一个就绪进程,此时,按由线程库维护的数据结构,进程 B的线程 3仍处于运行态 。 这时,进程 B己处于就绪态,但线程为线程库运行态 。
上述两种情况中,当内核切换控制权返回到进程 B时,便恢复执行线程 3。注意到当正在执行线程库中的代码时,一个进程也有可能由于时间片用完或被更高优先级的进程剥夺而被中断。当中断发生时,一个进程可能正处在从一个线程切换到另一个线程的过程中间;当一个进程恢复时,继续在线程库中执行,完成线程切换,并传送控制权给进程中的一个新线程。
ULT优点:
l线程切换不需要内核特权方式,因为,所有线程管理数据结构均在单个进程的用户空间中,管理线程切换的线程库也在用户地址空间运行,因而,进程不要切换到内核方式来做线程管理 。 这就节省了模式切换的开销,也节省了内核的宝贵资源 。
l按应用特定需要来调度,一种应用可能从简单轮转调度算法得益,同时,另一种应用可能从优先级调度算法获得好处 。 在不干扰 OS调度的情况下,根据应用需要可以裁剪调度算法,也就是说,线程库的线程调度算法与操作系统的进程调度算法是无关的 。
lULT能运行在任何 OS上,内核支持 ULT方面不需要做任何改变 。 线程库是可以被所有应用共享的应用级实用程序,许多当代操作系统和语言均提供了线程库,传统 Unix
并不支持多线程,但已有了多个基于 Unix的用户线程库 。
ULT有二个明显的缺点:
l 在传统的基于进程操作系统中,大多数系统调用将阻塞进程,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞 。
l 在纯 ULT中,多线程应用不能利用多重处理的优点 。 内核在一段时间里,分配一个进程仅占用一个 CPU,因而进程中仅有一个线程能执行 。 因此尽管多道程序设计能够明显地能加快应用处理速度,也具备了在一个进程中进行多线程设计的能力,但我们不可能得益于并发地执行一部分代码 。
克服上述问题的方法有两种一是用多进程并发程序设计来代替多线程并发程序设计,这种方法事实上放弃了多线程带来的所有优点 。 第二种方法是采用 jacketing技术来解决阻塞线程的问题 。 主要思想是把阻塞式的系统调用改造成非阻塞式的,当线程调用系统调用,
首先调用 jacketing实用例程,来检查资源使用情况,以决定是否执行系统调用或传递控制权给另一个线程 。
2.4.3.3 混合式线程
在混合系统中,内核支持 KLT多线程的建立,调度和管理,同时,也提供线程库,允许用户应用程序建立,调度和管理 ULT。
一个应用程序的多个 ULT映射成一些 KLT,
程序员可按应用需要和机器配置调整 KLT
数目,以达到较好效果 。
混合式中,一个应用中的多个线程能同时在多处理器上并行运行,且阻塞一个线程时并不需要封锁整个进程 。 如果设计得当的话,则混合式多线程机制能够结合了两者优点,并舍去它们的缺点 。
2.4.4 实例研究,Solaris的进程与线程
2.1.1.1 Solaris中的进程与线程概念
Solaris采用了 4个与进程和线程有关的概念,用来支持完全可抢占,SMP,和核心多线程结构:
l 进程 ( Process),通常的 Unix进程,它包含用户的地址空间和 PCB。
l 用户级线程 ( User-Level Threads),通过线程库在用户地址空间中实现,对操作系统来讲是不可见的,用户级线程 ( ULT) 是应用程序并行机制的接口 。
l轻量进程 ( Light Weight Process),一个 LWP可看作是
ULT和 KLT之间的映射,每个 LWP支持一个或多个 ULT并映射到一个 KLT上 。 LWP是核心独立调度的单位,它可以在多个处理器上并行执行 。
l内核级线程 ( Kernel-Level Threads),即 KLT,它们是能被调度和指派到处理器上去运行的基本实体 。
Solaris的线程实现分为二个层次:
用户层和核心层,用户层在用户线程库中实现;
核心层在操作系统内核中实现 。 处于两个层次中的线程分别叫用户级线程和内核级线程 。
ULT是一个代表对应线程的数据结构,它是纯用户级的概念,占用用户空间资源,对核心是透明的 。 ULT和 KLT不是一一对应,通过轻量级进程 LWP来映射两者之间的联系 。 一个进程可以设计为一个或多个 LWP,在一个 LWP上又可以开发多个 ULT,LWP与 ULT一样共享进程的资源 。 KLT和 LWP是一一对应的,一个 ULT要通过核心 KLT和 LWP二级调度后才真正占有处理器运行 。
有了 LWP,就可以在用户级实现 ULT,每个进程可以创建几十个 ULT,而又不占用核心资源 。 由于 ULT共享用户空间,因此当 LWP在一个进程的不同 ULT间切换时,
仅是数据结构的切换,其时间开销远低于两个 KLT间的切换时间 。 同样线程间的同步也独立于核心的同步体系,在用户空间中独立实现,而不陷入内核 。 核心能看见的是 LWP,而 ULT是透明的 。
多线程的程序员接口包括二个层次:
一层为线程接口,提供给用户,以编写多线程应用程序,这一层由动态连接库引用 LWP实现,线程库在 LWP之上调度线程,同步也在线程库实现 。 第二层为 LWP接口,提供给线程库,以管理 LWP。 这一层由核心通过 KLT实现 。 LWP通过这些接口访问核心资源 。
多线程的两个程序员接口是类似的,线程接口的切换代价较小,特别适用于解决逻辑并行性问题;而
LWP接口则适用于那些需要在多个处理器进行并行计算的问题。如果程序设计得当的话,混合应用两种程序员接口可以带来更大的灵活性和优越性。
图 2-33 Solaris线程的使用硬件线程库
L PULT KLT LWP Processor
L L L LL L L L L
PPPPP
用户内核进程 1 进程 2 进程 3 进程 4 进程 5
图中,进程 1是传统的单线程进程,当应用不需要并发运行时,可以采用这种结构 。
进程 2是一个纯的 ULT应用,所有的 ULT由单个内核线程支撑,因而,某一时刻仅有一个线程可运行 。 对于那些在程序设计时要表示并发而不真正需要多线程并行执行的应用来说,这种方式是有用的 。
进程 3是多线程与多 LWP的对应,Solaris允许应用程序开发多个 ULT工作在小于或等于 ULT个数的 LWP上 。
这样应用可以定义进程在内核级上的某种程度的并行 。
进程 4的线程与 LWP是一对一地捆扎起来的,这种结构使得内核并行机制完全对应用可见,这在线程常常因阻塞而被挂起时是有用的 。
进程 5包括多 ULT映射到多 LWP上,以及 ULT与 LWP
的一一捆绑,并且还有一个 LWP捆在单个处理器上 。
2.4.4.2 Solaris的进程结构
在 Solaris中已经有了进程,KLT和 ULT,为什么还要引入 LWP?我们先来讨论这个问题 。 最主要的原因是各种应用的需求,有些应用逻辑并行性程度高,有些应用物理并行性要求高 。 像窗口系统是典型的逻辑并行性程度高的应用,同时在屏幕上开出了多个窗口,窗口切换很频繁,但一个时刻仅有少数窗口处于活跃状态 。 我们可以用一组 ULT来表达窗口系统,用一个或很少几个 LWP
来支持这一组 ULT,可根据活跃窗口数目调正
LWP的个数 。 由于 ULT是由用户线程库实现的,
他们的管理不涉及内核 ;内核仅要管理与 ULT对应的一个或几个 LWP,系统开销小,窗口系统的效率高 。
大规模并行计算是物理并行性要求高的应用,可以把数组按列划分给不同的
ULT线程处理,如果每个 CPU对应一个
LWP,而一个 LWP对应多个 ULT,那么,
CPU有很多时间化在线程切换上 。 这种情况下,最好把列分给少量 ULT,而一个 ULT和一个 LWP绑定,以减少线程切换次数,提高并行计算的效率 。
Solaris中进程 (包括不同数目的轻量进程 ),进程结构如图 2-34
一内存分配表进程标识符用户标识符信号分配表文件描述符轻进程标识符优先数信号掩码寄存器堆栈
……
轻进程标识符优先数信号掩码寄存器堆栈
……
轻量进程 1 轻量进程 2
LWP的数据结构包括:
l轻量进程标识符标识了轻量进程
l 优先数定义了轻量进程执行的优先数
l 信号掩码定义了内核能够接受的信号
l寄存器域用于存放轻量进程让出处理器时的现场信息
l 轻量进程的内核栈包括每个调用层次的系统调用的参数,
返回值和出错码
l 交替的信号堆栈
l 用户,或用户和系统共同的虚时间警示
l 用户时间和系统处理器使用
l 资源使用和预定义数据
l 指向对应内核线程的指针
l指向进程结构的指针
轻量进程实际上不是进程,只是一个内核支持的 ULT,因此它有一个内核堆栈,都有一个对应的 KLT支持 。 LWT被独立调度,
进程中的 LWP共享进程地址空间和资源,
一个进程由于有多个 LWP,才有可能分得多个 CPU,才能实现物理上的并行性 。
ULT的数据结构包括:
l线程标识符标识了线程
l优先数定义了线程执行的优先数
l 信号掩码定义了能够接受的信号
l 寄存器域用于存放线程让出处理器时的现场信息
l 堆栈用于存放线程运行数据
l 线程局部存储器用于存放线程局部数据
KLT的数据结构 (每个 LWP对应一个 ) 是由内核创建的线程,每个 KLT有一个堆栈和一个数据结构,包括:
l 内核寄存器数据保存区
l 优先级和调度信息
l KLT的队列指针和堆栈指针
l 相关 LWP的指针及信息
l 进程数据结构,包括:
l 与进程相关的 KLT
l 进程地址空间指针
l 用户权限表
l 信号处理程序清单
l 与用户执行有关信息
Solaris支持实时类进程,其内核是可抢占的,它的内核函数和过程全部用线程来实现,所以,Solaris的内核是 KLT的集合 。 这些 KLT分两种,一种是负责执行一个指定的内核函数 ;另一种用来支持和运行 LWP,KLT是独立被调度和分配到 CPU上去执行 。
2.4.4.3 Solaris的线程状态继续 剥夺停止 睡眠
Sleeping
睡眠态
Runnable
可运行态
Active
活跃态
Stopped
停止态停止 唤醒指派停止指派唤醒 停止
Stopped
停止态
Running
运行态
Blocked
阻塞态
Runnable
可运行态时间片到或剥夺 唤醒停止阻塞系统调用继续
LWP
ULT
用户级线程的执行是由线程库管理的,让我们首先考虑非捆绑的线程,即多个 ULT可共享 LWP,线程可能处于四个状态之一:可运行,活跃,睡眠和停止 。 处在活跃状态的一个 ULT是指目前分配到 LWP上,并且在对应的内核线程 KLT执行时,它被执行 。 出现许多事件会导致 ULT离开活跃态,让我们考虑一个活跃的 ULT正在执行,下面的事件可能发生:
l同步:有一个 ULT调用了一条同步原语与其它线程协调活动 。 于是它就进入睡眠态,直到同步条件满足后,这个
ULT才被放入可运行队列 。
l挂起:任何线程可挂起它线程 ;一个线程也可挂起自己并进入停止状态 。 直到别的线程发出一个让挂起线程继续运行的请求,再把该线程移入可运行队列 。
l剥夺:一个活跃线程在执行时,另一个更高优先级的线程变为可运行状态,如果处于活跃状态的运行线程优先级较低,它就要被剥夺并放入可运行状态,而更高优先级的线程被分配到可用的 LWP上执行 。
l让位:若运行线程执行了 thread_yield( ) 库命令,库中的线程调度程序将查看是否有另一个可运行线程,它与当前执行的线程具有相同的优先级,则把执行线程放入可运行队列,另一个可运行线程分配到可用 LWP上 ;否则原线程继续运行 。
图 2-35还显示了 LWP的状态转换图我们可以把它看作是 ULT活跃状态的详尽描述,因为当一个 ULT处于活跃状态时只能捆绑到一个 LWP上 。 只有当 LWP处于运行状态时,一个处于活跃状态的 ULT才真正的处于活跃状态并执行 。 当一个处于活跃状态的 ULT调用一个阻塞的系统调用时,
则它对应的 LWP进入阻塞状态,这个 ULT
将继续处于活跃状态并继续与相应的 LWP
绑定,直到线程库显式地做出变动 。
Solaris提供了以下线程操作供用户线程程序设计。
thread_create()创建一个新的线程
thread_setconcurrency()置并发程度 ( 即 LWP的数目 )
thread _exit()终止当前进程,收回线程库分配给它的资源
thread_wait()阻塞当前线程,直到有关线程退出
thread_get_id()获得线程标识符
thread_sigsetmask(),thread_sigprocmask()置线程信号掩码
Thread_kill()生成一个送给指定线程的信号
Thread_stop()停止一个线程的执行
Thread_priority()置一个线程的优先数
2.4.5 实例研究,Windows 2000的进程与线程
2.4.5.1 Windows 2000中的进程与线程概念
Windows2000包括三个层次的执行对象:进程,
线程和作业 。 其中
作业是 Windows2000是共享一组配额限制和安全性限制的进程的集合 ;
进程是相应于一个应用的实体,它拥有自己的资源,如主存,打开的文件;
线程是顺序执行的工作调度单位,它可以被中断,使 CPU能转向另一线程执行 。
Windows 2000进程设计目标是提供对不同操作系统环境的支持,具有:多任务
(多进程 ),多线程,支持 SMP,采用了 C/S模型,能在任何可用 CPU上运行的特点 。 由内核提供的进程结构和服务相对来说简单,适用,
其重要的特性如下:
l作业,进程和线程是用对象来实现的 。
l 一个可执行的进程可以包含一个或多个线程 。
l进程或线程两者均有内在的同步设施 。
Windows 2000核心态结构:
核心态组件运行在核心地址空间中,同括以下几个部分:
1 执行体
2 核心
3 设备驱动程序
4 图形引擎
5 硬件抽象层
Windows采用 OO技术实现系统
对象,
对象类型,
属性和方法,
封装,继承,
消息
Windows 2000对象分类:
1 内核对象
2 执行体对象
执行体有一个对象管理器对象结构:
对象头
对象体
Windows 2000中进程作为对象来管理
进程对象,它具有属性和共性方法
进程结构图 2-36 进程以及控制和使用的资源
Object Table
Virtual Address Space Description
Thread x
File y
Section z
…
Process
Access
Token
Available Objects
Handle 1
Handle 2
Handle 3
当一个用户首次注册时,Windows2000为用户建立一个访问令牌 Access Token,它包括安全标识和进程凭证 。 这个用户建立的每一个进程均有这个访问令牌的拷贝 。 内核使用访问令牌来验证用户是否具有存取安全对象或在系统上和安全对象上执行受限功能的能力 。 访问令牌还控制了进程能否改变自己的属性,在这种情况下,进程没有获得它的访问令牌的句柄,进程若想打开它,安全系统首先决定这是否允许,进而决定进程能否改变自己的属性 。
与进程有关的还有一组当前分配给这个进程的虚拟地址空间块,进程不能直接改它,必须依靠为进程提供内存分配服务的虚存管理例程 。
进程包括一个对象表,其中包括了这个进程与其它使用资源之间的联系,通过对象句柄便可对某资源进行引用,存取令牌控制进程是否能改变其自身属性。图 2-
35包含了一个线程对象,这个线程可以访问一个文件对象和一个共享内存区对象,有一系列分给进程的虚拟地址空间块。
在 Window2000中,对象 (object)是对象类的实例,对象类是实现系统功能的操作和私有变量的封装体;句柄则是对打开了的对象实例 的引用。
2.4.5.2 进程对象
进程是由一个通用结构的对象来表示的 。 每个进程由属性和封装了的若干可以执行的动作和服务所定义 。 当接受到适当消息时,进程就执行一个服务,只能通过传递消息给提供服务的进程对象来调用这一服务 。 用户使用进程对象类 ( 或类型 ) 来建立一个新进程,对进程来说,
这个对象类被定义作为一个能够生成新的对象实例的模板,并且在建立对象实例时,属性将被赋值 。
每一个进程都由一个执行体进程 ( EPROCESS)
块表示 。 下面给出了 EPROCESS的结构,它不仅包括进程的许多属性,还包括并指向许多其它相关的属性,如每个进程都有一个或多个执行体线程 ( ETHREAD) 块表示的线程 。 除了进程环境块 ( PEB) 存在于进程地址空间中以外,EPROCESS块及其相关的其它数据结构存在于系统空间中 。 另外,WIN32子系统进程
CSRSS为执行 WIN32程序的进程保持一个平行的结构,该结构存在于 WIN32子系统的核心态部分 WIN32K.SYS,在线程第一次调用在核心态实现的 WIN32 USER或 GDI函数时被创建 。
内核进程块进程标识符父进程标识符退出状态创建和退出次数指向下一个进程的指引元配额块内存管理信息异常端口调试程序端口主访问令牌指引元局柄表指引元进程环境块映像文件名映像基址进程特权级
WIN32进程块指引元
EPROCESS块中的有关项目的内容如下:
l内核进程块 KRPROCESS:公共调度程序对象头,指向进程页面目录的指针,线程调度默认的基本优先级,时间片,相似性掩码,用于进程中线程的总内核和用户时间 。
l进程标识符等:操作系统中唯一的进程标识符,父进程标识符,运行映像的名称,进程正在运行的窗口位置 。
l配额块:限制非页交换区,页交换区和页面文件的使用,进程能使用的 CPU时间 。
l虚拟地址空间描述符 VAD:一系列的数据结构,描述了存在于进程地址空间的状态 。
l工作集信息:指向工作集列表的指针,当前的,峰值的,最小的和最大的工作集大小,上次裁剪时间,页错误计数,内存优先级,交换出标志,页错误历史纪录 。
l虚拟内存信息:当前和峰值虚拟值,页面文件的使用,用于进程页面目录的硬件页表入口 。
l异常 /调试端口:当进程的一个线程发生异常或引起调试事件时,进程管理程序发送消息的进程间通信通道 。
l访问令牌:指明谁建立的对象,谁能存取对象,谁被拒绝存取该对象 。
l句柄表:整个进程的句柄表地址 。
l 进程环境块 PEB:映像基址,模块列表,线程本地存储数据,代码页数据,临界区域超时,
堆栈的数量,大小,进程堆栈指针,GDI共享的句柄表,操作系统版本号信息,映像版本号信息,映像进程相似性掩码 。
lWIN32子系统进程块,WIN32子系统的核心组件需要的进程细节 。
操作系统还提供了一组用于进程的 WIN32函数:
l CreateProcess:使用调用程序的安全标识,创建新的进程和线程 。
l CreateProcessAsUser:使用交替的安全标识,
创建新的进程和线程,然后执行指定的 EXE。
lOpenProcess:返回指定进程对象的句柄 。
l ExitProcess:退出当前进程 。
lTerminateProcess:终止进程 。
l FlushInstructionCache:清空另一个进程的指令高速缓存 。
lGetProcessTimes:得到另一个进程的时间信息,描述进程在用户态和核心态所用的时间 。
lGetExitCodeProcess:返回另一个进程的退出代码,指出关闭这个进程的方法和原因 。
lGetCommandLine:返回传递给进程的命令行字符串 。
lGetCurrentProcessID:返回当前进程的 ID。
lGetProcessVersion:返回指定进程希望运行的 Windows
的主要和次要版本信息 。
lGetStartupInfo,返回在 CreateProcess 时 指 定 的
STARTUPINFO结构的内容 。
lGetEnvironmentStrings:返回环境块的地址 。
lGetEnvironmentVariable:返回一个指定的环境变量 。
lGetProcessShutdownParameters:取当前进程的关闭优先级和重试次数 。
lSetProcessShutdownParameters,置当前进程的关闭优先级和重试次数 。
调用 CreateProcess函数创建一个 WIN32进程。
创建的 WIN32进程的过程在操作系统的 3个部分中分阶段完成,这 三 个 部 分 是,WIN32 客户方的
KERNEL32.DLL,Windows2000执行体和 WIN32子系统进程 CSRSS。 具体步骤如下:
l打开将在进程中被执行的映像文件 (,EXE) 。
l创建 Windows2000执行体进程对象 。
l创建初始线程 ( 堆栈,描述表,执行体线程对象 ) 。
l通知 WIN32子系统已经创建了一个新的进程,以便它可以设置新的进程和线程 。
l 启 动 初 始 线 程 的 执 行 ( 除 非 指 定 了
CREATE_SUSPENDED标志 ) 。
l在新进程和线程的描述表中,完成地址空间的初始化,
加载所需的 DLL,并开始程序的执行
2.4.5.3 线程对象
ETHRED块中的有关项目的内容如下:
l创建和退出时间:线程的创建和退出时间 。
l进程识别信息:进程标识符和指向 EPROCESS的指引元 。
l线程启动地址:线程启动例程的地址 。
lLPC消息信息:线程正在等待的消息 ID和消息地址 。
l挂起的 I/O请求:挂起的 I/O请求数据包列表 。
l调度程序头信息:指向标准的内核调度程序对象 。
l执行时间:在用户态运行的时间总计和在核心态运行的时间总计 。
l内核堆栈信息指引元:内核堆栈的栈底和栈顶信息 。
l系 统 服 务 表 指 引 元,指 向 系 统 服 务 表 的 指 针 。
l调度信息:基本的和当前的优先级,时间片,相似性掩码,首选处理器,调度状态,冻结计数,挂起计数 。
图 2-37 ETHRED块的结构内核线程块创建和退出时间进程标识符指向 EPROCESS的指引元线程启动地址主访问令牌指引元模拟信息
LPC消息信息定时器信息挂起的 I/O请求调度程序头信息用户态时间总计核心态时间总计内核堆栈信息指引元系统服务表指引元线程调度信息陷阱帧线程本地存储数组同步信息搁置的 APC列表定时器块和等待块线程正在等待的对象列表线程环境块指引元操作系统提供一组用于线程的 WIN32函数:
lCreateThread:创建新线程 。
lCreateRemoteThread:在另一个进程创建线程 。
lExitThread:退出当前线程 。
lTerminateThread:终止线程 。
lGetExitCodeThread:返回另一个线程的退出代码 。
lGetThreadTimes:返回另一个线程的定时信息 。
lGetThreadSelectorEntry:返回另一个线程的描述符表入口 。
lGetThreadContext:返回线程的 CPU寄存器 。
lSetThreadContext:更改线程的 CPU寄存器 。
调用 CreateThread函数创建一个 WIN32线程的具体步骤如下:
l在进程地址空间为线程创建用户态堆栈 。
l初始化线程的硬件描述表 。
l 调用 NtCreateThread创建处于挂起状态的执行体线程对象 。 包括:增加进程对象中的线程计数,创建并初始化执行体线程块,为新线程生成线程 ID,从非页交换区分配线程的内核堆栈,设置 TEB,设置线程起始地址和用户指定的 WIN32 起始地址,调用 KeInitializeThread 设置
KTHREAD块,调用任何在系统范围内注册的线程创建注册例程,把线程访问令牌设置为进程访问令牌并检查调用程序是否有权创建线程 。
l通知 WIN32子系统已经创建了一个新的线程,以便它可以设置新的进程和线程 。
l线程句柄和 ID被返回到调用程序 。
l 除非调用程序用 CREATE_SUSPEND标识设置创建线程,
否则线程将被恢复以便调度执行 。
线程是 Windows2000操作系统的最终调度实体,
如图 2-38所示,它可能处于以下 6个状态之一:
l 就绪态 ——可被调度去执行的状态,微内核的调度程序维护所有就绪线程队列,并按优先级次序调度 。
l准备态 ——已被选中下一个在一个特定处理器上运行 。
此线程处于该状态等待直到该处理器可用 。 如果准备态线程的优先级足够高,则可以从正在运行的线程手中抢占处理器,否则将等待直到运行线程等待或时间片用完 。
l运行态 ——每当微内核执行进程或线程切换时,准备态线程进入运行态,并开始执行,直到它被剥夺,或用完时间片,或阻塞,或终止为止 。 在前两种情况下,线程进入到就绪态 。
l等待态 ——线程进入等待态是由于以下原因:
1) 出现了一个阻塞事件 ( 如,I/O) ; 2) 等待同步信号; 3) 环境子系统要求线程挂起自己 。
当等待条件满足时,且所有资源可用,线程就进入就绪态态 。
l过渡态 ——一个线程完成等待后准备运行,但这时资源不可用,就进入过渡态,例如,线程的堆栈被调出内存 。 当资源可用时,过渡态线程进入就绪态 。
l终止态 ——线程可被自己、其它线程、或父进程终止。一旦结束工作完成后,线程就可从系统中移去,或者保留下来以备将来初始化再用。
图 2-38 Windows2000线程状态资源可用事件完成但资源不可用事件完成资源可用阻塞挂起终止可运行
Running
运行态不可运行
Standby
准备态
Waiting
等待态
Ready
就绪态
Terminated
中止态
Transition
过渡态选中切换抢占或时间片到
2.4.5.4 作业对象
作业对象是一个可命名的,保护,共享的对象,它能够控制与作业有关的进程属性 。 作业对象的基本功能是允许系统将进程组看作是一个单元,对其进行管理和操作 。 有些时候,作业对象可以补偿 NT4在结构化进程树方面的缺陷 。 作业对象也为所有与作业有关的进程和所有与作业有关但已被终止的进程纪录基本的账号信息 。
作业对象包含一些对每一个与该作业有关的进程的强制限制
用户也能够在作业中的进程上设置安全性限制
用户也能够在作业中的进程上设置用户接口限制
一个进程只能属于一个作业,一旦进程建立,它与作业的联系便不能中断;所有由进程创建的进程和它们的后代也和同样的作业相联系 。 在作业对象上的操作会影响与作业对象相联系的所有进程 。
有关作业对象的 WIN32函数包括:
lCreateJobObject:创建作业对象 。
lOpen JobObject:通过名称打开现有的作业对象 。
lAssignProcessToJobObject:添加一个进程到作业 。
lTerminateJobObject:终止作业中的所有进程 。
lSetInformationToJobObject:设置限制 。
lQueryInformationToJobObject:获取有关作业的信息,
如,CPU时间,页错误技术,进程的数目,进程 ID列表,
配额或限制,安全限制等 。
2.5 处理机调度
2.5.1 处理机调度的层次
l 高级调度 (High Level Scheduling):又称作业调度,长程调度 (Long-term Scheduling)。 它将按照系统预定的调度策略决定把后备队列作业中的哪些作业调入主存,为它们创建进程并启动它们运行 。 在批处理操作系统中,作业首先进入系统在辅存上的后备作业队列等候调度,
因此,作业调度是必须的 。 在纯粹的分时或实时操作系统中,通常不需要配备作业调度 。
l中级调度 (Medium Level Scheduling):又称平衡负载调度,中程调度 (Medium-term
Scheduling)。 它决定主存储器中所能容纳的进程数,这些进程将允许参与竞争处理器资源 。 而有些暂时不能运行的进程被调出主存,这时这个进程处于挂起状态,当进程具备了运行条件,
且主存又有空闲区域时,再由中级调度决定把一部分这样的进程重新调回主存工作 。 中级调度根据存储资源量和进程的当前状态来决定辅存和主存中的进程的对换 。
l 低级调度 (Low Level Scheduling):又称进程调度,短程调度 (Short_term Scheduling)。 它的主要功能是按照某种原则决定就绪队列中的哪个进程或内核级线程能获得处理器,并将处理机出让给它进行工作 。
图 2-39 调度的层次中级调度新建态挂起就绪态挂起等待态高级调度低级调度运行态就绪态等待态终止态图 2-40 处理器调度与进程状态转换高级调度中级调度中级调度低级调度运行态就绪态终止态新建态挂起就绪态中级调度挂起等待态等待态高级调度高级调度
2.5.2 高级调度
对于分时操作系统来说,高级调度决定:
1)是否接受一个终端用户的连接;
2)一个程序能否被计算机系统接纳并构成进程;
3)一个新建态的进程是否能够加入就绪进程队列。有的分时操作系统虽没有配置高级调度程序,
但上述的 中级调度
在多道批处理操作系统中,作业是用户要求计算机系统完成的一项相对独立的工作 。 高级调度的功能是按照某种原则从后备作业队列中选取作业进入主存,
并为作业做好运行前的准备工作和作业完成后的善后工作 。 当然一个程序能否被计算机系统接纳并构成可运行进程也作高级调度的一项任务 。 图 2-41给出了批处理操作系统的调度模型 。
中级调度处理器低级调度高级调度完成超时挂起就绪队列挂起等待队列等待队列就绪队列等待事件交互式用户事件出现后备作业队列中级调度
2.5.3 中级调度
很多操作系统为了提高内存利用率和作业吞吐量,专门引进了中级调度 。 中级调度决定那些进程被允许参与竞争处理器资源,
起到短期调整系统负荷的作用 。 它所使用的方法是通过把一些进程换出主存,从而使之进入,挂起,状态,不参与进程调度,
起到滑系统的作用 。 有关中级调度的详细内容参见第三章中有关,进程挂起,小节 。
2.5.4 低级调度
它的主要功能是按照某种原则把处理器分配给就绪进程或内核级线程 。 进程调度程序是操作系统最为核心的部分,进程调度策略的优劣直接影响到整个系统的性能 。
2.5.5 选择调度算法的原则
调度算法所要达到的目标
l资源利用率 ——使得 CPU或其他资源的使用率尽可能高且能够并行工作 。
l响应时间 ——使交互式用户的响应时间尽可能短,或尽快处理实时任务 。
l周转时间 ——批处理用户从作业提交给系统开始到作业完成获得结果为止这段时间间隔称作业周转时间,应该使作业周转时间或作业平均周转时间尽可能短 。
l吞吐量 ——使得单位时间处理的作业数尽可能多 。
l公平性 ——确保每个用户每个进程获得合理的 CPU份额或其他资源份额 。
作业周转时间
如果作业 i提交给系统的时刻是 ts,完成时刻是 tf,那么该作业的周转时间 ti为:
ti = tf - ts
实际上,它是作业在系统里的等待时间与运行时间之和 。
平均作业周转时间
从操作系统来说,为了提高系统的性能,
要让若干个用户的平均作业周转时间和平均带权周转时间最小 。
平均作业周转时间 T = (Σ ti) / n
作业带权周转时间 和 平均作业带权周转时间
如果作业 i的周转时间为 ti,所需运行时间为 tk,则称 wi=ti /tk为该作业的带权周转时间 。 因为,ti是等待时间与运行时间之和,故带权周转时间总大于 1。
平均作业带权周转时间 W = (Σ wi) / n
用平均作业周转时间来衡量对同一作业流施行不同作业调度算法时,它们呈现的调度性能;用平均作业带权周转时间来衡量对不同作业流施行同一作业调度算法时,
它们呈现的调度性能 。 这两个数值均越小越好 。
2.6 批处理作业的管理与调度
2.6.1 作业和进程的关系
l作业 (JOB)是用户提交给操作系统计算的一个独立任务 。
l一般每个作业必须经过若干个相对独立又相互关连的顺序加工步骤才能得到结果,其中,每一个加工步骤称一个作业步 (Job Step),往往上一个作业步的输出是下一个作业步的输入 。
l作业由用户组织,作业步由用户指定,一个作业从提交给系统,直到运行结束获得结果,要经过提交,收容,执行和完成四个阶段 。 当收容态作业被作业调度程序选中进入主存并投入运行时,操作系统将为此用户作业生成相应用户根进程或第一个作业步进程 。 进程是对系统中已提交完毕的任务 (程序 )的执行过程,它在,运行,” 就绪,” 等待,等多个状态的交替之中,在 CPU上推进,最终完成一个程序的任务 。 用户根进程或作业步进程在执行过程中可生成作业步子进程,当然子进程还可以生成其它的子进程,这些进程并发执行,高效地协作完成用户作业的任务 。 在多道程序设计环境中,由于多个作业可同时被投入运行,因此,并发系统中,某一时刻并发执行的进程是相当多的 (如 Unix中有几十个 ),操作系统要负责众多进程的协调和管理 。 后面将要引进线程概念,一个进程中还可以孵化出多个线程,由线程并发执行来完成作业任务 。
可以看出作业和进程之间的主要关系:
作业是任务实体,进程是完成任务的执行实体 ;没有作业任务,进程无事可干,
没有进程,作业任务没法完成 。 作业概念更多地用在批处理操作系统,而进程则可以用在各种多道程序设计系统 。
2.6.2 批处理作业的管理
多道批处理操作系统采用脱机控制方式,
它提供一个作业控制语言,用户使用作业控制语言书写作业说明书,它是按规定格式书写的一个文件,把用户对系统的各种请求和对作业的控制要求集中描述,
并与程序和数据一起提交给系统 (管理员 )。 计算机系统成批接受用户作业输入,
把它们放到输入井,然后在操作系统的管理和控制下执行 。
作业控制块
多道批处理操作系统具有独立的作业管理模块,
为了有效管理作业,必须像进程管理一样为每一个作业建立作业控制块 ( JCB) 。 JCB通常是在批作业进入系统时,由 Spooling系统建立的,
它是作业存在于系统的标志,作业撤离时,JCB
也被撤销 。 JCB的主要内容从用户作业说明书中获得,包括:作业情况 (用户名,作业名,语言名等 ),资源需求 (估计 CPU运行时间,最迟截止期,
主存量,设备类型 /台数,文件数和数据量,函数库 /实用程序等 ),资源使用情况 (进入系统时间,
开始运行时间,己运行时间等 ),作业控制 (优先数,控制方式,操作顺序,出错处理等 )。
作业在它的整个生命周期中将顺序地处于以下四个状态:
l 输入状态:此时作业的信息正在从输入设备上预输入 。
l 后备状态:此时作业预输入结束但尚未被选中执行 。
l 执行状态:作业已经被选中并构成进程去竞争处理器资源以获得运行 。
l 完成状态:作业已经运行结束,正在等待缓输出 。
多道批处理操作系统的处理机调度至少应该包括作业调度和进程调度两个层次
多道批处理操作系统的处理机调度至少应该包括作业调度和进程调度两个层次 。
作业调度属于高级调度层次,处于后备状态的作业在系统资源满足的前提下可以被选中从而进入内存计算 。 而只有处于执行状态的作业才真正构成进程获得计算的机会 。
作业调度选中了一个作业且把它装入主存储器时就为该作业创建一个用户进程 。 这些进程将在进程调度的控制下占有处理器运行 。 为了充分利用处理器,往往可以把多个作业同时装入主存储器,这样就会同时有多个用户进程,这些进程都要竞争处理器 。
进入计算机系统的作业只有经过两级调度后才能占用处理器。第一级是作业调度,使作业进入主存储器;第二级是处理器调度,使作业进程占用处理器。作业调度与处理器调度的配合能实现多道作业的同时执行,作业调度与进程调度的关系如图 2-42。
图 2-42 作业调度与进程调度的关系执行状态运行就绪 等待输入状态后备状态完成状态进程调度中级调度缓输出作业调度预输入完成
2.6.4 作业调度算法
2.1.1.1 先来先服务算法
先来先服务 (First Come,First Served)算法是按照作业进入系统的先后次序来挑选作业,
先进入系统的作业优先被挑选。
这种算法容易实现,但效率不高,只顾及到作业等候时间,而没考虑作业要求服务时间的长短 。 显然这不利于短作业而优待了长作业,或者说有利于 CPU繁忙型作业不利于 I/O繁忙型作业 。 有时为了等待长作业的执行,而使短作业的周转时间变得很大 。 从而平均周转时间也变大 。
例如,下面三个作业同时到达系统并立即进入调度:
作业名 所需 CPU时间作业 1 28
作业 2 9
作业 3 3
现采用 FCFS算法进行调度,那么,三个作业的周转时间分别为,28,37和 40,因此,平均作业周转时间 T = (28+37+40)/3
= 35
若这三个作业提交顺序改为作业 2,1,3,
平均作业周转时间缩短为约 29。
如果三个作业提交顺序改为作业 3,2,1,
则平均作业周转时间缩短为约 18。
由此可以看出,FCFS调度算法的平均作业周转时间与作业提交的顺序有关 。
2.6.4.2 最短作业优先算法
最短作业优先 (Shortest Job First )算法是以进入系统的作业所要求的 CPU时间为标准,总是选取估计计算时间最短的作业投入运行 。 这一算法也易于实现,但效率也不高,它的主要弱点是忽视了作业等待时间 。 由于系统不断地接受新作业,而作业调度又总是选择计算时间短的作业投入运行,因此,使进入系统时间早但计算时间长的作业等待时间过长,
会出现饥饿的现象 。
例如,若有以下四个作业同时到达系统并立即进入调度:
作业名 所需 CPU时间作业 1 9
作业 2 4
作业 3 10
作业 4 8
假设系统中没有其他作业,现对它们实施 SJF调度算法,
SJF的作业调度顺序为作业 2,4,1,3,
平 均 作 业 周 转 时 间 T = (4+12+21+31)/4
= 17
平均带权作业周转时间 W =
(4/4+12/8+21/9+31/10)/4 = 1.98
如果对它们施行 FCFS调度算法,
平均作业周转时间 T = (9+13+23+31)/4 = 19
平均带权作业周转时间 W =
(9/9+13/4+23/10+31/8)/4 = 2.51
由此可见,SJF的平均作业周转时间比
FCFS要小,故它的调度性能比 FCFS好 。
但实现 SJF调度算法需要知道作业所需运行时间,否则调度就没有依据,作业运行时间只知道估计值,要精确知道一个作业的运行时间是办不到的 。
SJF算法进一步讨论
SJF算法既可以是非抢占式的,也可以是抢占式的 。 当一个作业正在执行时,一个新作业进入就绪状态,如果新作业需要的 CPU时间比当前正在执行的作业剩余下来还需的 CPU时间短,
抡占式短作业优先算法强行赶走当前正在执行作业,这种方式也叫最短剩余时间优先算法
(Shortest Remaining Time First)算法 。 此算法不但适用于 JOB调度,同样也适用于进程调度 。
下面来看一个例子,假如现有四个就绪作业其到达系统和所需 CPU时间如下:
一个例子,假如现有四个就绪作业其到达系统和所需 CPU时间如下:
作业名 到达系统时间 CPU时间 (毫秒 )
--------------------------------------------
Job1 0 8
Job2 1 4
Job3 2 9
Job4 3 5
Job1从 0开始执行,这时系统就绪队列仅有一个作业。 Job2在时间 1到达,而 Job1的剩余时间 (7毫秒 )大于 JOB2所需时间 (4毫秒 ),所以,Job1被剥夺,Job2被调度执行。这个例子的平均等待时间是 ((10-1)+(1-1)+(17-2)+(5-
3))/4=26/4=6.5毫秒。如果采用非抢占式 SJF
调度,那么,平均等待时间是 7.75毫秒。
P1 P2 P4 P1 P3
0丫 1丫 5丫 10
丫
17
丫
26
丫
2.6.4.3响应比最高者优先 (HRN)算法
先来服务算法与最短作业优先算法都是比较片面的调度算法。先来先服务算法只考虑作业的等候时间而忽视了作业的计算时问,而最短作业优先算法恰好与之相反,它只考虑用户估计的作业计算时间而忽视了作业的等待时间。响应比最高者优先算法是介乎这两种算法之间的一种折衷的算法,既考虑作业等待时间,又考虑作业的运行时间,这样既照顾了短作业又不使长作业的等待时间过长,改进了调度性能。
响应比定义
我们把作业进入系统后的等待时间与估计运行时间之比称作响应比,现定义;
响应比 =已等待时间 /估计计算时间显然,计算时间短的作业容易得到较高的响应比,因为,这时分母较小,使得 HRN较高,因此本算法是优待短作业的 。 但是,如果一个长作业在系统中等待的时间足够长后,由于,分子足够大,使得 HRN较大,那么它也将获得足够高的响应比,从而可以被选中执行,不至于长时间地等待下去,饥饿的现象不会发生 。
例如,若有以下四个作业先后到达系统进入调度:
作业名 到达时间 所需 CPU时间作业 1 0 20
作业 2 5 15
作业 3 1 5
作业 4 15 10
假设系统中没有其他作业,现对它们实施
SJF的作业调度顺序为作业 1,3,4,2,
平均作业周转时间 T =
(20+15+20+45)/4 = 25
平均带权作业周转时间 W =
(20/20+15/5+25/10+45/15)/4 = 2.25
如果对它们施行 FCFS调度算法,
平均作业周转时间 T =
(20+30+30+35)/4 = 38.75
平均带权作业周转时间 W =
(20/20+30/15+30/5+35/10)/4 = 3.13
如果对这个作业流执行 HRN调度算法
l 开始时只有作业 1,作业 1被选中,执行时间 20;
l 作业 1执行完毕后,响应比依次为 15/15,10/5、
5/10,作业 3被选中,执行时间 5;
l 作业 3执行完毕后,响应比依次为 20/15,10/10,
作业 2被选中,执行时间 15;
l 作业 2执行完毕后,作业 4被选中,执行时间 10;
平均作业周转时间 T = (20+15+35+35)/4 =
26.25
平均带权作业周转时间 W =
(20/20+15/5+35/15+35/10)/4 = 2.42
2.6.4.4 优先数法
这种算法是根据确定的优先数来选取作业,每次总是选择优先数高的作业。
一种是由用户自己提出作业的优先数 。 有的用户为了自己的作业尽快的被系统选中就设法提高自己作业的优先数,
这时系统可以规定优先数越高则需付出的计算机使用费就越多,以作限制 。
另一种是由系统综合考虑有关因素来确定用户作业的优先数 。
例如,根据作业的缓急程度;作业的类型;作业计算时间的长短,等待时间的多少,资源申请情况等来确定优先数 。
确定优先数时各因素的比例应根据系统设计目标来分析这些因素在系统中的地位而决定 。 上述确定优先数的方法称静态优先数法;如果在作业运行过程中,根据实际情况和作业发生的事件动态的改变其优先数,这称之为动态优先数法 。
2.6.4.5 分类调度算法
分类调度算法预先按一定的原则把作业划分成若干类,以达到均衡使用操作系统资源和兼顾大小作业的目的 。 分类原则包括作业计算时间,对内存的需求,
对外围设备的需求等 。 作业调度时还可以为每类作业设置优先级,从而照顾到同类作业中的轻重缓急 。
2.6.4.6用磁带与不用磁带的作业搭配
这种算法将需要使用磁带机的作业分开来 。 在作业调度时,把使用磁带机的作业和不使用磁带机的作业搭配挑选 。 在不使用磁带机的作业执行时,可预先通知操作员将下一批作业要用的磁带预先装上,这样可使要用磁带机的作业在执行时省去等待装磁带的时间 。 显然这对缩短系统的平均周转时间是有益的 。
2.7 进程调度
2.7.1 进程调度的功能进程调度负责动态地把处理器分配给进程或内核级线程。因此,它又叫处理器调度或低级调度。操作系统中实现进程调度的程序称为进程调度程序,或分派程序 (Dispatcher)。进程调度算法多数适用于线程调度,下面着重介绍进程调度。
何时发生 CPU调度呢?
有四种情况都会发生 CPU调度,当一个进程从运行态切换成等待态时 ; 当一个进程从运行态切换成就绪态时 ; 当一个进程从等待态切换成就绪态时和当一个进程中止时。
进程调度的主要功能是:
l 记住进程的状态 。 这个信息一般记录在一个进程的进程控制块内 。
l 决定某个进程什么时候获得处理器,以及占用多长时间 。
l 把处理器分配给进程 。 即进行进程上下文切换,把选中进程的进程控制块内有关现场的信息;如程序状态字,通用寄存器等内容送入处理器相应的寄存器中,从而让它占用处理器运行 。
l 收回处理器 。 将处理器有关寄存器内容送入该进程的进程控制块内的相应单元,从而使该进程让出处理器 。
进程调度有两种基本方式,
非抢占式和抢占式。
非抢占式进程调度方式中,一旦某个高优先级的进程上有了 CPU,它就一直运行下去,直到其自身原因 (结束或等待 )主动出让 CPU时,才调度另一个高优先级进程运行。
抢占式进程调度方式中,任何时刻严格按高优先级进程在 CPU上运行的原则进行进程 /线程调度,每当一个高优先级进程运行期间,系统中又有更高优先级进程就绪,进程调度将迫使当前运行进程出让 CPU。
可以把两种基本方式组合起来,
形成折衷方式,
实现思路如下:把就绪进程分成不同优先权的几个队列,如按系统进程和用户进程分成高低优先级两队,队列内采用非抢占式,但队列间采用抢占式 。 仅当高优先级队列空时,才能调度低优先级队列 ;而低优先级队列进程运行时,高优先级队列中若有进程就绪,可以抢占处理器 。
2.7.2 进程调度算法
2.7.2.1 先来先服务算法
先来先服务算法是按照进程进入就绪队列的先后次序来分配处理器 。 先进入就绪队列的进程优先被挑选,运行进程一旦占有处理器将一直运行下去直到运行结束或阻塞 。 这种算法容易实现,但效率不高,显然不利于 I/O频繁的进程 。
2.7.2.2 时间片轮转调度
轮转法调度也称之为时间片调度,具体做法是调度程序每次把 CPU分配给就绪队列首进程使用一个时间片,例如
100ms,就绪队列中的每个进程轮流地运行一个这样的时间片。当这个时间片结束时,就强迫一个进程让出处理器,
让它排列到就绪队列的尾部,等候下一轮调度。
这种调度策略可以防止那些很少使用外围设备的进程过长的占用处理器而使得要使用外围设备的那些进程没有机会去启动外围设备 。
间隔时钟
基本轮转法
改进 轮转法
轮转法调度是一种剥夺式调度,系统耗费在进程切换上的开销比较大,这个开销与时间片的大小很有关系 。 如果时间片取值太小,以致于大多数进程都不可能在一个时间片内运行完毕,切换就会频繁,系统开销显著增大,所以,从系统效率来看,时间片取大一点好 。 另一方面,时间片长度较大,那么随着就绪队列里进程数目的增加,
轮转一次的总时间增大,亦即对每个进程的响应速度放慢了 。 为了满足用户对响应时间的要求,
要么限制就绪队列中的进程数量,要么采用动态时间片法,根据当前负载状况,及时调整时间片的大小 。 所以,时间片大小的确定要从进程个数,切换开销,系统效率和响应时间等方面考虑 。
2.7.2.3 优先权调度
可以有以下几种考虑,使用外围设备频繁者优先数大,这样有利于提高效率;重要算题程序的进程优先数大,这样有利于用户;进入计算机时间长的进程优先数大,这样有利于缩短作业完成的时间;交互式用户的进程优先数大,这样有利于终端用户的响应时间等等,以上采用了静态优先数法 。
效率高性能好的进程调度可采用动态优先数法,基本原则是,① 根据进程占有 CPU时间多少来决定,当一个进程占有 CPU时间愈长,那么,在它被阻塞之后再次获得调度的优先级就越低,反之,进程获得调度的可能性越大 ;② 根据进程等待 CPU时间多少来决定,当一个进程在就绪队列中等待时间愈长,那么,在它被阻塞之后再次获得调度的优先级就越高,反之,进程获得调度的可能性越小 。
2.7.2.4 多级反馈队列调度
这种方法又称反馈循环队列或多队列策略 。 其主要思想是将就绪进程分为两级或多级,系统相应建立两个或多个就绪进程队列,较高优先级的队列一般分配给较短的时间片 。 处理器调度每次先从高级的就绪进程队列中选取可占有处理器的进程,只有在选不到时,才从较低级的就绪进程队列中选取 。
图 2-44 一个三级调度策略低级就绪队列高级就绪队列 中级就绪队列等待磁盘磁带等待其他外设运行选中,时间片 500ms超过时间片启动磁盘磁带启动其他外设选中,时间片 200ms选中,时间片 100ms
2.7.2.5 保证调度算法
一种完全不同的调度算法是向用户做出明确的性能保证,然后去实现它 。 一种很实际并很容易实现的保证是:当你工作时己有 n个用户登录在系统,则你将获得 CPU处理能力的 1/n。 类似的,如果在一个有 n个进程运行的用户系统中,每个进程将获得 CPU处理能力的 1/n。
为了实现所作的保证,系统必须跟踪各个进程自创建以来已经使用了多少 CPU
时间 。 然后它计算各个进程应获得的
CPU时间,即自创建以来的时间除以 n。
由于各个进程实际获得的 CPU时间已知,
所以很容易计算出实际获得的 CPU时间和应获得的 CPU时间之比,于是调度将转向比率最低的进程 。
2.7.2.6 彩票调度算法
其基本思想是:为进程发放针对系统各种资源
( 如 CPU时间 ) 的彩票 。 当调度程序需要做出决策时,随机选择一张彩票,持有该彩票的进程将获得系统资源 。 对于 CPU调度,系统可能每秒钟抽 50次彩票,每次中奖者可以获得 20ms的运行时间 。
在此种情况下,所有的进程都是平等的,它们有相同的运行机会 。 如果某些进程需要更多的机会,
就可以被给予更多的额外彩票,以增加其中奖机会 。 如果发出 100张彩票,某一个进程拥有 20张,
它就有 20%的中奖概率,它也将获得大约 20%的
CPU时间 。
彩票调度法有几点有趣的特性 。 彩票调度的反映非常迅速,例如,如果一个新进程创建并得到了一些彩票,则在下次抽奖时,它中奖的机会就立即与其持有的彩票成正比 。
如果愿意的话,合作的进程可以交换彩票 。 例如,一个客户进程向服务器进程发送一条消息并阻塞,它可以把所持有的彩票全部交给服务器进程,以增加后者下一次被选中运行的机会;当服务器进程完成响应服务后,它又将彩票交还给客户进程使其能够再次运行;实际上,
在没有客户时,服务器进程根本不需要彩票 。
彩票调度还可以用来解决其他算法难以解决的问题 。 例如,一个视频服务器,其中有若干个在不同的视频下将视频信息传送给各自的客户,假设它分别需要 10,20和
25帧 /秒的传输速度,则分别给这些进程分配 10,20和 25
张彩票,它们将自动按照正确的比率分配 CPU资源 。
2.7.3 实时调度
2.7.3.1 实时操作系统的特性
实时系统是那些时间因素非常关键的系统 。 例如,
计算机的一个或多个外设发出信号,计算机必须在一段固定时间内做出适当的反应 。 一个实例是,
计算机用 CD-ROM放 VCD时,从驱动器中获得的二进制数据必须在很短时间转化成视频和音频信号,如果转换的时间太长,图像显示和声音都会失真 。 其他的实时系统还包括监控系统,自动驾驶系统,安全控制系统等等,在这些系统中,迟到的响应即使正确,也和没有响应一样糟糕 。
硬实时系统和软实时系统
实时系统通常分为硬实时 ( hard real time) 系统和软实时 ( soft real time) 系统 。 前者意味着存在必须满足的时间限制;后者意味着偶尔超过时间限制时可以容忍的 。 这两种系统中,
实时性的获得时通过将程序分成很多进程,而每个进程的行为都预先可知,这些进程处理周期通常都很短,往往在一秒钟内就运行结束,
当检测到一个外部事件时,调度程序按满足他们最后期限的方式调度这些进程 。
周期性和非周期性事件
实时系统要响应的事件可以进一步划分为周期性 ( 每个一段固定的时间发生 ) 事件和非周期性 ( 在不可预测的时间发生 ) 事件 。 一个系统可能必须响应多个周期的事件流,根据每个事件需要的处理时间,系统可能根本来不及处理所有事件 。 例如,有 m个周期性事件,事件 i的周期为 Pi,其中每个事件需要 Ci秒的 CPU时间来处理,则只有满足以下条件:
C1/P1 + C2/P2 + … + Cm/Pm ≤ 1
时,才可能处理所有的负载 。 满足该条件的实时系统称作可时刻调度的 ( schedulable) 。
举例来说,一个软实时系统处理三个事件流,其周期分别为 100ms,200ms和 500ms,
如果事件处理时间分别为 50ms,30ms和
100ms,则这个系统是可调度的,因为
0.5 + 0.15 + 0.2 ≤ 1
如果加入周期为 1秒的第 4个事件,则只要其处理时间不超过 150ms,该系统仍将时可调度的 。 当然,这个运算的隐含条件是进程切换的时间足够小,可以忽略 。
2.7.3.2 实时调度算法
1) 单比率调度算法单比率调度事先为每个进程分配一个与事件发生频率成正比的优先数 。 例如,周期为 20ms的进程优先数为 50,周期为
100ms的进程优先数为 10,运行时调度程序总是调度优先数最高的就绪进程,
并采取抢占式分配策略 。 可以证明概算法是最优的 。
2) 限期调度算法限期调度算法的基本思想是:当一个事件发生时,
对应的进程就被加入就绪进程队列 。 该就绪队列按照截止期限排序,对于一个周期性事件,
其截止期限即为事件下一次发生的时间 。 该调度算法首先运行队首进程,即截止时间最近的那个进程 。
3) 最少裕度法最少裕度法的基本思想是:首先计算各个进程的富裕时间,即裕度 ( laxity),然后选择裕度最少的进程执行 。
2.7.4 多处理器调度
一些计算机系统包括多个处理器,目前应用较多的,
较为流行的多处理器系统有:
l松散耦合多处理器系统:如 cluster,它包括一组独立的处理器,每个处理其拥有自己的主存和 I/O
通道 。
l紧密耦合多处理器系统:它包括一组处理器,共享主存和外设 。
因此操作系统的调度程序必须考虑多粒处理器的调度 。 显然,单个处理器的调度和多粒处理器的调度有一定的区别,现代操作系统往往采用进程调度与线程调度相结合的方式来完成多处理器调度 。
2.7.4.1 同步的粒度
同步的粒度,就是系统中多个进程之间同步的频率,它是刻画多处理系统特征和描述进程并发度的一个重要指标 。 一般来说,我们可以根据进程或线程之间同步的周期 ( 即每间隔多少条指令发生一次同步事件 ),把同步的粒度划分成以下 5个层次:
l细粒度 ( fine-grained),同步周期小于 20条指令 。
这是一类非常复杂的对并行操作的使用,类似于多指令并行执行 。 它属于超高并行度的应用,目前有很多不同的解决方案,本书将不涉及这些解决方案,
有兴趣的可以参见有关资料 。
l中粒度 ( medium-grained),同步周期为 20-200条指令 。 此类应用适合用多线程技术实现,即一个进程包括多个线程,多线程并发或并行执行,以降低操作系统在切换和通信上的代价 。
l粗粒度 ( coarse-grained),同步周期为 200-2000条指令 。 此类应用可以用多进程并发程序设计来实现 。
l超粗粒度 ( very coarse-grained),同步周期为 2000
条指令以上 。 由于进程之间的交互是非常不频繁,
因此这一类应用可以在分布式环境中通过网络实现并发执行 。
l独立 ( independent),进程或线程之间不存在同步 。
对于那些具有独立并行性的进程来说,多处理器环境将得到比多道程序系统更快的响应 。 考虑到信息和文件的共享问题,在多数情况下,共享主存的多处理器系统将比那些需要通过分布式处理实现共享的多处理器系统的效率更高 。
对于那些具有粗粒度和超粗粒度并行性的进程来说,并发进程可以得益于多处理器环境 。 如果进程之间交互不频繁的话,分布式系统就可以提供很好的支持,而对于进程之间交互频繁的情况,多处理器系统的效率更高 。
无论是有独立并行性的进程,还是具有粗粒度和超粗粒度并行性的进程,在多处理器环境中的调度原则和多道程序系统并没有太大的区别 。 但在多处理器环境中,一个应用的多个线程之间交互非常频繁,针对一个线程的调度策略可能影响到整个应用的性能 。 因此在多处理器环境中,我们主要关注的是线程的调度 。
2.7.4.2 多处理器调度的设计要点
多处理器调度的设计要点之一是如何把进程分配给处理器。
多处理器调度的设计要点之二石川
>
2.7.4.3 多处理器的调度 算法
1) 负载共享调度算法
2) 群调度算法
3) 处理器专派调度算法
4) 动态调度算法
>
群调度算法中为应用进程分配 CPU时间
方法一 面向应用进程平均分配
方法二 面向所有进程平均分配
2.7.5 实例研究 ——传统 Unix调度算法
传统 Unix的进程调度采用多级反馈队列,对于每一个优先级队列采用时间片调度策略 。 系统遵从 1秒抢占的原则,
即一个进程运行了 1秒还没有阻塞或完成的话,它将被抢占 。 优先数根据进程类型和执行情况确定,计算公式:
Pj(i) = Basej + CPUj(i-1)/2 + nicej
CPUj(i) = Uj(i)/2 + CPUj(i-1)/2
其中:
l Pj(i):进程 j的优先数,时间间隔为 i;取值越小,优先级越高
l Basej:进程 j的基本优先数
l Uj(i):进程 j在时间间隔 i内的处理器使用情况
l CPUj(i):进程 j在时间间隔 i内的处理器使用情况的指数加权平均数
l nicej:用户控制的调节因子
3 用户进程在下列情况下重新计算优先数:发生中断和时钟中断 ;对优先数 >100的进程,每秒计算一次
4 计算公式:
p-pri=min(127,100+p-cpu/16+p-nice)
p-nice用户控制因子,设成 0~20或 0~-20
p-CPU是反映进程运行状况的参数,定时修改它
5 结论
Unix早期版本调度算法
1 采用动态优先数进程调度策略,数值越小,优先权越高
2 系统进程使用 sleep(chan,pri)进入等待放弃 CPU。 Chan为等待事件,pri为优先数供唤醒用,分为 -100,-90,-50,1,40,90。
每个进程的优先数每秒计算一次,并随后做出调度决定 。 基础优先数用作把所有进程划分到固定的优先级队列中,CPUj 和
nicej受到限制以防止进程迁移出分配给它的由基础优先数确定的队列 。 这些队列用作优化访问块设备或允许操作系统快速响应系统调用 。 根据有利于 I/O设备有效使用的原则,进程队列按优先级从高到低排列:
l 对换
l 块设备控制
l 文件操纵
l 字符设备控制
l 用户进程
2.7.6 实例研究 —Unix SVR4调度算法
Unix SVR4对调度算法的主要修改包括:
l提供了基于静态优先数的抢占式调度,包括 3类优先级层次,160
个优先数 。
l引入了抢占点 。 由于 Unix的基本内核不是抢占式的,它将被划分划分成一些处理步骤,如果不发生中断的话,这些处理步骤将一直运行直到结束 。 在这些处理步骤之间,存在着抢占点,称为 safe
place,此时内核可以安全地中断处理过程并调度新进程 。 每个 safe
place被定义成临界区,从而保证内核数据结构通过信号量上锁并被一致地修改 。
在 Unix SVR4中,每一个进程必须被分配一个优先数,从而属于一类优先级层次 。 优先级和优先数的划分如下:
l实时优先级层次 ( 优先数为 159-100),这一优先级层次的进程先于内核优先级层次和分时优先级层次的进程运行,并能利用抢占点抢占内核进程和用户进程 。
l内核优先级层次 ( 优先数为 99-60),这一优先级层次的进程先于分时优先级层次进程但迟于实时优先级层次进程运行 。
l分时优先级层次 ( 优先数为 59-0),最低的优先级层次,一般用于非实时的用户应用 。
Unix SVR4的进程调度参见图 2-45,它事实上还是一个多级反馈队列,每一个优先数都对应于一个就绪进程队列,而每一个进程队列中的进程按照时间片方式调度。位向量 dqactmap用来标志每一个优先数就绪进程队列是否为空。当一个运行进程由于阻塞、时间片或抢占让出处理器,调度程序首先查找 dqactmap已发现一个较高优先级的非空队列,然后指派进程占有处理器运行。另外,
当执行到一个定义的抢占点,内核程序将检查一个叫作 kprunrun的标志位,如果发现有高优先级的实时进程处于就绪状态,就执行抢占。
图 2-45 Unix SVR4的就绪进程队列
dqactmap
dispq 159,.....,.....159 2 1 0
0,.....,.....1 1 1 0
P
P
P P
P
P
2.7.7实例研究 —Windows NT调度算法
Windows NT的设计目标有两个一是向单个用户提供交互式的计算,
环境,
二是支持各种服务器 ( server) 程序 。
Windows NT的调度是基于内核级线程的,
它支持抢占式调度,包括多个优先数层次,在某些层次线程的优先数是固定的,
在另一些层次线程的优先数将根据执行的情况动态调整 。 它的调度策略是一个多级反馈队列,每一个优先数都对应于一个就绪队列,而每一个进程队列中的进程按照时间片方式调度 。
1 概述
线程调度算法
Kernel’s Dispatcher
线程调度触发事件
2 线程调度 API
3 线程优先级
16个实时线程优先级
15个可变线程优先级
1个系统线程优先级
4 中断优先级与线程优先级关系
5 线程时间配额
6 线程调度数据结构
调度器就绪队列
就绪位图
空闲位图
内核自旋锁
7 调度策略
主动切换
抢先
时间配额用完
结束
8 SMP上线程调度
亲合关系
线程首选 CPU和第二 CPU
就绪线程局运行 CPU选择
为特定 CPU调度线程
Windows NT有两个优先级层次:
l实时优先级层次 ( 优先数为 31-16),用于通信任务和实时任务 。 当一个线程被赋予一个实时优先数,在执行过程中这一优先数是不可变的 。 NT支持优先数驱动的抢占式调度,一旦一个就绪线程的实时优先数比运行线程高,它将抢占处理器运行 。
l 可变优先级层次 ( 优先数为 15-0),用于用户提交的交互式任务 。 具有这一层次优先数的线程,可以根据执行过程中的具体情况动态地调整优先数,但是 15这个优先数是不能被突破的 。
图 2-46 Windows NT的线程优先级最高 (31)
实时优先级层次最低 (16)
最高 (15)
最低 (0)
可变优先级层次一个线程如果被赋予可变优先数,那么它的优先数调整服从下面规则:
l 线程所属的进程对象有一个进程基本优先数,取值范围从 0到 15。
l 线程对象有一个线程基本优先数,取值范围从 -2到 2。
l 线程的初始优先数为进程基本优先数加上线程基本优先数,但必须在 0到 15的范围内 。
l 线程的动态优先数必须在初始优先数到
15的范围内 。
当运行线程用完了时间片,调度程序将降低它的优先数,
而当运行进程因为 I/O阻塞后,调度程序则将提高它的优先数 。 并且优先数的提高与等待的 I/O设备有关,等待交互式外围设备 ( 如键盘和显示器 ) 时,优先数的提高幅度大于等待其他类型的外围设备 ( 如磁盘 ),显然这种优先数调整方式有利于交互式任务 。
当 NT运行在单个处理器上时,最高优先级的线程将运行直到它结束,阻塞或被抢占 。 而当 NT运行在 N个处理器上时,N-1个处理器上将运行 N-1个最高优先级的线程,
其他线程将共享剩下的一个处理器 。 值得指出的是,当线程的处理器亲和属性 ( processor affinity attribute) 指定了线程执行的处理器时,虽然系统有可用的处理器,
但指定处理器被更高优先级的线程占用,此时该线程必须等待,而可用处理器将被调度给优先级较低的就绪线程 。