学习笔记参考:广州大学软件工程操作系统课程
操作系统(Operating System,简称OS)是管理计算机硬件与软件资源的计算机程序。操作系统是计算机学生必修课程之一,也是计算机考研的必考项,因此,掌握好操作系统对于工作以及日后考研学习都十分重要。
1、操作系统引论
一、操作系统(OS)的定义:
是计算机最基础的软件系统。他管理软硬件资源,控制程序执行,改善人机界面,合理组织计算机工作流程,为用户使用计算机提供良好的运行环境
二、操作系统目的
- 方便性
- 有效性
- 可扩充性
- 开放性
三、操作系统的作用
-
作为用户与计算机硬件系统的接口(软件接口)
- 命令方式
- 图形、窗口方式
- 系统调用方式
-
计算机系统资源的管理者
- 系统资源的分类
- 处理器
- 存储器
- I/O设备以及信息(数据和程序)
- OS的主要功能也是针对这四类资源进行有效的管理
- 处理机管理:用于分配和控制处理机
- 存储器管理:主要负责内存的分配和回收
- I/O设备管理:负责I/O设备的分配和操纵
- 文件管理:负责文件的存取、共享和保护
- 系统资源的分类
-
OS实现对计算机资源的抽象,用作扩充机器
- 通常把覆盖了软件的机器称为扩充机器或虚拟机
四、操作系统的发展过程
1、人工操作方式(没有OS)–纸带
缺点:
(1)用户独占全机
(2)CPU等待人工操作
2、脱机输入/输出方式
优点:
(1)减少了CPU的空闲时间
(2)提高了I/O速度
注意:并没有提高某程序执行速度,因为中间还多了几个过程。
二、单道批处理系统
处理过程:在一个监督程序(Minitor)的控制下,一批作业一个接一个的连续处理
这种单道批处理就跟排队打饭一样,前面的那个没有打完饭,后面的就没法打饭。
特征:
- 自动性
- 顺序性
- 单道性
三、多道批处理系统
用户所提交的作业都先存放在外存上并排成一个队列,称为"后备队列",由作业调度程序按一定的算法从后备队列中选择若干个作业调入内存,共享CPU和系统中的各种资源
好处:
- 提高CPU的利用率
- 提高内存和I/O设备利用率
- 增加系统吞吐量
特征:
- 多道性
- 无序性
- 调度性
缺点:
- 平均周转时间长
- 无交互能力
四、分时系统
分时系统是为了满足用户需求所形成的一种新型的OS
时间片:
给定一个时间片,假设是5ms,那么每个5ms执行下一个程序。不断重复间隔执行不同的程序
特征:
- 多路性
- 独立性
- 及时性
- 交互性
五、实时系统
能在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致的运行。
外部设备所发出的激励信号并无明显的周期性,但都必须联系一个截止时间
特征:
- 多路性
- 独立性
- 及时性
- 交互性
- 可靠性
实时交互系统:
QNS(汽车领域)、Vxworks(卫星通讯,军事演习,导弹制导,飞机导航)、uc/oc-ii
六、网络操作系统
七、分布式操作系统
分布式系统是多个处理机通过通信线路互连而构成的松散耦合的系统
分布式操作系统是配置在分布式系统上的操作系统
1.1操作系统的基本特性
一、并发
并行性:两个或多个事件在同一时刻发生
并发性:两个或多个事件在同一时间间隔发生
二、共享
系统中的资源可以供内存中的多个并发执行的进程(线程)共同使用
主要有两种共享方式:
1、互斥共享方式(例子:打印机)
打印机可以给多个进程使用,但是为了避免打印混乱,一般都是需要等待执行完后,再允许另一进程对资源的访问
在一段时间内只允许一个进程访问的资源叫做临界资源或独占资源。
计算机中大多数物理设备,以及栈,变量和表格,都是临界资源。
2、同时访问方式
所谓的"同时"往往是宏观上的,微观上,这些进程可能在交替的对资源进行访问
典型的设备是磁盘设备
并发和共享是操作系统的两个最基本的特征,他们又是互为存在的条件
三、虚拟
是指通过某种技术(虚拟技术)将一个物理实体变为若干个逻辑上的对应物。
在操作系统中,虚拟的实现主要是通过分时使用的方法。
四、异步性–停停走走的方式运行
###=# 1.2 操作系统的主要功能
一、处理机管理功能
1、进程控制
2、进程同步
3、进程通信
4、调度
- 在后备队列等待的每个作业,一般都要经过调度才能执行。包括作业调度和进程调度
二、存储器管理功能
1、内存分配(有静态和动态两种方式) - 内存分配数据结构
- 内存分配功能
- 内存回收功能
2、内存保护(一个典型的例子就是指针)
3、地址映射
逻辑地址转换为物理地址
4、内存扩充—借助虚拟存储技术
1.3 设备管理功能
- 缓冲管理
- 设备分配
- 设备处理(设备驱动程序)
- 虚拟设备
1.4 文件管理功能
1、文件存储空间的管理
2、目录管理
3、文件的读/写管理和保护
1.5 内核分类
1、单内核,也称为宏内核—Linux
2、微内核–QNX,鸿蒙
3、混合内核
2、进程和线程
2.1 进程的概念
操作系统有个很重要的作用就是进程管理,相当于大脑
2.1.1 顺序和并发
顺序:很好理解,不解释
顺序执行时的特征:
- 顺序性
- 封闭性
- 独占全局资源,结果不受外界的影响
- 可再现性
- 重复执行结果相同(逻辑不变)
顺序执行的场合:单道批处理系统。—效率低,浪费资源,只有一个程序在跑。
并发:效率高、节省时间、若干个程序同时被执行(时间片)。但是并发有可能会出错。
由于时间片,可能是程序A先执行出结果,也可能是B,并且,由于i是全局变量,所以容易被覆盖修改。
并发执行的特征:
- 失去封闭性
- 运行过程不稳定
- 不可再现性
**解决方案:**对运行过程施加相互制约
因为并发的效率和正确性的问题,所以引入了进程的概念。
进程(Process):
通俗的定义:进程是程序的一次执行(程序动态执行的过程)。
广义定义:一个具有独立功能的程序关于某个数据集合的一次运行活动。
进程VS程序
程序——静态(机器代码,数据)
进程——动态(程序的一次执行)
1.动态和静态
2.暂存和长存
3.程序和进程的对应(一个程序可能有多个进程)
他们就好比 演出(进程)和剧本(程序) 的关系:
一个程序可以有多个进程——一个剧本可以有多个演出(话剧、歌唱、舞蹈等形式)
并且进程是有周期的,演出也是有周期的,演出是从开始到结束。
进程的基本特征:
- 动态性
- 并发性
- 独立性
- 独立运行,独立获得资源,独立接受调度
- 异步性
2.2进程的状态和组成:
1、进程的三种基本状态
- 运行状态
- 能分到CPU运行
- 就绪状态
- 其他进程正在占用CPU,它正在排队等待
- 阻塞(等待)状态
- 因等待某种事件的发生(如系统调用、I/O操作、合作进程信号)而暂时不能运行的状态。这个时候,即使CPU空闲,也无法使用。
- 新建状态
- 进程刚被创建未进入就绪队列中
- 终止状态
- 正常终止
- 异常终止
- 挂起状态
- 运行暂停,此时系统回收资源,将其实体复制到外存(硬盘)的进程交换区。挂起不等于撤销,可通过解挂重新分配内存。
阻塞VS挂起
阻塞——资源到了—>就绪
挂起:由客观原因引起,需要有激活过程
2.状态的转换
注意:
没有下面两种转换:
- 阻塞—运行
- 就绪—阻塞
- 只有运行的时候会出现资源短缺而进入阻塞
UNIX进程状态转换
Linux中进程的五种状态:
2.2.1进程描述
进程映像由四部分组成:
程序、数据集合、PCB(进程控制块)、栈
PCB是进程组成中最关键的部分,含有进程的描述信息和控制信息,是系统对进程施行识别和控制的依据。
做个比喻:人是自然人,但是出到社会,人们对你的理解是你的相关信息,档案、身份证、征信记录等等。
PCB包括如下:
进程名、特征信息、进程状态信息、调度优先权、通信信息、现场保护区(上下文)、资源需求、分配和控制方面的信息、进程实体信息、族类关系、其他信息。
寄存器VS存储器
例子:银行(硬盘):存储了主要的数据(主要财产)。放在家里的钱(内存)。而通用寄存器,就好比是口袋,每个口袋都有名字(上衣口袋,裤子口袋…),每个口袋有专门的功能(内村口袋用来放钱,裤子口袋用来放纸巾…),并且这些口袋的钱是最直接和现实生活打交道的。
每个进程都有唯一的PCB
PCB是进程存在的标志(你的身份证是你存在的标志)
2.2.2 进程队列
- 线性方式
- 有最大数的限制,操作麻烦
- 链接方式
- 索引方式
Linux系统的进程控制块PCB为结构体 task_struct
Linux使用了一个线性表task来存放多个进程的PCB:
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
2.2.3 进程控制
进程控制一般是由OS的内核中的原语来实现的
操作系统内核的功能:
- 中断处理
- 时钟管理
- 原语操作(原子操作)
- 不可分割,执行过程不允许中断,所以一般都是执行最关键的操作用原语
2.2.4进程的创建
通常把创建进程的进程称为父进程,而把被创建的进程称为子进程
当子进程被撤消时,应将其从父进程那里获得的资源归还给父进程。此外,在撤消父进程时,也必须同时撤消其所有的子进程。
句柄:其作用相当于一个令牌,可以用来控制被创建的进程。但是,这个句柄是可以进行传递的。
引起创建进程的事件:
- 系统内核为用户创建新进程
- 用户登录
- 作业调度
- 提供服务
- 用户自己创建新进程
- 应用请求
进程创建过程:
- 申请空白PCB
- 为新进程分配所需运行资源
- 初始化PCB
- 如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。
进程的终止
- 正常结束
- 当程序运行到Halt指令时,将产生一个中断,去通知OS本进程已经完成。
- 异常结束
- 在进程运行期间,由于出现某些错误和故障而迫使进程终止。
- 外界干预
-进程应外界的请求而终止运行
进程终止过程
如果系统中发生了要求终止进程的某事件,OS便调用进程终止原语:
(1) 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态;
(2) 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度;
(3) 若该进程还有子孙进程,还应将其所有子孙进程也都予以终止,以防它们成为不可控的进程;
(4) 将被终止进程所拥有的全部资源或者归还给其父进程,或者归还给系统;
(5) 将被终止进程(PCB)从所在队列(或链表)中移出,等待其它程序来搜集信息。
进程的阻塞与唤醒
引起进程阻塞或唤醒的事件:
1、向系统请求共享资源失败
2、等待某种操作的完成
3、新数据尚未到达
4、等待新任务的到达
进程阻塞过程(主动行为)
进程通过调用阻塞原语block将自己阻塞
唤醒:唤醒原语wakeup
wakeup执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。
2.2.5Linux进程控制常用函数
- fork()
- exec()
- wait()
- exit()
fork()函数
作用:创建一个新进程。
系统调用格式: pid=fork( )
参数定义:int fork( )
返回值意义:
- 0:在子进程中,pid变量保存的fork( )返回0,表示当前进程是子进程。
- 大于0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。
- -1:创建失败。
如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程的副本,称为子进程。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文(即程序)。父进程与子进程并发执行。
例子1:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
main()
{ pid_t pid;
pid=fork();
printf("Hello,World!\n");
}
上面结果表明,fork()开启了两个进程。
例子2:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
main()
{ pid_t pid;
pid=fork();
if (pid<0)
{
printf("proc create error!\n");
exit(1);
}
else if (pid==0)
printf("Hello,World!\n");
else
printf("what is your name?\n");
}
上面结果表明,创建进程成功,但是,实际上,这两条语句哪句在先哪句在后(父进程先执行完还是子进程先执行完)是不一定的,因为时间片的原因,两个进程交互着执行一小段时间。
fork( )只是将父进程的用户级上下文拷贝到新进程中,相当于“克隆”。
exec系列函数
exec( )系列函数用新进程取代了原来进程,并将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上。因此,如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行。新进程的进程标识符id 与调用进程相同。
exec( )系列有6个函数: execl、execlp、execle、execv、execvp、 execve,真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用。
exec( )系列在系统库unistd.h中,其基本功能相同,只是以不同的方式来给出参数。主要参数包括路径、程序名、参数等。
例子1:
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
main( )
{
execl("/bin/ls","ls","-l","../",NULL);
printf("hello,world!\n");
exit(0);
}
上面结果没有打印出hello world,说明没有执行下面的语句,原因是execl产生了新进程覆盖了原来的进程。
exec()和fork()的联合使用
因为使用fork虽然可以创建一个新的进程,但是子进程是父进程的副本,而exec可以创建一个新的进程,但会覆盖原来的进程,所以我们希望创建新进程的同时又不会覆盖父进程,可以采用exec和fork结合的方式
用fork( )建立子进程,然后在子进程中使用exec( ),这样就实现了父进程与一个与它完全不同子进程的并发执行。
例子1:
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
main( )
{
int pid;
pid=fork( ); /*创建子进程*/
switch(pid)
{
case -1: /*创建失败*/
printf("fork fail!\n");
exit(1);
case 0: /*子进程*/
execl("/bin/ls","ls","-l","../",NULL);
printf("this is son!\n");
exit(1);
default: /*父进程*/
printf("this is father !\n");
exit(0);
}
}
2.3 线程(轻型进程)
进程:使得多个程序能够并发执行,提高资源利用率和吞吐量
线程:减少程序在并发执行时所付出的时空开销,使得OS具有更好的并发性
进程的两个基本属性:
- 进程是一个可拥有独立资源的独立单位
- 进程同时又是一个可独立调度和分派的基本单位
- 每个进程在系统中有惟一的PCB,系统可根据其PCB感知进程的存在,对进程进行调度,将断点信息保存在其PCB中。
程序并发所需要的时空开销:
- 创建进程
- 撤销进程
- 进程切换(对进程进行执行上下文的切换)
线程—作为调度和分派的基本单位
可以使得多个程序能够更好的并行执行,同时又减少系统开销。
结论:
- 进程—资源分配的最小单位
- 线程—程序执行的最小单位
线程应用场景:
- 需要有多功能并发
- 改善交互性
- 多核CPU,充分发挥多核CPU的性能
多线程OS中的进程属性
- 进程是一个可拥有资源的基本单位
- 多个线程可并发执行
- 进程已不是可执行的实体
线程的两种实现方式:
1、用户级线程ULT(User Level Threads)——线程整个放在在用户空间,内核对线程一无所知,只对进程管理。
2、内核支持线程KST(Kernel Supported Threads) ——内核知道线程的存在,并对它们进行管理。内核调度时,以线程为基本单位。
用户级线程ULT下,调度以进程为单位进行,各个进程轮流执行一个时间片。
而内核支持线程KST下,调度以线程为单位进行,各个线程轮流执行一个时间片。
Linux中线程基本操作的相关函数
- pthread_creat:创建线程。
- pthread_exit:线程退出
- pthread_join:线程之间的同步。类似进程之间的同步函数wait。函数pthread_join将当前线程挂起,等待另一线程的结束
例子
注意:编译命令:gcc thread.c -lpthread -o thread
2.3.1 进程的同步和互斥
进程间的相互关系主要有三种:同步、互斥、通讯
进程的同步关系
若干合作进程为了完成一个共同的任务,需要相互协作运行进度,一个进程开始之前需要要求另外一个进程已经完成某个操作,否则前面的进程只能等待。
进程的互斥关系
多个进程由于共享了独占性资源(一次只能有一个进程占用,比如打印机等),必须协调各进程对资源的存取顺序,确保不会有两个或两个以上的进程占用。(谁先占,那么就只能一个人占,其他人就不能进来)互斥和资源共享相关
资源称为临界资源,存取操作区域称为临界区
比如下面两个程序中,当A程序在修改A的时候,B程序不能进来修改,否则就乱套了。其中,A表示的是临界资源,而操作的这段区域称为临界区。
互斥关系也属于同步关系,是一种特殊的同步
临界资源: 一次仅允许一个进程使用的共享资源称为临界资源
临界区: 每个进程中访问临界资源的那段代码称为临界区
临界区进入准则
- 单个入区
- 独自占用
- 尽快退出
- 落败让权:如果进程不能进入临界区,则让出CPU,以免出现忙等现象
临界区的设置是大一点好还是小一点好呢?——尽量小一点
互斥实现方式(临界区访问方式)
一、硬件同步机制
1、关中断
- 是实现互斥的最简单的方法之一
- 原理:由于禁止中断,时钟中断也被禁止
- 缺点:
- 滥用中断权利可能会导致严重的后果
- 关中断的时间过长,会影响系统效率
- 不适用多CPU系统,因为在一个CPU上关中断并不能防止进程在其他CPU上执行相同临界区的代码
2、TSL指令、SWAP指令
- 利用硬件指令以实现互斥的方法
- 该指令的执行过程不能被分割
- 仍然是不符合让权等待原则
boolean TSL(boolean *lock){
boolean old;
old=*lock;
*lock=TRUE;
return old;
}
# *lock=True 表示资源正在被占用 为FALSE表示资源空闲
二、软件方式
1、锁机制
- 原理:设置一个标志锁,表明临界区可用还是不可用
- 注意:锁机制不满足让权等待
#上锁
上锁原语 lock(W):
while(W==1);
w=1;
#开锁原语unlock(W ):
W=0;
说明:W=0,此时执行lock,让W=1,假如这个时候程序B也要进行访问,这个时候程序B执行lock,在while处循环下不去,此时A执行临界区代码,执行完毕后unlock使得W=0,这个时候程序B的while循环判断W=0了,所以可以执行临界区代码并将W置为1…
2、信号量机制(P(wait(S))测试、V操作(signal(S)增加)
- 整型信号量–结构型信号量–信号量集
- 整型信号量
- P,V是两个原子操作
- 整型信号量的P,V操作仍然会忙等
对信号量S的操作限制:
- 信号量S可以初始化为一个非负值
- 只能由P、V两个操作来访问信号量
对于整型信号量的P,V和前面的锁机制的不同:
根据S的初始不同,整型信号的P,V操作可以根据S的初始化不同允许多个进程进入临界区。S的数字表示能占资源的个数。
原语:原语操作也被叫做“原子操作”,即一个操作中的动作要么做全,要么不做,不能有分割
记录型信号量(结构型信号量)
记录型信号量机制是一种不存在忙等现象的进程同步机制。 但采取让权等待的策略后,又会出现多个进程等待访问同一邻接资源的情况。因此,除了需要一个代表资源数目的整型变量value外,还应该增加一个进程链表指针list,用于链接上述的所有等待进程。
typeof struct {
int value; //资源数目
struct pross_control_block *list; //进程链表指针
}semaphore;
//p操作
P(semaphore * S){
S->value--;
if(S->value<0) block(S->list);
}
//V操作
V(semaphore *S){
S->value++;
if(S->value<=0) wakeup(S->list);
}
说明:类似图书馆借书
假设图书馆有2本书,假设小明来借书,这个时候value–,value变成1,小明将书借走,又来一个小黄来借书,value–变成0,小黄将书借走,小红来借书,这个时候value–变成-1,这个时候P中将小红登记到借书名单内(放到阻塞队列中),小绿来借书,value再–变成-2,将小绿放到阻塞对列中,假设这个时候小明看完书并归还,执行V操作,这个时候value++为-1并唤醒阻塞队列(将书借给小黄)…
规律:
当value为正数:资源的总数
当value为负数:排队的人数
AND型信号量
有的应用场景,是一个进程往往需要获得两个或更多的共享资源后才能执行任务的情况。假设现在有两个进程A和B,他们都要求访问共享数据D和E,共享数据应作为临界资源。
注意:下面的wait相当于P操作;假设Dmutex和Emutex的初始值都为1
process A:
wait(Dmutex);
wait(Emutex);
process B:
wait(Emutex);
wait(Dmutex);
上面的程序明显是有问题的,因为如果A和B同时申请资源,这个时候AwaitD,BwaitE,这个时候,A没法继续向下执行,B也没法继续向下执行(资源各有一部分被双方占用)——死锁
AND同步机制的基本思想
将进程在整个运行过程中所需要的所有资源,一次性的全部分配给进程,待使用完毕后一起释放。要么全分配,要么一个都不分配 为Swait和Ssingnal操作
Swait(S1,S2,...Sn){ // P操作
while(TRUE){
if(Si>=1&&....&&Sn>=1){
for(i=1;i<=n;i++) Si--;
break;
}
else{
//将进程挂在第i个Si<=0的等待队列中,并把各个值恢复原值
}
}
}
信号量集
前面的操作都只能对临界资源的一个单位进行操作,当一次需要多对N个单位临界资源进程操作时,这显然是低效的,并且会增加死锁的概率。
由此形成了一般化的信号量集机制:
Swait(
S
1
S_1
S1,
t
1
t_1
t1,
d
1
d_1
d1,…,
S
n
S_n
Sn,
t
n
t_n
tn,
d
n
d_n
dn) P操作
Ssingnal(
S
1
S_1
S1,
t
1
t_1
t1,
d
1
d_1
d1,…,
S
n
S_n
Sn,
t
n
t_n
tn,
d
n
d_n
dn) V操作
其中,S表示信号量资源的数目,t是下限,表示
S
1
S_1
S1至少要有
t
1
t_1
t1那么多个,一次请求资源量减少
d
1
d_1
d1
特例:
- Swait(S,1,1)蜕化成一般的记录型信号量(S>1时)或互斥信号量(S=1时)
- Swait(S,1,0):相当一个可控开关,当S>1时,允许多个进程进入,当S=0后,将阻止任何进程进入特定区。
信号量的应用
1、利用信号量实现互斥
为该资源设置一个互斥信号量mutex,并设置其初始值为1.
main(){
//信号量mutex的初始值为1
int mutex=1;
cobegin //并发
Pa();
Pb();
Pc();
coend;
}
2、利用信号量P,V实现同步关系
同步是指协调并发进程中的执行先后关系,保证每个进程在合适的时机进行
基本思路:
- 定义有意义的信号量S,设置合理的初值
- 当某进程的运行条件不满足时,利用P操作暂停当前执行
- 当满足时,利用V操作释放信号量,从而使被暂停的进程得以被唤醒继续
例子:先执行
S
1
S_1
S1再执行
S
2
S_2
S2
进程P1和进程P2共享一个公用信号量signal
P{
S1;
V(signal);
}
V{
P(signal);
S2;
}
例子2:司机VS售票员
司机的进程
起步;
行驶;
停车;
售票员的进程
关门;
售票;
开门;
同步要求:
1、只有售票员关门后,司机才能起步
2、只有司机停车之后,售票员才可以开门
司机的进程
while(TRUE){
P(signal1);
起步;
行驶;
停车;
V(signal2);
}
售票员的进程
while(TRUE){
关门;
V(signal1);
售票;
P(signal2);
开门;
}
2.3.2 经典进程的同步问题
“生产者-消费者“问题
生产者在生产,消费者消费产品,在他们之间设置了一个具有N个缓冲区的环形缓冲池,生产者将生产的产品放入一个缓冲池中,消费者进程可以从缓冲区中取走产品进行消费:
需要满足的同步条件:
1、不能向满缓冲区存产品
2、不能向空缓冲区存产品
3、每个时刻仅允许一个生产者或一个消费者存取1个产品
分析:因为两个进程每次只允许一个进行存取操作,所以先用P,V互斥一下,还有,不能向满的存产品,不能向空的取产品,换而言之,当取时,需要看这个产品还有没有,当存的时候,需要判断是否已满。
- 设缓冲区的编号为0-N-1,in和out分别是生产者进程和消费者进程使用的指针,指向可用的缓冲区,初始值为0
- 设置三个信号量
- full 表示放有产品的缓冲区,初始值为0
- empty 表示可供使用的缓冲区,初始值为N
- mutex 互斥信号量,初始值为1,保证任何时候只有一个进程使用缓冲区
int in =0,out=0;
semaphor mutex=1,empty=N,full=0;
生产者进程Producer{
whlie(True){
P(empty);
P(mutex);
产品送往buffer(in);
in=(in+1)mod N;
//以N为模
V(mutex);
V(full);
}
}
消费者进程Consumer{
P(full);
P(mutex);
从buffer(out)中取出产品;
out=(out+1)mod N;
//以N为模
V(mutex);
V(empty);
}
利用AND信号量解决生产者消费者问题
用Swait(empty,mutex)来代替P(empty)和P(mutex);
用Ssignal(mutex,full) 来代替V(mutex)和V(full)
用Swait(full,mutex)代替P(full)和P(mutex)
用Ssignal(mutex,empty)代替V(mutex)和V(empty)
哲学家进餐问题
一张圆桌,五个碗五支筷子,五个哲学家的生活方式是交替进行思考用餐,平时,一个哲学家进行思考,饥饿时便试图取其左右两边的筷子,只有拿到两双筷子才能进餐,进餐完毕,放下筷子继续思考。
分析步骤:
临界资源:筷子
进程:五个进程(五个哲学家)
临界区代码:吃饭
利用记录型信号量
因为每一支筷子不可能同时两个哲学家拿到,所以筷子之间是互斥的。可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组:
semaphpore chopstick[5]={1,1,1,1,1} //各信号量初始为1
第i位哲学家的活动可以表示为:
使用AND信号量机制
为了防止死锁,需要同时拿起左边和右边的筷子才能吃饭(为了防止同时有5个人拿起左边的筷子造成死锁)
semaphpore chopstick[5]={1,1,1,1,1} //各信号量初始为1
do{
....
//思考
Swait(chopstick[(i+1)%5],chopstick[i]);
...
//进餐
Signal(chopstick[(i+1)%5],chopstick[i]);
}while(TRUE);
读者和写者问题
只读文件进程:reader进程
可写进程:writer进程
典型例子:航班预订系统的数据库
一个文件,很多进程都可以去读,但是写的时候只能有一个写(进程与进程之间在写进程时是互斥的)
要求:
- 允许多个进程进行读操作
- 不允许writer和reader进程同时操作
- 不允许多个writer进程同时操作
分析:信号量设置
- wmutex:互斥量,写者与其他读者/写者互斥。初值为1
- mutex:互斥量,互斥访问临界资源readcount。初值为1
- readcount:读者计数,初值为0
//读者reader进程
while(TRUE){
P(mutex);
readcount++; //readcount也是作为临界资源,防止reader进程修改破坏
if(readcount==1)
P(wmutex);
V(mutex);
P(wmutex); //有写者就不能有读者
执行读操作
P(mutex);
readcount--;
if(readcount==0)
V(wmutex);
V(mutex);
}
//写者writer进程
while(TRUE){
P(mutex);
P(wmutex); //因为一次只能有一个写者
执行写操作
V(mutex);
V(wmutex);
}
利用信号量集机制解决读者-写者问题
假设最多有RN个读者同时读取。为此,又引入了一个信号量L,初始值为RN,通过执行wait(L,1,1)来控制读者数目。
mx表示写资源
int RN;
semaphore L=RN,mx=1;
void reader(){
do{
Swait(L,1,1;mx,1,0);
....
进行读操作
Ssignal(L,1);
}while(TRUE)
}
void writer(){
do{
Swait(mx,1,1;L,RN,0);
...
进行写操作
Ssignal(mx,1);
}while(TRUE);
}
void main(){
cobegin
reader();
writer();
coend
}
练习:
桌子上有一盘子,最多可容纳5个水果,每次只能从中放入或取出一个水果。爸爸专门向盘子中放苹果(apple),妈妈专门向盘子中放橘子(orange),儿子专门等吃橘子,女儿专门等苹果。
分析:
盘子为互斥资源,可以放两种水果,empty初值为5
father放苹果前先看看有没有空间,若有则抢盘子,放apple。后向女儿发信号(V(apple))
mother放橘子前先看看有没有空间,如果有则抢盘子,放orange。后向儿子发信号(V(orange))
女儿先看看有无苹果,若有则抢盘子,取走苹果后将盘子释放(V(empty))
儿子先看看有无橘子,若有则抢盘子,取走苹果后将盘子释放(V(empty))
semaphore empty=5,mutex=1,apple=1,orange=0;
void father(){
P(empty); //先看看还有没有位置
P(mutex); //互斥
放苹果
V(mutex);
V(apple);
}
void mother(){
P(empty);
P(mutex);
放橘子
V(mutex);
V(orange);
}
void son(){
P(orange);
P(mutex);
取橘子
V(mutex);
V(empty);
}
void daughter(){
P(apple);
P(mutex);
取苹果
V(mutex);
V(empty);
}
void main(){
cobegin
father();
daughter();
mother();
son();
coend
}
打瞌睡的理发师问题
理发店里面有一个理发师一把理发座椅和几把座椅,等待理发的可以在上面等待
如果没有顾客到来,理发师就坐在理发椅上打瞌睡
有顾客到来,就唤醒理发师,如果顾客正在理发,该顾客可以坐在座椅上等待,如果座椅满座了,就到别处理发。
分析:
理发师进程和顾客各有一个进程
理发师进程:开始工作,先看看有没有顾客,没就打瞌睡,如果有就被唤醒,进行理发,等待人数-1。因为理发师不停理发,所以使用while循环
顾客进程:每个顾客一个进程,所以有多个进程。每个进程中,如果顾客数超过座位数,就结束进程。否则等待。因为顾客理完发就走,因此不需要while循环
引入三个信号量和一个控制变量
控制变量waiting用来记录等候理发的顾客数,初值为0
信号量customers用来记录等候理发的顾客数,并做阻塞理发师的进程
信号量barbers用来记录正在等候顾客的理发师数,并用做阻塞顾客的进程,初始为0
信号量mutex用于互斥,初值为1
fefine CHAIRS 5
typedef struct{
int value;
struct PCB*list
}semaphore;
semaphore customers=0;
semaphore babers=0;
semaphore mutex=1;
int waiting =0;
void baber(){
while(TRUE){
P(customer); //如果没有顾客,打瞌睡
P(mutex); //互斥进入临界区
waiting---;
V(barbers); //一个理发师进行理发
V(mutex); //退出临界区
cut_hair(); //理发
}
}
void customer(){
P(mutex); //互斥进入临界区
if(waiting<CHAIRS){
waiting--;
V(customers); //若有必要,唤醒理发师
V(mutex);
P(barbers);
get_haircut();
}else{
V(mutex); //店里满人
}
}
3.死锁
3.1 死锁的概述
死锁定义为一组相互竞争资源或进行通信的进程间的“永久”阻塞。
当一组进程中的每个进程都在等待某个事件(典型情况下是等待释放所请求的资源),而仅有这组进程中被阻塞的其他进程才可触发该事件时,就称这组进程发生了死锁。死锁是永久性。
区分:
mutex=0;
进程A:
....
P(mutex);
工作语句;
....
进程B:
...
while(1);
V(mutex);
....
上面两个进程没有发生死锁。虽然A不能进行,但是这并不是死锁。注意死锁的定义:一组相互竞争资源或进行通信的进程间的“永久”阻塞。上面的例子并不是因为竞争资源或进行通信的阻塞。
3.2 资源问题
在系统中有很多资源,其中能够引起死锁的主要是:需要采用互斥访问方法的、不可以被抢占的资源,即临界资源。
3.2.1 可重用性资源和消耗性资源
可重用性资源:可供用户重复使用多次的资源
- 每一个可重用性资源中的单元只能分配给一个进程使用,不允许多个进程共享
- 进程在使用可重用性资源时,需按照这样的顺序:
- 请求资源:如果请求失败,将被放进阻塞或循环队列等待
- 使用资源
- 释放资源
可消耗性资源(临时性资源)
它是在进程运行期间,由进程动态创建和消耗的,具有性质:
- 每一类可消耗性资源的单元数目在进程运行期间是可以不断变化的,有时他可以有许多,也可以为0
- 进程在运行过程中,可以不断创造可消耗性资源的单元,将他们放入该资源的缓冲区中,以增加该资源类的单元数目
- 进程在运行过过程中,可以请求若干个可消耗性资源,用于进程自己的消耗,不再将他们返还该资源类中
3.2.2 可抢占性资源和不可抢占性资源
可抢占性资源
某进程在获得这类资源后,该资源可以再其他进程或系统抢占。例如CPU内存属于可抢占性资源,这类资源不会引起死锁。
不可抢占性资源
一旦系统将资源分配给该进程后,就不能强行收回,只能在进程用完之后自行释放。典型例子:打印机
3.3 计算机系统中死锁形成原因
竞争不可抢占性资源引起死锁
例子:
P1
...
Open(f1,w);
Open(f2,w);
P2
...
Open(f2,w);
Open(f1,w);
利用资源分配图发现,形成环路,死锁状态:
竞争可消耗性资源引起死锁
例子:
会发生死锁:
不会发生死锁:
进程推进顺序不当引起死锁:对资源进程申请和释放的顺序是否合法也会影响死锁。
3.4 死锁定义,必要条件和处理方法
3.4.1 死锁的定义
如果一组进程中的每一个进程都在等待仅由该组进程中的其它进程才能引发的事件,那么该组进程是死锁的(Deadlock)。
3.4.2 产生死锁的必要条件
- 互斥条件:在一段时间内,某资源只能被一个进程使用。如果有其他进程申请使用该资源,需要等待。
- 请求和保持条件:进程必须至少保持一个资源,但如果又提出了新的资源申请,而该资源已经被其他进程占有,此时请求进程被阻塞,但对自己获得的资源不释放。
- 不可抢占条件:资源自己占有,使用完后再释放
- 循环等待条件:必然形成环路
3.4.3 处理死锁方法
3.4.3.1 预防死锁
主要是破坏产生死锁的后三个必要条件。
破坏请求和保持条件:当一个进程在请求资源时,他不能持有不可抢占资源,这通常通过下面两个协议保证:
- 第一种协议:所有进程开始运行之前,必须一次性申请其在整个运行过程中所需的全部资源。
- 若系统有足够的资源分配给该进程,便可以把其需要的所有资源分配给他。这样,在整个运行期间,就不会再提出资源要求,破坏了"请求"条件
- 如果系统缺少任何一个资源,都不会分配给进程,那么进程就会进入等待状态,在等待状态进程不会占用任何资源,破坏了保持条件
优点:简单,易行且安全
缺点:
-
资源被严重浪费,严重恶化了资源的利用率
-
使进程经常会发生饥饿现象:由于进程可能处于等待状态,如果资源长期被其他进程占用,而使等待该资源的进程迟迟不能开始运行
-
第二种协议:运行一个进程只获取初期所需的资源后便开始运行。进程运行过程中再逐步释放已分配的全部资源,然后再请求新的所需资源。(不要吃着碗里的看着锅里的,如果要想从锅里拿食物,必须先将碗里的吃完之后再拿)
破坏不可抢占条件
当一个保持了某些不可抢占性资源的进程,提出新的资源请求而不能得到满足时,他必须释放已经保持的所有资源,待以后需要时再重新申请。
破坏“循环等待”条件
一个能保证“循环等待”条件不成立的方法是,对系统所有资源类型进行线性排序,并赋予不同的序号。
在对所有资源类型进行线性排序后规定每个进程必须按序号递增的顺序请求资源
问题:
- 假如某进程已占有高序列号资源,想请求低序列号资源,怎么办?
- 释放所有高序列号资源,才能申请低序列号资源
- 申请同序列号资源?
- 同时申请
- 序号怎么定?
- 大多数进程对资源需要的先后顺序来定。
缺点:序号较为固定,会限制新类型设备的增加,有的作业顺序与众不同的话可能会浪费,按规定申请资源也会限制用户编程的自主性
3.4.3.2 避免死锁
在资源分配过程中,防止系统进入不安全状态,避免发生死锁。目前常用此方法。
系统状态:安全状态和不安全状态
当系统处于安全状态时,可避免发生死锁。当系统处于不安全状态时时,可能进入死锁。
安全状态&安全序列
例子:
假定系统中有三个进程P1、P2和P3,共有12台磁带机。
假设在T0时刻状态如下表所示:
进程 | 最大需求 | 已分配 |
---|---|---|
P1 | 10 | 5 |
P2 | 4 | 2 |
P3 | 9 | 2 |
分析:因为已经分配了9台,总共有12台,还剩下:12-9=3台,那么可以先分配给P2,P2执行完释放资源:4,剩下资源5,然后再分配给P1,释放后剩下10,再分配给P3.
因此,T0时刻是安全的,因为此时存在一个安全序列(P1,P2,P3)。
由安全状态到不安全状态的转换
如果不按照安全序列分配资源,则系统可能会由安全状态进入到不安全状态。
利用银行家算法避免死锁
算法前提条件:新进程要求的资源数目不应超过系统所拥有的资源总量。
算法基本思想:当进程请求一组资源时,系统首先确定是否有足够的资源分配给该进程。若有,再进一步计算在将这些资源分配给进程后,是否会使系统处于不安全状态。如果不会,才将资源分配给它,否则让进程等待。
银行家算法中的数据结构:
- 可利用资源向量Available:含m个数组。Available[j]=K,表示系统中Rj类资源有K个
- 最大需求矩阵Max:n×m的矩阵。Max[i,j]=K,表示进程i需要j类资源的最大数目为K个。
- 分配矩阵Allocation :n×m的矩阵。 Allocation[i,j]=K,表示进程i已分配j类资源的数目为K个。
- 需求矩阵Need :n×m的矩阵。Need[i,j]=K,表示进程i还需要j类资源K个才能完成任务。
- 关系:Need[i,j]=Max[i,j]-Allocation[i,j]
算法:
设
R
e
q
u
e
s
t
i
Request_i
Requesti是进程
P
i
P_i
Pi的请求向量,如果
R
e
q
u
e
s
t
i
[
j
]
=
K
Request_i[j]=K
Requesti[j]=K,表示进程
P
i
P_i
Pi需要K个
R
j
R_j
Rj类型的资源。当
P
i
P_i
Pi发出资源请求后,系统按下述步骤进行检查:
(1) 如果
R
e
q
u
e
s
t
i
Request_i
Requesti≤
N
e
e
d
[
i
,
j
]
Need[i, j]
Need[i,j],便转向步骤(2); 否则认为出错,因为它所需要的资源数已超过它所宣布的最大值。
(2) 如果
R
e
q
u
e
s
t
i
Request_i
Requesti ≤Available[j],便转向步骤(3); 否则,表示尚无足够资源,
P
i
P_i
Pi须等待。
(3) 系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值:
- Available[j] = Available[j] - Requesti[j];
- Allocation[i, j] = Allocation[i, j] + Requesti[j];
- Need[i, j] = Need[i, j] - Requesti[j];
(4) 系统执行安全性算法 (设置faluse and true),检查此次资源分配后系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。
3.4.3.3 检测和解除死锁
死锁的检测
资源分配图:系统死锁,可用资源分配图来描述
该图是由节点N和边E组成:N={
P
1
P_1
P1,
P
2
P_2
P2,…,
P
n
P_n
Pn}∪{
R
1
R_1
R1,
R
2
R_2
R2,…,
R
n
R_n
Rn}。e={
P
i
P_i
Pi,
R
j
R_j
Rj}是资源请求边,由进程
P
i
P_i
Pi指向资源
R
j
R_j
Rj,他表示进程
P
i
P_i
Pi请求一个单位的
R
j
R_j
Rj资源。E = {
R
j
R_j
Rj,
P
i
P_i
Pi}是资源分配边,由资源
R
j
R_j
Rj指向进程
P
i
P_i
Pi,它表示把一个单位的资源
R
j
R_j
Rj分配给进程
P
i
P_i
Pi。
死锁定理
在资源分配图中,找出一个既不阻塞又非独立的进程结点
P
i
P_i
Pi,消去
P
i
P_i
Pi的资源请求边和资源分配边使之变成一个孤立节点,其他的节点类似:
在进行一系列的简化后,若能消去图中所有的边,使所有的进程结点都成为孤立结点,则称该图是可完全简化的;若不能通过任何过程使该图完全简化,则称该图是不可完全简化的。
所有的简化顺序都将得到相同的不可简化图。
S为死锁状态的充分条件是:当且仅当S状态的资源分配图是不可完全简化的。
死锁的解除
- 抢占资源:从一个或多个进程中抢占足够的资源,分配给死锁的进程,以解除死锁状态。
- 通过回退执行实现恢复
- 终止进程
- 终止所有死锁进程
- 逐个终止进程:按照某种顺序,逐个地终止进程,直至有足够的资源,以打破循环等待,把系统从死锁状态解脱出来为止
3.4.3.4 对比三种方法
3.5饥饿和活锁的概念
-
饥饿的概念
某些进程所需的资源总是被别的进程占有或抢占。——饥饿或饿死
当等待时间给进程的推进和响应带来明显的影响时,就称发生了进程饥饿。当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时,称该进程被饿死。 -
活锁的概念
活锁是指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,耗尽CPU资源,使系统效能下降。活锁有可能自行解开。
例子:过桥问题,两个人都很谦让让对方先过
4. 调度
4.1 调度的概念
引例:
当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是“调度”研究的问题。
在多道程序系统中,进程的数量往往是多于处理机个数的,这样不可能同时并行的处理各个进程。
处理机调度,就是从就绪队列中按照一定的算法选择一个进程并将处理机分配给他运行,以实现进程的并发执行
4.2 调度的三个层次
- 高级调度(作业调度)
- 中级调度(内存调度)
- 低级调度(进程调度)
4.2.1 高级调度
由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序
高级调度(作业调度):按一定原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。
高级调度是辅存(外存)与内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业结束才调出。
4.2.2 中级调度
引入虚拟存储技术之后,可将暂时不能运行的进程调置外存,等他重新具备运行条件且内存又稍有空闲时,再重新调入内存。
这么做的目的是为了提高内存利用率和系统吞吐量。
暂时调到外存等待的进程为挂起状态。注意:PCB并不会一起调到外存,而是会常驻内存。PCB中会记录进程数据在外存中存放的位置,进程状态信息等,操作系统通过内存中的PCB来保持对各个进程的监控、管理。被挂起的进程PCB会被放到挂起队列中。
中级调度(内存调度),就是要决定将哪个处于挂起状态的进程重新调入内存。一个进程可能会被多次调入,调出内存,因此中级调度发生频率要比高级调度更高。
4.2.3 低级调度
低级调度(进程调度),主要任务是按照某种方法和策略从就绪队列中取一个进程,将处理机分配给他。
进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。
进程调度的频率很高。
4.2.4 三中调度的对比和总结
4.3 调度的时机、切换与过程,调度方式
4.3.1 进程调度的时机
4.3.2 进程调度的方式
- 非剥夺调度方式,又称非抢占方式
- 只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态
- 实现简单,系统开销小但无法及时处理紧急任务,适合早期的批处理系统
- 剥夺调度方式,又称抢占方式
- 当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。
- 可以优先处理更紧急的进程,也可实现让各进程按时间片轮流执行的功能(通过时钟中断),适合分时操作系统,实时操作系统。
4.3.3 进程的切换与过程
“狭义的进程调度”与“进程切换”的区别
狭义的进程调度指的是从就绪队列中选出一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程,后一种情况就需要进程切换)
广义的进程调度包含了选择一个进程和进程切换两个步骤。
进程切换的过程主要包括:
- 对原来运行进程各种数据的保存
- 对新的进程各种数据的恢复
(如:程序计数器,程序状态字,各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)
注意:进程切换是有代价的,因此如果过于频繁的进行进程调度,切换,必然会使系统的效率降低,使系统大部分时间都花在进程切换上,而真正用于执行时间减少。
4.4调度算法的评价指标
4.4.1 CPU利用率
由于CPU昂贵,因此人们希望让CPU尽可能的工作。
CPU利用率:指CPU“忙碌”的时间占总时间的比例
C
P
U
利
用
率
=
忙
碌
的
时
间
总
时
间
CPU利用率=\frac{忙碌的时间}{总时间}
CPU利用率=总时间忙碌的时间
4.4.2 系统吞吐量
希望用尽可能少的时间处理尽可能多的作业
系统吞吐量:单位时间内完成作业的数量
系
统
吞
吐
量
=
总
共
完
成
了
多
少
作
业
总
共
花
了
多
少
时
间
系统吞吐量=\frac{总共完成了多少作业}{总共花了多少时间}
系统吞吐量=总共花了多少时间总共完成了多少作业
4.4.3 周转时间
用户总是关心自己的作业从提交到完成花了多少时间
周转时间,是指作业被提交给系统开始,到作业完成为止的这段时间间隔。
包括四个部分:作业在外存后备队列上等待作业调度(高级调度)的时间,进程在就绪队列上等待进程调度(低级调度)的时间,进程在CPU上执行的时间,进程等待I/O操作完成的时间。后三项在一个作业的整个处理过程中,可能发生多次。
(
作
业
)
周
转
时
间
=
作
业
完
成
时
间
−
作
业
提
交
时
间
(作业)周转时间=作业完成时间-作业提交时间
(作业)周转时间=作业完成时间−作业提交时间
平
均
周
转
时
间
=
各
作
业
周
转
时
间
之
和
作
业
平均周转时间=\frac{各作业周转时间之和}{作业}
平均周转时间=作业各作业周转时间之和
带
权
周
转
时
间
=
作
业
周
转
时
间
作
业
实
际
运
行
的
时
间
=
作
业
完
成
时
间
−
作
业
提
交
时
间
作
业
实
际
运
行
的
时
间
带权周转时间=\frac{作业周转时间}{作业实际运行的时间}=\frac{作业完成时间-作业提交时间}{作业实际运行的时间}
带权周转时间=作业实际运行的时间作业周转时间=作业实际运行的时间作业完成时间−作业提交时间
平
均
带
权
周
转
时
间
=
各
作
业
带
权
周
转
时
间
之
和
作
业
数
平均带权周转时间=\frac{各作业带权周转时间之和}{作业数}
平均带权周转时间=作业数各作业带权周转时间之和
带权周转时间必然>=1
带权周转时间与周转时间都是越小越好
4.4.4 等待时间
用户自己的作业尽可能少的等待处理机
等待时间,指进程/作业处于等待处理机状态时间之和,等待时间越长,用户满意度越低
对于进程来说,等待时间就是指进程建立后等待被服务的时间之和,在等待I/O完成期间其实进程也是被服务的,所以不计入等待时间。
对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列的等待时间。
一个作业总共需要被CPU服务多久,被I/O设备服务多久一般是确定不变的,因此调度算法其实只会影响作业/进程等待时间。
4.4.5 响应时间
对用户来说,希望自己提交的请求尽早被系统服务、响应。
响应时间,指从用户提交请求到首次产生响应所用的时间。
4.5 调度算法
- 先来先服务(FCFS first come first serve)
- 最短作业优先(SJF)
- 最高响应比优先(HRRN)
4.5.1 先来先服务
4.5.2 短作业优先
注意:
- 如果未特殊说明,所提到的“短作业/进程优先算法”默认是非抢占式的。
- 很多书上会说“SJF调度算法的平均等待时间,平均周转时间最少”
- 严格上来说,这个表述是错误的。最短剩余时间优先算法得到的平均等待时间和平均周转时间更少
- 在所有进程同时可运行时,采用SJF调度算法的平均等待时间,平均周转时间最少
- 在所有进程几乎同时到达时,采用SJF调度算法的平均等待时间,平均周转时间最少
- 抢占式的短作业/进程优先算法(最短剩余时间优先,SRNT算法)的平均等待时间,平均周转时间最少
4.5.3 高响应比优先
4.6 调度算法2
- 时间片轮转(RR)
- 优先级调度
- 多级反馈队列
4.6.1 时间片轮转(RR)
注意:
- 如果时间片太大,使得每个进程都可在一个时间片就能完成,则时间片轮转调度算法退化为先来先服务调度算法,并且会增大进程响应时间。因此时间片不能太大。
- 进程调度,切换是有时间代价的(保存,恢复运行环境),因此如果时间片太小,会导致进程切换过于频繁,系统会花大量的时间来处理进程切换,从而导致实际用于进程执行的时间比例减少。可见时间片也不能太小。
4.6.2 优先级调度算法
注意:
- 就绪队列未必只有一个,可以按照不同优先级来组织。另外,也可以把优先级高的进程排在更靠近队头的位置
- 优先级可分为静态优先级和动态优先级
- 静态优先级:创建进程时确定,之后一直不变
- 动态优先级:创建进程时有一个初始值,之后会根据情况动态地调整优先级
- 如何设置优先级
- 系统进程优先级高于用户进程
- 前台进程优先级高于后台进程
- 操作系统更偏好I/O型进程(或称I/O繁忙型进程)
- 与I/O型进程相对的是计算机型进程(或称CPU繁忙型进程)
- 动态优先级中如何调整
- 追求公平,提升资源利用率的角度
- 如果某进程在就绪队列中等待了很长时间,则可以适当提升其优先级
- 如果某进程占用处理机运行了很长时间,可以适当降低其优先级
- 如发现一个进程频繁的进行I/O操作,则可以适当提升其优先级
4.6.3 多级反馈队列调度算法
设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
新进程到达时先进入第1级队列,按FCFS原则排队等待被分配时间片。若用完时间片进程还未结束,则进程进入下一级队列队尾。如果此时已经在最下级的队列,则重新放回最下级队列队尾。
只有第k级队列为空时,才会为k+1级队头的进程分配时间片,被抢占处理机的进程重新放回原队列队尾。
上面这三种算法,更适用于交互式系统。