操作系统复习(持续更新)
操作系统复习
进程
概念
动态的,程序的一次执行过程,同一个程序多次执行会对应多个进程
当进程被创建时,操作系统会为该进程分配一个唯一的不重复的PID号
进程的组成
进程控制块PCB
操作系统会记录PID、进程所属用户、分配资源的情况、进程的运行情况,保存在PCB(进程控制块)中。
程序段和数据段
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
PCB是进程存在的唯一标志!
进程的特征
- 动态性:是程序的一次执行过程,是动态产生和消失的
- 并发性:各进程可以并发执行
- 独立性:进程能够独立运行,独立获得资源,独立接收调度
- 异步性:各进程按各自独立、不可预知的速度向前推进
- 结构性:进程由PCB、程序段、数据段组成
进程的状态与转换
进程的状态
- 创建态:进程被创建时
- 就绪态:进程创建完毕,CPU忙
- 运行态:进程正在CPU上运行
- 阻塞态:请求等待某个时间的发生(等待某种系统资源的分配、等待其他进程的响应)
进程的转换
运行态\(\rightarrow\)阻塞态:是进程自身做出的主动行为
阻塞态\(\rightarrow\)就绪态:是一种被动行为
不能从阻塞态直接转换为运行态,也不能直接从就绪态直接转换为阻塞态
运行态\(\rightarrow\)就绪态:时间片耗尽
进程的组织
链接方式(大部分)
使用队列(链表)
索引方式
进程控制
创建新进程、撤销已有进程、实现进程转换等,就是实现进程的状态转换。
进程控制的实现
用原语实现,具有原子性,一气呵成,期间不允许被中断。
可以用关中断指令和开中断指令这两个特权指令实现原子性。
- 关中断指令:CPU执行了关中断指令之后,就不再例行检查中断信号
- 关中断和开中断之间的指令序列是不可中断的,这样就实现了原子性
进程控制相关的原语
- 进程创建原语:申请空白PCB,将调用者参数添加进PCB,将新进程设置为就绪态
- 进程阻塞原语:将进程由运行态变为阻塞态,同时将进程插入等待队列,修改PCB中响应的表项
- 进程唤醒原语:将进程从阻塞态变成就绪态,将进程从阻塞队列中移出,加入就绪队列,修改PCB中相应的表项
- 进程终止原语:回收进程资源,消去资源描述块和PCB
进程创建原语
-
申请空白PCB
-
为新进程分配所需资源
-
初始化PCB
-
将PCB插入就绪队列( 创建态\(\rightarrow\)就绪态)
引起进程创建的事件
- 用户登录
- 作业调度
- 提供服务
- 应用请求
进程撤销原语
- 从PCB种找到终止进程的PCB
- 若程序正在运行,立即剥夺CPU,将CPU分配给其他进程
- 终止所有子进程(树形结构)
- 将该进程拥有的资源归还给父进程或者操作系统
- 删除PCB
引起进程终止的事件
- 正常结束(
exit
系统调用) - 异常结束
- 外界干预(手动kill)
进程的阻塞和唤醒
进程的切换
进程通信
共享存储
多个进程间存在共享存储区
可以通过增加页表项和段表项将同一片共享内存区映射到各个进程的地址空间内
各个进程对共享存储的访问是互斥的(进程自己负责实现互斥)
-
基于存储区的共享:灵活性好,速度快,是高级通信方式
-
基于数据结构的共享:灵活性差,速度慢,低级通信方式
消息传递
进程间的数据交换以格式化的消息为单位。
- 直接通信:要指明接收进程的ID,发送原语
send(Q,msg)
放入内核中Q的消息队列,接收原语recv(P,&msg)
,从消息队列复制消息到进程Q - 间接通信:
send(A,msg)
发送消息体到A信箱,recv(A,&msg)
从A信箱接收消息
管道通信
在内存中开辟一个大小固定的内存缓冲区,数据先进先出(FIFO)先写入的先读出
管道只能采用半双工通信
各进程要互斥的访问管道(由操作系统实现)
当多个进程读同一个管道时,会发生错乱,解决方案:一个管道允许多个写进程,一个读进程;或者多个写进程,多个读进程,但读进程会轮流读取。
线程
基本概念
引入线程,来增加并发度
线程是一个基本的CPU执行单元,也是程序执行流的最小单位
- 进程是资源分配的基本单位,线程是调度的基本单位
- 如果是同一个进程内的不同线程,则不需要切换进程环境,系统开销小
线程的属性
- 线程是处理机调度的单位
- 不同的线程可以占用不同的CPU
- 每个线程都有一个线程ID,线程控制块TCB
- 也有就绪、阻塞、运行三种状态
- 同一进程的不同线程共享进程的资源
- 同一进程中的线程间通信不需要系统干预
- 同一进程中的线程切换,不会影响进程切换,且开销很小
线程的实现方式
用户级线程
线程的管理是由应用程序完成的,线程切换不需要从用户态转换为内核态,操作系统不能意识到用户级线程的存在
- 优点:线程管理的系统开销小,效率高
- 缺点:当用户级线程被阻塞时,整个进程都会被阻塞,并发度不高,线程不可以在多核处理机上使用
内核级线程
线程的管理由操作系统完成,线程切换需要变态,操作系统可以意识到内核级线程的存在
- 优点:线程的并发性强
- 缺点:线程管理的成本高,开销大
多线程模型
-
一对一模型:一个用户级线程映射到一个内核级线程
- 优点:当线程被阻塞,其他线程还能继续执行,并发能力强
- 缺点:线程管理的成本高
-
多对一模型:
- 优点:线程管理的系统开销小,效率高
- 缺点:当用户级线程被阻塞时,整个进程都会被阻塞,并发度不高,线程不可以在多核处理机上使用
-
多对多模型:
- 克服多对一模型并发度不高的缺点
- 克服了一对一模型系统开销太大的缺点
线程的状态与转换
线程的组织与控制
TCB
- 线程标识符TID
- 程序计数器PC(线程目前执行到哪)
- 其他寄存器(线程运行的中间结果)
- 堆栈指针(保存函数调用信息、局部变量等)
- 线程运行状态(运行、阻塞、就绪)
- 优先级(线程调度、资源分配的参考)
线程表(Thread Table)
处理机调度
高级调度(作业调度)
按照一定的规则从外存的作业后备队列里挑选一个作业调入内存,并创建进程。
每个作业只调入一次,调出一次。作业调入时会建立PCB,调出时撤销PCB
低级调度(进程调度)
进程调度是操作系统中最基本的调度,进程调度的频率很高。
选择一个进程为其分配处理机
中级调度(内存调度)
将某些进程的数据调出外存,暂时调到外存等待的进程状态为挂起状态。
从挂起队列里选择合适的进程将其数据调回内存
被挂起的PCB会被组织成挂起队列。
进程调度的时机
需要进行进程调度与切换的情况
- 当前运行的进程主动放弃处理机
- 进程正常终止
- 运行中发生异常而终止
- 进程主动请求阻塞(等待I/O)
- 当前运行的进程被动放弃处理机
- 分给进程的时间片用完
- 由更紧急的事需要处理(如I/O中断)
- 有更高优先级的进程进入就绪队列
不能进行进程调度与切换的情况
- 处理中断的过程中
- 进程在操作系统内核程序临界区中
- 在原子操作中(原语)
进程调度的方式
- 非抢占式方式:只允许进程主动放弃处理机,适用于批处理系统
- 抢占方式:可以优先处理更紧急的进程,适用于分时操作系统,实时操作系统
调度算法的评价指标
-
CPU利用率=\(\frac{忙碌的时间}{总时间}\)
-
系统吞吐量:单位时间内完成作业的数量=\(\frac{总共完成了多少道作业}{总共花费了多长时间}\)
-
周转时间:从作业被提交到作业完成花费的时间
- 平均周转时间=\(\frac{各作业周转时间之和}{作业数}\)
- 带权周转时间=\(\frac{周转时间}{运行时间}\)
-
等待时间:指进程处于等待处理机状态时间之和\(=等待时间-运行时间\)
- 平均等待时间
-
响应时间:用户提交请求到响应的时间
调度算法
先来先服务(FCFS,First Come First Serve)
短作业优先
高响应比优先算法
这三种调度算法常用于批处理系统,交互性很差
时间片轮转调度算法
时间片太大:时间片轮转调度算法会退化成先来先服务调度算法,会增加进程响应时间。
时间片太小:会导致进程切换太频繁,进程切换也会消耗时间,会导致实际用于进程执行的时间比例减小。
优先级调度算法
多级反馈队列调度算法
这三种算法适用于交互式系统。
进程同步和互斥
一个时间段内只允许一个进程使用的资源称为临界资源
do{
entry section; //进入区
critical section; //临界区
exit section; //退出区
remainder section; //剩余区
}while(ture)
进入区:负责检查是否可以进入临界区
临界区:访问临界资源的代码段
退出区:解除正在访问临界资源的标志
剩余区:做其他处理
- 进程互斥访问的原则:
- 空闲让进,临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
- 忙则等待,已有进程进入临界区,其他试图进入临界区的进程必须等待
- 有限等待,对请求访问的进程,保证能在有限时间内进入临界区
- 让权等待,当进程不能进入临界区时,应立即释放处理机,防止进程忙等待
进程互斥的软件实现方法
单标记法
算法思想:每个进程进入临界区的权限只能被另一个进程赋予
P0进程:
while(turn!=0);
critical section;
turn=1;
remainder section;
P1进程:
while(turn!=1);
critical section;
turn=0;
remainder section;
双标记先检查法
bool flag[2];//表示进入临界区的意愿
flag[0]=false;
flag[1]=false;
P0进程:
while(flag[1]);//先检查P1进程的意愿
flag[0]=true;//标记P0想进入临界区
critical section;
flag[0]=false;
remainder section;
P1进程:
while(flag[0]);
flag[1]=true;
critical section;
flag[0]=false;
remainder section;
违反了忙则等待原则,检查和上锁不是一气呵成的,检查后,上锁前可能发生进程切换。
双标记后检查法
P0进程:
flag[0]=true;
while(flag[1]);
critical section;
flag[0]=false;
remainder section;
P1进程:
flag[1]=true;
while(flag[0]);
critical section;
flag[1]=false;
remainder section;
违背了空闲让进和有限等待原则,各进程都无法进入,产生饥饿现象。
Peterson算法
P0进程:
flag[0]=true;//先表示P0想进入临界区
turn=1;//表示可以优先让P1进入临界区
while(flag[1]&&turn==1);
critical section;
flag[0]=false;
remainder section;
P1进程:
flag[1]=true;
turn=0;
while(flag[0]&&turn==0);
critical section;
flag[1]=false;
remainder secion;
遵循了空闲让进,忙则等待,优先等待这三个原则,但没有遵循让权等待的原则。
进程互斥的硬件实现方法
中断屏蔽方法
利用开/关中断指令实现
关中断 //不允许当前进程被中断
临界区
开中断
不适用于多处理机环境
TestAndSet指令
简称TS指令
//lock表示当前临界区是否被加锁
bool TestAndSet(bool *lock){
bool old;
old=*lock;//old用来存放lock原来的值
*lock=true;//无论之前是否加锁,都将lock设为true
return old;
}
while(TestAndSet(&lock));//上锁并检查
critical section;
lock=false;
remainder section;
检查上锁一气呵成,适用于多处理机环境,不满足让权等待
Swap指令
Swap(bool *a,bool *b){
bool temp;
temp=*a;
*a=*b;
*b=temp;
}
bool old=true;
while(old==true)
Swap(&lock,&old);
critical section;
lock=false;
remainder section;
不满足让权等待
锁
互斥锁(mutex lock)
需要连续循环忙等待的互斥锁,可称为自旋锁
信号量
wait(S)
原语和signal(S)
原语,简称为P、V操作
int S=1;//初始化信号量S,表示当前系统中可用的资源数
void wait(int S){
while(S<=0);//如果资源数不够,就循环等待
S=S-1;//如果够,占用一个资源
}
void signal(int S){
S=S+1;//使用完之后,在退出区释放资源
}
wait(S);//进入区,申请资源
使用资源 //临界区。访问资源
signal(S);//退出区。释放资源
记录型信号量
typedef struct{
int value; //剩余资源数
struct process *L; //等待队列
}semaphore;
void wait(semaphore S){
S.value--;
if (S.value<0){
block(S.L);//如果资源数不够,就时进程从运行态变为阻塞态
}
}
void signal(semaphore S){
S.value++;
if(S.value<=0){//说明仍有进程等待分配资源
wakeup(S.L);//从等待队列中唤醒进程
}
}
遵循让权等待原则,资源数不够时,就放入阻塞队列。
信号量机制
信号量=资源的剩余数量,信号量的值如果小于0,说明此时有进程在等待这种资源
信号量机制实现进程互斥
semaphore mutex=1;
P1(){
P(mutex);//申请资源
critical section;
V(mutex);//释放资源
}
P2(){
P(mutex);
critical section;
V(mutex);
}
mutex
:进入临界区的名额
信号量机制实现进程同步
semaphore S=0;
P1(){
code1;
code2;
V(S);//释放资源,将进程从阻塞队列中拿出
code3;
}
P2(){
P(S);//加入阻塞队列
code4;
code5;
code6;
}
\(code1\rightarrow code2\rightarrow code4\)
信号量机制实现前驱关系
生产者消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区去出一个产品并使用,生产者消费者共享一个大小为n的缓冲区。
缓冲区没满->生产者生产
缓冲区没空->消费者消费
缓冲区是临界资源,必须互斥访问
semaphore mutex=1;//互斥信号量
semaphore empty=n;//同步信号量,表示空闲缓冲区的数量
semaphore full=0;//同步信号量,表示非空缓冲区的数量
producer(){
while(1){
生产一个产品;//尽量不要放入临界区
P(empty);//消耗一个空闲缓冲区
P(mutex);
把产品放入缓冲区;
V(mutex);
V(full);//增加一个非空缓冲区
}
}
consumer(){
while(1){
P(full);//减少一个非空缓冲区
P(mutex);
从缓冲区取出一个产品;
V(mutex);
V(empty);//增加一个空闲缓冲区
使用产品;//尽量不要放入临界区
}
}
实现互斥的信号量是在同一个进程中进行的PV操作
实现同步的信号量是在两个进程中实现的PV操作
实现互斥的P操作一定要在实现同步的P操作之后,会发生死锁
V操作不会导致进程阻塞,因此两个V操作顺序可以交换
读者写者问题
允许多个进程进行读操作,只允许一个进程进行写操作
在写者进行写操作时不允许其他写者或读者工作
semaphore rw=1;
int count = 0;//记录有几个进程在访问文件
semaphore mutex=1;//保证对count的互斥访问
writer(){
while(1){
P(rw);
writing;
V(rw);
}
}
reader(){
while(1){
P(mutex);
if(count==0)
P(rw);
count++;
V(mutex);
reading;
count--;
if(count==0)
V(rw);
}
}
读进程是优先的,写进程可能一直阻塞等待,因为只有count=0
的时候才能执行V(rw)
。
再设置一个semaphore w=1;
writer(){
while(1){
P(w);
P(rw);
writing;
V(rw);
V(w);
}
}
reader(){
while(1){
P(w);
P(mutex);
if(count==0)
P(rw);
count++;
V(mutex);
V(w);
reading;
count--;
if(count==0)
V(rw);
}
}