进程同步问题
Peterson算法
var
flagi, flagj: boolean := false, false;
turn: integer;
begin
repeat
flagi := true;
turn := j; // 由于turn的取值只有一个,所以满足空闲让进
while (flagj and turn == j)
do no_op; //不满足让权等待
临界区
flagi := false;
until false;
end.
begin
repeat
flagj := true;
turn := i;
while (flagi and turn == i)
do no_op;
临界区
flagj := false;
until false;
end.
硬件方法解决
硬件解决的两种指令方案均为原子操作,TS指令与Swap指令无法中断
Test-and-Set
function TS(var lock:boolean):boolean;
begin
TS:=lock;
lock:=true;
end;
var
lock:boolean:=false;
begin
repeat
while TS(lock) do skip; //无法满足让权等待
临界区
lock:=false;
until false;
end.
Swap
procedure Swap(var a,b:boolean);
var
temp:boolean;
begin
temp:=a;
a:=b;
b:=temp;
end;
var
lock:boolean:=false;
begin
repeat
key:=true;
repeat
Swap(lock,key); //不满足让权等待
until key=false;
临界区
lock:=false;
until false;
end.
信号量
记录型信号量机制解决了让权等待
记录型信号量:
- 记录型信号量数据结构
type semaphore=record
value:integer;
queue:queue of process;
end;
value>=0时 表示系统中的可用资源数量
value<0时 表示系统中阻塞的进程数量
- P原语与V原语
// 占用资源P原语的功能可以用wait函数来描述
procedure wait(s)
var
s:semaphore;
begin
s.value:=s.value-1;
if s.value<0 then block(s.queue);
end;
// 释放资源V原语的功能可以用signal函数来描述
procedure signal(s)
var
s:semaphore;
begin
s.value:=s.value+1;
if s.value<=0 then wakeup(s.queue);
end;
- 解决互斥访问临界资源的样例
不论p0 p1的执行顺序,count结果总是一样的
var
mutex:semaphore;
count:integer;
procedure p0(mutex)
var
r0:integer;
begin
P(mutex);
r0:=count;
r0:=r0+1;
count:=r0;
V(mutex);
end;
procedure p1(mutex)
var
r1:integer;
begin
P(mutex)
r1:=count;
r1:=r1+1;
count:=r1;
V(mutex);
end;
- 解决同步问题
如果需要先执行p0,p0执行完毕后再执行p1,需要设置初始的mutex.value=0,然后在p0结束前释放资源,在p1执行前获得资源
procedure p0(mutex)
begin
...
V(mutex);
end;
procedure p1(mutex)
begin
P(mutex);
...
end;
AND型信号量集:
对于多个进程要共享两个以上的资源,记录型信号量可能导致死锁
- Swait操作
procedure Swait(s:array[0..n] of semaphore)
begin
if (s[1].value>=1 and ... s[i].value>=1 ... and s[n].value>=1) then
for i:=1 to n do
s[i].value:=s[i].value-1
else
blockProcessAndResetPC(s[firstless]);
//阻塞进程并重新设置计数器 将进程挂入第一个无法满足的资源的阻塞队列中
end;
将Swait操作进一步精细化
procedure Swait(s:array[0..n] of semaphore)
var
zSbG:boolean:=false;
i:integer;
begin
for i:=1 to n do
if (s[i].value<1)
begin
zSbG:=true;
break;
end;
if (zSbG)
begin
ResetPC(); //进程被唤醒时重新执行Swait
block(s[i].queue) //将进程挂入第一个无法满足的资源的阻塞队列
end
else
for i:=1 to n do
s[i].value:=s[i].value-1;
end;
- Ssignal操作
procedure Ssignal(s:array[0..n] of semaphore)
var
i:integer;
begin
for i:=1 to n do
begin
s[i].value:=s[i].value+1;
wakeup(s[i].queue);
end;
end;
一般信号量集
- Swait操作
procedure Swait(s:array[0..n] of semaphore,t,d:array[0..n] of integer)
var
i:integer;
begin
if (s[1].value>=t[1] and ... s[i].value>=t[i] ... and s[n].value>=t[n]) then
for i:=1 to n do
s[i].value:=s[i].value-d[i]
else
blockProcessAndResetPC(s[firstless]);
end;
将Swait操作进一步精细化
procedure Swait(s:array[0..n] of semaphore,t,d:array[0..n] of integer)
var
zSbG:boolean:=false;
i:integer;
begin
for i:=1 to n do
if (s[i].value<t[i])
begin
zSbG:=true;
break;
end;
if (zSbG)
begin
ResetPC(); //进程被唤醒时重新执行Swait
block(s[i].queue) //将进程挂入第一个无法满足的资源的阻塞队列
end
else
for i:=1 to n do
s[i].value:=s[i].value-d[i];
end;
- Ssignal操作
procedure Ssignal(s:array[0..n] of semaphore,d:array[0..n] of integer)
var
i:integer;
begin
for i:=1 to n do
begin
s[i].value:=s[i].value+d[i];
wakeup(s[i].queue);
{这里并不准确,
因为当前阻塞的进程所需要的资源数可能要比d[i]所释放的资源数量大,
所以需要特别设计}
end;
end;
处理机调度
调度
- 高级调度
从外存中读取若干道作业装入内存,并为其创建进程控制块
状态:新状态 终止状态
算法:作业量确定->多道程序度 作业量选择->调度算法 - 中级调度
管理内存空间,处理挂起与唤醒
状态:挂起状态(就绪与阻塞) - 低级调度
用来决定就绪队列中的哪个进程将获得处理机,由分派程序把处理机分配给该进程
状态:就绪状态 阻塞状态 运行状态
分类:非抢占方式 抢占方式
调度算法
先来先服务算法FCFS
特点:
- 不会被中断,按照程序的顺序进行
- 对服务时间长的进程有利,对服务时间短的进程不利
- 周转时间=完成时刻-到达时刻
- 带权周转时间=(完成时刻-到达时刻)/服务时间
- 对CPU繁忙型作业有利,对I/O繁忙型作业不利
- 由于等待I/O进程将被频繁插入到阻塞队列末尾而耗费大量等待时间
短作业优先调度算法SRTF
特点:
- 选取服务时间最短的若干道作业进入内存
- 能有效降低作业平均等待时间并提高系统吞吐量
- 不利于服务时间长的作业
- 完全未考虑作业的紧迫程度
- 无法准确估计作业服务时间
进程运行过程分析:
- 交互式作业:在I/O操作之间仅运行很短时间
- 批处理作业:在I/O操作之间可能运行很长时间
进程调度考量:
- 基于进程下一轮的处理器集中使用时间(下次执行I/O之前的时间量,即CPU burst)的长短确定优先级
- 根据进程以往执行情况推测CPU burst
CPU burst推算公式:E[i] = (θ * T[i-1]) + ((1-θ) * E[i-1]) //E:预测值 T:实际值 θ:权重
高优先权优先调度算法FPF
算法分类:
- 非抢占式
- 抢占式
优先权类型:
- 静态优先权
- 动态优先权
高响应比优先调度算法
优先权(响应比)计算公式:(等待时间+要求服务时间)/要求服务时间
特点:
- 兼顾长短作业
- 计算响应比加大调度进程的系统开销
时间片轮转调度算法
按照先来先服务原则,为队列中的进程赋予时间片。时间片消耗完毕的进程进入队尾
影响时间片大小的因素:
- 系统对响应时间的要求
- 就绪队列中的进程数目
- 系统的处理能力
特点:
- 当时间片足够大时,时间片轮转调度算法将退化为先来先服务调度算法
- 有可能增大进程等待时间,降低系统吞吐量
- 满足多用户需求
多级反馈队列调度算法
- 设置多个就绪队列并赋予不同优先权
- 根据优先权分配各个队列中进程执行获得的时间片大小 高大低小
- 综合了各种算法的优点
- 通过反馈方法对进程服务时间预估值进行调整
最早截止时间优先调度算法EDF
- 优先调度截止时间更近的进程
- 非抢占式算法可能导致进程饿死
最低松弛度优先调度算法LLF
- 松弛度计算公式:
截止时刻-当前时刻-服务时间
- 松弛度小的优先执行
- 松弛度为0时触发抢占式调度
死锁
产生死锁的条件
- 互斥条件:资源排他性使用
- 请求和保持条件:请求资源未果,进程虽阻塞但保持占有资源不放
- 不剥夺条件:进程已获资源未使用完之前不能被剥夺
- 环路等待条件:进程-资源环形链相互等待
死锁的处理方法
预防死锁
设置某些前提以破坏产生死锁的必要条件
- 摒弃“请求和保持”条件
- 系统要求所有进程在开始运行之前,都必须一次性地申请其在整个运行过程所需的全部资源
- 简单安全且易于实现但是会造成资源浪费、进程延迟
- 摒弃“不剥夺”条件
- 进程在需要资源时才提出请求,且得不到满足时应时放弃已占有资源
- 实现复杂:反复申请与释放资源,进程周转时间延长、系统吞吐量降低、系统开销增加
- 摒弃“环路等待”条件
- 所有资源按类型进行线性排队,所有进程对资源的请求严格按照资源序号递增次序提出
- 资源次序不灵活,缺乏扩展性
避免死锁
资源动态分配过程中,利用某种方法去防止系统进入不安全状态
检测死锁
运行过程中通过系统设置的检测机制及时检测死锁的发生,并精确确定相关进程和资源
- 死锁定理
- 系统状态为死锁状态的充要条件是当且仅当该状态下的资源分配图是不可完全化简的
- 死锁检测算法
work=Available; // 代表可用资源 isolatedSet={P[i]|Allocation[i]=0 ^ Request[i]=0} // 孤立进程集 // Allocation 分配给进程的资源数量 Request 进程请求的资源数量 for all P not in isolatedSet do // 对于所有非孤立进程 begin for all Request[i]<=work do // 将可用资源能满足需求的进程放入孤立进程集 begin work=work+Allocation[i]; isolatedSet=isolatedSet+[p[i]] end end // 停止条件:本次循环和上次循环孤立进程集未发生变化 deadlock=not (isolatedSet==P) // 如果全部进程孤立则不为死锁
解除死锁
- 基本方法
- 剥夺其他进程足够数量的资源分配给死锁进程
- 撤销死锁进程
- 评价指标
- 为解除死锁撤销的进程数目多少
- 撤销死锁进程付出的代价最小