文章目录
1)进程模型
1.1 程序和进程的区别
- 程序是便编写好的指令(代码)
- 进程是程序的一次执行活动,活动有生命周期,运行过程中需要处理数据
1.2 进程的运行方式
- 真并行:多个CPU或者CPU多核
- 伪并行:一个CPU的时间分成多个较小的时间片,轮流分配给多个任务运行。
1.3 进程的管理
用程序控制块(PCB\PTE)管理进程的信息
进程=程序+数据+进程控制块
-
程序控制块
存放进程的管理和控制信息的数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。
-
对于程序和进程关系的理解
例如:同时打开很多word,word是一个程序,不同的窗口是不同的进程。
2)进程之间的关系
-
在unix中,每个进程有且只有一个父进程,所有进程都由一个祖先进程创造而来,祖先进程没有父进程
-
unix启动时会创造一个进程(init),工作到机器关机
-
父进程管理子进程:父进程创建子进程,子进程结束时候需要父进程帮忙清理僵尸态,释放掉占用的资源。
-
父进程结束时,如果还有正在运行的子进程,父进程会有以下两种做法:
- 杀死子进程
- 将子进程的父进程设置为init
-
系统调用fork创造进程
3)进程的状态
-
正在运行(Running):进程获得了所需要的所有资源,并且得到了CPU正在运行
-
就绪状态(Ready):进程准备好了相关的资源,由于CPU正在为其他进程服务,在等待CPU。
-
阻塞状态(Block):还有其他资源没有准备好。
- 正在运行的进程发现缺少相关资源,主动去睡觉
- 调度器分配给进程的时间片用完
- 调度器分配给进程时间片
- 已经准备好被占用的相关的资源,被资源占用者叫醒
-
Running和Ready 合成为 Runnable,可运行态,表示从众多进程中选择一个执行,执行的即为Running,其他未执行的称为Ready
-
进程刚被创建时候的状态,需要等待分配最基本的资源
-
进程结束时,转入僵尸态,需要父进程帮忙释放掉自己占用的资源
4)线程
4.1 线程模型
目的:进程中需要同时执行多个任务序列,多个执行的任务需要共享数据
-
进程中每个用户对应一个执行序列,这个执行序列就是线程;线程是进程的多个任务,里面包含自己的线程描述符,基本的数据,可以被独立的调度,独立的运行
-
调度的最小单元是线程
-
资源分配的最小单位是进程
4.2 线程的实现
实现的方式:
-
内核管理进程,进程内部维护运行时,里面维护自己的线程表。操作系统分配时间片给进程,进程分配时间片给线程。
优点:老旧的操作系统中,操作系统不支持线程;每个进程内部可以采用不同的线程调度策略,使得调度策略个性化。
缺点:当进程中的某个线程缺少资源时,会向进程反应,进程通知内核之后,被设置为阻塞态(Blocked),会导致进程中其他不需要该资源的线程被阻塞。
-
操作系统内核直接维护进程和线程表
优点:不会因某个线程的资源占用影响使得其他线程被阻塞
缺点:老旧系统不支持,需要改造;不支持个性化调度策略
-
把一个进程中的线程分成多组,每一组由不同的线程管理,操作系统管理主线程
弹出式线程:用完不进入阻塞态,直接丢弃,用时创建新的。
多线程会有可能造成资源冲突
5)调度算法
5.1 调度算法的目标
-
进程的调度:Running和Ready中来回转换
-
进程分类:CPU密集型、I/O密集型
CPU密集型:大量占用CPU进行计算
I/O密集型:涉及到数据输入输出
-
目标:公平、平衡
公平:每个进程的到时间片
平衡:各个进程得到的时间相对平均
-
对于不同类型操作系统的目标:
大型操作系统:较大的吞吐量(单位时间内完成任务数量)和较短的平均周转时间(任务完成时间)
交互式系统:足够短的相应时间,保证各个进程在人的接受范围内做出相应
实时系统:根据用户需要调度、稳定、达到设定的目标
5.2 调度算法的实现
5.2.1 批处理系统的调度
-
先来先服务(First Come First Served):短任务等待时间较长,系统效率不高
-
最短作业优先(Shortest Job First ):可能造成长作业的饥饿(长时间得不到执行)
-
最短剩余时间优先(Shortest Remaining-time Next ):允许暂停任务,预期算出剩余时间
-
高响应比优先(High Response-ratio First):
周转时间 = 任务到达时间-任务结束时间 (周转时间 = 等待时间+执行时间)
相应比(加权周转时间)= 周转时间/执行时间 = 1+等待时间/执行时间
5.2.2 交互式系统的调度
-
轮转调度(Round-robin scheduling ):多个任务轮流使用CPU,达到伪并行的效果
-
优先级调度(Priority scheduling ):按照优先级轮转调度,优先级
任务的优先级划分为0~127,其中0最高
实时进程优先级:0~39(任务必须马上运行,时间上得到保证,不能耽误、很重要的进程)
普通进程优先级= base(固定40) + nice(静态优先) + cpu_penalty (动态优先)
nice:(0~39),默认20,用户只可以调大nice,root可以随意调整nice值
cpu_penalty:占用CPU时间越长,惩罚值越大,每秒钟惩罚值减半(防止最后优先级都变为127,减半参数为0.5,参数可调)
-
其他:多级队列(Multiple queues )、最短进程优先(Shortest process next )、保证调度(Shortest process next )、彩票调度(Lottery scheduling )、公平分享调度(Fair-share scheduling)
6)竞争
-
互斥:两个线程抢占同一资源产生竞争,导致冲突。这样的资源叫关键资源(临界资源)
关于临界资源的约束:- 如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
- 任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。
- 进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
- 如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。
-
同步:进程中有内在的先后逻辑关系,如 进程2需要进程1运行的结果
-
解决方法:
- 忙等待:轮询检查资源是否可用
- 睡眠与唤醒:无法工作转为睡眠状态
6.1 忙等待
-
屏蔽中断:整个机器完全停滞,供一个进程使用
-
锁变量:使用变量记录临界资源是否被使用,但容易引发进程冲突
-
严格轮转法:两个进程同时访问一个变量(turn),0/1控制进入。但是会造成自己阻碍自己进入临界区。
-
peterson解法:
- 进程0通过调用enter_region()进入临界区,此时1也想调用enter_region()来进入临界区, 但interested[0]为TRUE,因此被while循环挂起,当进程0出临界区时调用leave_region(),将 interested[0]设为FALSE,进程1即可及时进入临界区。
- 当进程0在调用enter_region()过 程的任意时刻被中断,进程1进入临界区后进程0再次进行时,依然会被挂起。(实际上while 循环体中的两条判断句就保证了,当一个进程在临界区中时,另一个想进入临界区的进程必然会被挂起)
限制:只使用于两个进程。
- TSL指令:先把lock的值拷贝到寄存器,同时更改lock为1。优点lock的检查和更改在一个指令中,保证操作原子性。
6.2 睡眠与唤醒
信号量(semaphore):包含一个整数一个等待队列,与临界资源(打印机、扫描仪、内存中的一个变量等)一一对应。等待队列就是等待使用该资源的进程,当一个进程离开时,会检查该资源的等待队列。信号量正为可用个数,负为等待使用进程个数。
PV原语:操作信号量的代码,操作系统底层提供解决方案,使其不可分割,执行起来短小精悍。P下降,V上升(出自荷兰语)。
//进入临界区之前进程执行P原语
P (Semaphore s) {
s=s-1;
if (s<0){
//增加到等待队列
}
}
//离开临界区之后执行V原语
V (Semaphore s) {
s=s+1;
if (s<=0){
//唤醒等待队列的进程
}
}
/*
假如有一台打印机,现在S=1,打印机的任务代码为
P(S)
print(process,doc)
V(S)
a、b、c三个进程来打印,我们假设过程如下
*/
-
a 开始打印,首先执行P(S),s=s-1=1-1=0,没有空闲的资源,没有等待打印的进程,此时a进入print(a,doc),进行打印操作,此时等待队列和s变化状态如下
s = 1 -> 0 等待队列:
-
此时b进程到达,P(S),s=s-1=0-1=-1,此时s<0 进入阻塞态,b进入等待队列
s = 1 -> 0 -> -1 等待队列: b
-
此时c进程到来,执行P原语,s=s-1=-1-1=-2,此时s<0 进入阻塞态,c进入等待队列
s = 1 -> 0 -> -1 -> -2 等待队列: b - c
-
此时s=-2,代表有两个进程正在等待,假设此时a打印结束,a离开打印机的临界区,执行V原语,s=s+1=-2+1=-1,代表只有一个进程等待,s=-1≤0,所以a会唤醒等待队列中的一个进程,b状态改变为Ready,等待被调度器调度
s = 1 -> 0 -> -1 -> -2 -> -1 等待队列: c
-
b也打印完成,执行V原语,s=s+1=-1+1=0,s=0≤0,所以b在临走时唤醒c
s = 1 -> 0 -> -1 -> -2 -> -1 -> 0 等待队列:
-
c打印完成,执行V原语,s=s+1=1>0,所以c直接离开
s = 1 -> 0 -> -1 -> -2 -> -1 -> 0 -> 1 等待队列:
PV原语同样适合忙等待:
P (Semaphore s){
while (!s>0){
//放弃CPU,重新排队
}
s--;
}
V (Semaphore s){
s++;
}
我们假设相同情况
- a开始打印,s=1>0,跳过while,s–,s变为0
- b要打印,执行P原语,进入while循环,轮询s状态
- c要打印,执行P原语,进入while循环,轮询s状态
- a打印完,执行V原语,s++,s=1
- b或者c可以运行,调度其中一个运行
- 剩下的进程运行
7)通信
7.1 进程间通信
-
信号(Signal):不同整数代表不同含义,如9代表kill
-
管道(Pipes):单向通信,分为无名管道和有名管道,无名管道:有族亲关系进程之间的通信,有名管道:没有族亲关系的进程之间的通信
-
其他:消息队列(message)、信号量(semaphore)、共享内存(shared memory)、sockets(适用于不同机器上的通信)
7.2 线程间通信
一个进程中多个线程天然共享进程的资源,使用POSIX提供的Pthreads函数保证资源不会被同时访问到。
8)死锁
8.1 死锁概念及特点
有些资源不可被多个进程同时使用,对于资源的获取,可以使用P原语申请相关资源的信号量,在对于多个资源的申请上有可能造成死锁。例:
进程a,b,需要临界资源1,2
a->1->2
b->2->1
执行过程
a->1
b->2
a->2(等待b)
b->1(等待a,形成死锁)
特点:
- 循环等待
- 不能抢夺
- 拥有部分,期望更多
- 资源互斥使用
8.2 死锁的检查与恢复
-
鸵鸟算法:死锁现象发生概率很低,解决困难,处理代价大。当死锁问题偶然出现时,手工干预(重启机器)
-
检测方法:
-
建立有向图,判断途中是否存在有向环。
下图表示进程A期望资源B
下图表示进程C拥有资源D
下图表示死锁状态(A拥有D,期望B,C拥有B,期望D)
-
拥有矩阵:矩阵Aij元代表第i个进程拥有第j个进程
期望矩阵:矩阵Aij元代表第i个进程期望第j个进程
- 解决方法:杀死一个最年轻的进程、进程回滚、抢夺资源。
8.3 死锁的避免和预防
避免方法:
- 轨迹图法:不适于多资源多进程
-
银行家算法:最大限度保证安全状态
安全序列:存在一个分配回收的过程使得所有进程都能执行完
安全状态:如果一个状态存在一个或多个安全序列就叫做安全状态
安全状态:
非安全状态
死锁预防,针对死锁特点,预防方法如下:
- 循环等待→资源有序申请,把资源排序,逐步申请
- 不能抢夺→强行拿走资源
- 拥有部分,期望更多→使进程拥有全部资源
- 资源互斥使用→添加任务队列