赞
踩
进程是资源分配的基本单位。
概念:进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
a) 动态性,可动态创建和结束进程
b) 并发性,进程可以被独立调度并占用CPU运行
c) 独立性,不同进程之间互相不影响
d) 制约性,因访问共享数据/资源或进程间同步产生制约
总之,进程包含了正在运行的一个程序的所有状态信息。
程序是进程的基础,进程是程序功能的体现。
进程控制块是操作系统管理控制进程运行所用的信息集合,它描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。
操作系统使用PCB来描述进程的基本情况以及运行变化的过程。且PCB是进程存在的唯一标志,每个进程都在操作系统中有一个对应的PCB。
进程创建时生成该进程的PCB,进程终止时回收它的PCB,它的生命周期和进程的生命周期一样。
进程的组织管理:通过对PCB的组织管理来实现进程控制
关于进程控制块的描述正确的是(ABCD)
A.操作系统用进程控制块来描述进程的基本情况以及运行变化的过程
B.进程控制块是进程存在的唯一标志
C.每个进程都在操作系统中有一个对应的进程控制块
D.操作系统管理控制进程运行所用的信息集合是进程控制块
进程的生命周期如下:进程创建,进程执行,进程等待,进程抢占,进程唤醒,进程结束.
创建完成进程会进入就绪队列中,等待进程执行
内核选择一个就绪的进程,让它占用处理机并执行进程,如何选择进程(处理机调度算法)
进程进入等待(阻塞)状态的情况:
需要退还系统资源。
进程的三种基本状态:
进程还有其它的基本状态:
在不少系统中进程只有上述三种状态,但在另一些系统中又增加了一些新状态。
处于挂起状态的进程映像在磁盘上,目的是减少进程占用内存(进程存储在外存中)。
新增加了两个挂起状态:
等待挂起状态,进程在外存并等待某事件的出现。
就绪挂起状态,进程在外存,只要进入内存即可运行。
挂起:把一个进程从内存中转到外存中。
引入挂起状态的原因:
1)终端用户的请求 当终端用户在自己的程序运行期间发现有可疑问题时,希望暂时使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。这种静止状态称为挂起状态。
2)父进程请求 有时父进程希望挂起自己的某个子进程,以便考查和修改该子进程,或者协调各子进程间的活动。
3)负荷调节的需要 当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
4)操作系统的需要 操作系统有时希望挂起某些进程,以便检查运行中德资源使用情况或进行记账。
由操作系统来维护一组队列,表示系统中所有进程的当前状态,不同队列表示不同状态,根据进程状态不同,进程PCB加入相应队列。进程状态变化时,它所在的PCB会从一个队列换到另一个队列。
线程是独立调度的基本单位。
引入线程可以提高进程内部的并发性。多进程并发比较复杂,在进程内部引入多线程机制会提高进程内部的并发性。
多进程的实现系统之间的通信和数据共享复杂(要经过内核共享数据)且系统的开销较大,因此在进程内部增加一类实体,实体之间可以并发执行,且实体之间共享相同的地址空间,因此引入线程的概念。
线程的概念:线程是进程的一部分,描述指令流执行状态,他是进程中的指令执行流的最小单元,是CPU调度的基本单位。
进程的作用变成了资源分配,进程由一组相关资源构成,包括地址空间(代码段、数据段)打开文件等各种资源。线程作为处理机调度角色,线程描述在进程资源环境中的指令流执行状态。(线程是程序执行的最小单元)。
线程的特点:
进程切换主要涉及资源保存与恢复。
使用进程控制块PCD保存内核的进程状态记录。内核将相同状态的进程的PCB放置在同一个队列当中。
进程切换暂停当前运行进程,从运行状态变成其他状态。
要调度另一个进程从就绪状态变成运行状态。
要保存进程生命周期的信息(寄存器(PC,SP...)CPU状态,内存地址空间。
操作系统系统调用供用户创建进程。
a) Windows进程创建API: CreateProcess(filename)
b) Unix或者Linux进程创建系统调用: fork/exec 重点掌握fork
c) fork()创建一个继承的子进程的步骤
Copy on Write(COW)技术:在Linux程序中,fork()会产生一个和父进程完全相同的子进程,把父进程的内容复制一份 但子进程在此后多会exec系统调用再把父进程内容覆盖,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
https://www.cnblogs.com/wuchanming/p/4495479.html
用户应用程序通过系统调用exec()加载执行可执行文件。
a) 程序加载和执行系统调用exec(),允许进程“加载”一个完全不同的程序,并从main开始执行
b) 允许进程加载时指定启动参数(argc, argv).
c) exec调用成功时,它是相同的进程,和加载之前原进程的PID参数相同但运行了不同的程序。
d) 代码、堆、栈的数据被完全重写。
进程等待与退出是父子进程之间的交互。
wait()函数:系统调用函数,用于父进程等待子进程的结束。
那么就会涉及到exit和wait的执行顺序问题。
父进程先wait,子进程再exit:
进程的有序终止:子进程先exit,然后父进程wait。
通过进程调度算法从进程就绪队列中选择一个进程占用处理机,不同环境下的调度算法目标不同,因此需要针对不同环境来讨论调度算法。
从就绪队列中挑选下一个占用CPU运行的进程。
调度程序:是指挑选就绪进程的内核函数。
要解决的问题:
i. 调度策略,依据什么原则挑选进程和线程。
ii. 调度时机,什么时候进行调度。
调度时机
若当前进程因时间片用完而让出处理机时,该进程应转变为就绪状态。
调度策略要解决的问题是挑选就绪队列中的哪一个进程。
调度算法性能指标:
希望“更快”的服务:高吞吐量和低延迟。
批处理系统没有太多的用户操作,在该系统中,调度算法的目标是保证吞吐量和周转时间。
依据进程进入就绪状态的先后顺序排列。
进程进入等待或结束状态时,就绪队列中的下一个进程占用CPU。
特点
既然在先来先服务算法中,把短进程排在前面可以减少周转时间,那么可以一直选择就绪队列中执行时间最短进程占用CPU进入运行状态,就是短进程优先算法。
就绪队列按预期的执行时间来排序,具有最优平均周转时间。
如果有一个进程正在运行,突然出现另一个时间更短的进程,此时允许更短的进行抢占。
缺点:
在短进程优先算法中长进程会发生饥饿,改进:选择就绪队列中响应比R值最高的进程。
R=(w+s)/s; w:等待时间,s:执行时间。
特点:
时间片:处理机分配资源的基本时间单元
算法思路:时间片结束时,按照FCFS先来先服务算法切换到下一个就绪进程,长度为n个的就绪队列每隔(n-1)个时间片进程执行一个时间片。
RR算法开销
存在额外的上下文切换(进程现场保护)资源切换 开销大
时间片长度
兼顾就绪队列排队和时间片划分的组合算法,也是现实操作系统使用的算法
多级反馈队列调度算法是一种根据先来先服务原则给就绪队列排序,为就绪队列赋予不同的优先级数,不同的时间片,按照优先级抢占CPU的调度算法。
就绪队列被划分成多个独立的子队列,每个队列拥有自己的调度策略。
在系统中设置多个就绪队列,并为每个队列赋予不同的优先级。第一个队列的优先级最高,第二个次之,其余队列的优先级逐个降低。
该算法为不同列中的进程所赋予的执行时间片的大小也各不相同,在优先级愈高的队列中,其时间片愈小。
FSS控制用户对系统资源的访问。
可能出现一些用户组比其他用户组更重要,为了保证不重要的组无法垄断资源,可以将未使用的资源按比例分配,没有达到资源使用率目标的组获得更高的优先级。
实时操作系统:正确性依赖于其时间和功能两方面的操作系统。实时性最重要。
实时操作系统分类
实时任务:规定时间完成一次任务所需要的资源
周期性实时任务:一系列相似的任务有规律性地重复执行。
硬时限:错过任务时限会导致灾难性的后果,必须进行验证,保证最坏情况下能够满足时限。硬时限可以保证系统的确定性。
可调度:表示一个实时操作系统能够满足任务时限要求。
软时限:通常能满足任务时限,若不满足则降低要求。尽力满足任务时限
可调度表示一个实时操作系统能够满足任务时限要求。
操作系统中高优先级进程长时间等待低优先级进程所占用资源的现象。基于优先级的可抢占调度算法均可出现优先级反置。
独立进程不和其他进程共享资源或状态,结果具备确定性和可重现性,调度顺序不重要。并发进程在多个进程间有资源共享,进程运行可能存在不确定性和不可重现。并发进程执行过程是不确定性和不可重现的,程序错误可能是间歇性发生的。然而需要进程之间的资源共享,并发执行可以加速实现进程间的协作。
原子操作是指一次不存在任何中断或失败的操作,要么成功要么失败,执行的结果没有暂态。
操作系统需要利用同步机制在并发执行的同时,保证一些操作是原子操作。
两人买面包问题:
正确方式:
利用两个原子操作实现一个锁(lock),处理过程不会被打断
Lock.Acquire()
Lock.Release()
breadlock.Acquire(); // 进入临界区
if(noBread) {
buy bread;
}
breadlock.Release(); // 退出临界区
进程的交叉关系
相互感知的程序 | 交互关系 | 进程间的影响 |
---|---|---|
相互不感知(完全不了解其他进程的存在) | 独立 | 一个进程的操作对其他进程的结果无影响 |
间接感知(双方都与第三方交互,如共享资源) | 通过共享进行协作 | 一个进程的结果依赖于共享资源的状态 |
直接感知(双方直接交互,如通信) | 通过通信进行协作 | 一个进程的结果依赖于从其他进程获得的信息 |
关系:
对临界区资源进行访问的那段代码称为临界区。临界区每次进入都需要进行条件检查。
为了互斥访问临界资源,每个进程在进入临界之前,需要先进行检查。
临界区的实现方法:禁用中断 软件方法 更高级的抽象方法
多线程或者多进程并发会导致资源竞争。需要对多线程和多进程进行同步,协调多线程对共享数据的访问,且在任何时候只能有一个线程执行临界区代码。
此外,确保同步正确的方法有底层硬件支持(TS指令 原子操作)和高层次的编程抽象。信号量和管道为高层次的编程抽象方法。
自旋锁为什么无法按先来先服务方式使用资源?
原因:自旋锁是由TS指令实现的临界区申请操作,第一个检测到临界区空闲的申请者而不是第一个开始检测的申请者进入。也就是说,访问顺序是由硬件随机决定的。如果要实现FIFO方式,一般都需要一个队列。
信号量是操作系统提供的一种协调共享资源访问的方法。
软件同步是平等线程间的一种同步协商机制,而操作系统是管理者,由操作系统仲裁谁来使用资源,其地位高于进程,因此信号量高于软禁同步。
信号量用来表示系统资源的数量。
信号量与软件同步区别:
由一个整形(sem)变量和两个原子操作组成。sem表征资源的数量。
P():sem减1,增加一个线程
若sem < 0, 进入等待,否则继续
V():sem加1,释放一个线程
sem <= 0,唤醒一个等待进程
信号量是被保护的整数变量
例题:如果有5个进程共享同一程序段,每次允许3个进程进入该程序段,若用PV操作作为同步机制则信号量S为-1时表示有三个进程进入了程序段,有一个进程在等待。
通常假定信号量是公平的
两者等价,基于一个可以实现另一个。
临界区的互斥访问控制。
mutex = new Semaphore(1);
每类资源设置一个信号量,初值为 1。
mutex->P();
Critical Section;
mutex->V();
不申请直接释放,会导致多个线程进入缓冲区。只申请不释放,会导致缓冲区无线程,但谁也进不去。
condition = new Semaphore(0);
条件同步设置一个信号量,初值为 0
线程a执行P操作后信号量为负值,进入等待状态,线程B执行V操作后,信号量又回到0,此时线程a可以继续往下执行,通过这种方式实现条件同步。
总结:什么是信号量?它与软件同步方法的区别在什么地方?
信号量是由操作系统提供的一种协调共享资源访问的方法。信号量是一种抽象数据类,由一个被保护的整形变量(sem)和P()、V()两个原子操作组成,表示系统资源的数量。
区别:
生产者在生成数据后放在一个缓冲区里,消费者从缓冲区取出数据处理,任何时刻只能有一个生产者或消费者可访问缓冲区。
解决方式:
a) 任何时刻只能有一个线程操作缓冲区(互斥访问) 二进制信号量mutex
b) 缓冲区为空时,消费者必须等待生产者(条件同步)资源信号量fullBuffers
c) 缓冲区满时,生产者必须等待消费者(条件同步) 资源信号量emptyBuffers
使用信号量存在的问题:
为了解决信号量出现的一些问题,例如在生产者-消费者中,PV操作是在两个不同的进程中,很容易出现多写漏写的情况。
管程将共享资源相关的PV操作集中到一起从而简化进程之间的同步控制。
管程在信号量的基础上提供条件同步,使用更容易,所以 Java 采用的是管程技术。synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。
管程是一种用于多线程互斥访问共享资源的程序结构。
可以假定管程为赛车跑道,为了安全任意一个时刻只能有一辆车在跑道疾驰,过程中会暂停更换零件,这时允许其他赛车进入跑道。
管程与临界区有什么异同?
相同点:在任一时刻最多只有一个线程执行管程代码或临界区代码;
不同:正在管程中的线程可临时放弃管程的互斥访问,等待事件出现时恢复;而临界区不支持临时退出;
管程和信号量的比较:
一个锁 + 0或者多个条件变量;锁是控制管程代码的互斥访问(入口),条件变量用来管理共享数据的并发访问
特点:
条件变量为管程内的等待机制:进入管程的线程因资源被占用而进入等待状态,每个条件变量表示一种等待原因,对应一个等待队列。
信号量和条件变量是并发问题的两种处理模型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyLL54Ld-1592980170374)(https://i.loli.net/2019/07/29/5d3e507177ae590470.png)]
方案一:
信号量法,两次P操作拿左边和右边刀叉 吃完放弃 两次V操作。
#define N 5 // 哲学家个数
semaphore fork[5]; // 互斥操作,信号量初值为1
void philosopher(int i) {
// 哲学家编号:0 - 4
while (ture) {
think();
P(fork[i]); // 拿左边的叉子
P(fork[(i + 1) % N]); // 拿右边的叉子
eat();
V(fork[i]); // 放下左边的叉子
V(fork[(i + 1) % N]); // 放下右边的叉子
}
}
有可能5个人同时拿左边叉子,都拿不到右边叉子,形成死锁。
方案二:添加一个互斥信号量,将就餐变成一个近似临界区,每次只能有一个人就餐。
#define N 5 // 哲学家个数 semaphore fork[5]; // 信号量初值为1 semaphore mutex; // 互斥信号量,初值1 void philosopher(int i) // 哲学家编号:0 - 4 while(TRUE) { think(); P(mutex); // 进入临界区 只有一个哲学家能就餐 P(fork[i]); // 去拿左边的叉子 P(fork[(i + 1) % N]);// 去拿右边的叉子 eat(); V(fork[i]); // 放下左边的叉子 V(fork[(i + 1) % N]); // 放下右边的叉子 V(mutex); // 退出临界区 }
任何时间只有一个哲学家就餐,性能差。
方案三
#define N 5 // 哲学家个数 semaphore fork[5]; // 信号量初值为1 void philosopher(int i) // 哲学家编号:0 - 4 while(TRUE) { think(); if (i % 2 == 0) { // 偶数 先拿左 后拿右 奇数 先拿右 后拿左 P(fork[i]); // 去拿左边的叉子 P(fork[(i + 1) % N]); // 去拿右边的叉子 } else { P(fork[(i + 1) % N]); // 去拿右边的叉子 P(fork[i]); // 去拿左边的叉子 } eat(); V(fork[i]); // 放下左边的叉子 V(fork[(i + 1) % N]); // 放下右边的叉子 }
若系统中有五台打印机,有多个进程均需要使用两台,规定每个进程一次仅允许申请一台,则在不发生死锁的情况下至多允许多少个进程参与竞争。
哲学家就餐问题:当5个进程的时候如果都同时申请到了1台,就发生死锁了。如果是4个进程,那必然有一个能申请到2台。
问题描述:读者只读取数据,不修改,且运行多个读者并发读取。写者可以读取和修改数据,读写互斥且写写互斥,不可并发写数据。
writer
P(WriteMutex);
write();
V(WriteMutex);
reader
P(CountMutex); // 保护 Rcount
if (Rcount == 0)
P(WriteMutex);
//若为当前第一个读者,开启读写互斥
++Rcount;
V(CountMutex);
read();
P(CountMutex);
--Rcount;
//若为当前最后一个读者,释放互斥访问权限
if (Rcount == 0)
V(WriteMutex);
V(CountMutex);
读者写者问题的优先策略:
管程的状态变量
AR = 0; // 正在读的读者
AW = 0; // 正在写的写者
WR = 0; // 等待读的读者
WW = 0; // 等待写的写者
Lock lock;
Condition okToRead;
Condition okToWrite;
reader
Database::Read() { // Wait until no writers; StartRead(); read database; // check out – wake up waiting writers; DoneRead(); } Database::StartRead() { lock.Acquire(); while ((AW+WW) > 0) { //写者优先,只要有写者就等待 WR++; okToRead.wait(&lock); WR--; } AR++; lock.Release(); } Database::DoneRead() { lock.Acquire(); AR--; if (AR == 0 && WW > 0) { // 当前没有读者并有等待写的写者 则唤醒写者 okToWrite.signal(); } lock.Release(); }
writer
Database::Write() { // Wait until no readers/writers; StartWrite(); write database; // check out-wake up waiting readers/writers; DoneWrite(); } Database::StartWrite() { lock.Acquire(); while ((AW+AR) > 0) { //写者优先,有正在写的写着或正在读的读者则等待 WW++; okToWrite.wait(&lock); WW--; } AW++; lock.Release(); } Database::DoneWrite() { lock.Acquire(); AW--; if (WW > 0) { // 优先唤醒等待写的写者 okToWrite.signal(); } else if (WR > 0) { // 如果没有等待写的写者 才唤醒等待读的读者 okToRead.broadcast(); } lock.Release(); }
while中的判断条件可根据优先策略进行调整,例子采取了写者优先策略。
以上管程在读/写操作时并没有申请互斥信号量,因为在之前申请互斥信号量时将正在/等待读/写的计数等设置完成,此时可确保读写正常进行。
死锁是由于竞争资源或者通信关系,两个或更多的并发进程各自占有某种资源而又都等待别的进程释放它们所占有的资源的现象。
进程访问资源的流程
资源分类
资源分配图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-se48uhkY-1592980170376)(https://i.loli.net/2019/07/29/5d3e674039de655749.png)]
死锁预防,破坏四个必要条件之一:
死锁避免
使用银行家算法避免死锁。
是一个避免死锁产生的算法。以银行借贷分配策略为基础,判断并保证系统处于安全状态。
规则
线程在第一次申请资源的时候声明所需最大资源量,在操作系统满足所有资源要求并完成任务后及时释放资源。
同时在线程申请资源不超过操作系统拥有资源最大值时,操作系统应尽量满足线程的需求。
数据结构
n = 线程数量 m = 资源类型数量
最大需求矩阵:Max(总需求量) :n x m 矩阵,表示线程Ti最多请求类型Rj的资源Max[i, j]个实例
可利用资源向量:Available(剩余空闲量) 长度为 m 的向量,表示当前有Available[i]个类型Rj的资源可用
分配矩阵:Allocation(已分配量) = n x m 矩阵,表示当前分配了Allocation[i, j]个资源实例
需求矩阵:Need(未来需要量) = n x m 矩阵,表示未来需要Need[i, j]个资源实例
Need[i, j] = Max[i, j] - Allocation[i, j]
安全状态判断
安全状态判断是银行家算法的核心部分。其基本思想是判断当前的剩余资源可以满足某一个线程的未来需要,并且迭代到最后可以满足所有线程的序列生成一个安全序列。
Work = Available // 当前资源剩余空闲量
Finish[i] = false for i : 1, 2, ..., n // 线程i没结束
Work = Work + Allocation[i] //线程i的需求量小于当前剩余空闲资源量,所以分配给他之后再回收
Finish[i] = true
转 2
4. 若所有线程Ti满足 Finish[i] == true,则为安全状态
银行家算法流程
初始化: Requesti 线程Ti的资源请求向量
Requesti[j] 线程Ti请求资源Rj的实例
循环:
1.如果 Requesti ≤ Need[i], 转到步骤2。否则, 拒绝资源申请, 因为线程已经超过了其最大要求
2.如果 Requesti ≤ Available, 转到步骤3。否则, Ti 必须等待, 因为资源不可用
3.通过安全状态判断来确定是否分配资源给Ti: 生成一个需要判断状态是否安全的资源分配环境
Available = Available - Requesti;
Allocation[i] = Allocation[i] + Requesti;
Need[i]= Need[i] – Requesti;
若安全 则分配资源给Ti
若不安全 则拒绝Ti的资源请求
银行家算法示例
按照序列 T2->T1->T3->T4 运行不存在死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mxAJFyn-1592980170380)(https://i.loli.net/2019/07/29/5d3e8d5db415e75776.png)]
T2只需要一个R3资源,满足要求,先分配给T2线程,然后T2线程释放全部资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KmIbRLF2-1592980170383)(https://i.loli.net/2019/07/29/5d3e8d7ed6ac850896.png)]
接下来可用资源可以满足剩下三个资源中的任意一个,若银行家算法存在多个满足条件的线程,则多个线程之间的先后顺序并不重要,因为这些进程的资源最后都会释放,之后可以满足需求更大的线程资源请求。
允许系统进入死锁状态,定期调用死锁检测算法来搜索图中是否存在死锁。出现死锁时,用死锁恢复机制进行恢复。
死锁检测算法数据结构
与银行家算法相比,没有最大资源请求判断。
步骤:
死锁恢复
死锁恢复有进程终止和资源抢占两种方法.
进程终止
可以选择终止所有死锁的进程,也可以一次只终止一个进程直到死锁消除.
终止进程的顺序应该是
资源抢占
进程通信(IPC,Inter-Process Communication)是进程进行通信和同步的机制。
IPC提供两个基本操作:发送操作(send message),接收操作 (receive message)。
进程间通讯的实现方式大致可以分成两种:间接通信和直接通信。
进程必须正确命名对方
两个进程必须同时存在时,直接通信才可以进行。
间接通信,通过操作系统维护的消息队列实现进程间的消息接收和发送。
每个消息队列都有一个唯一的标识,只有共享了相同消息队列的进程,才能够通信。
阻塞(同步)与非阻塞通信(非同步)
通信链路缓冲
进程发送的消息在链路上可能有三种缓冲方式:
管道、信号(量)、消息队列、共享内存
管道:进程间基于内存文件(或内存缓冲区)的通信机制,一个进程可以通过管道把数据传递给另外一个进程。前者向管道中写入数据,后者从管道中读出数据。是一种简介通信机制。
创建管道时并不关心通信的另一端,可能从键盘、文件、程序读取,也可能写入到终端、文件、程序。
管道的两种操作:读管道和写管道。
信号的作用是为了通知进程某个时间已经发生,是进程间的软件中断通知和处理机制 (如 ctrl-C 中断程序执行)
信号的接收处理方式:
信号机制其实是在软件层次上对中断机制的一种模拟,一个进程收到信号和收到中断请求可以说是一样的;
中断和信号的区别是,前者运行在核心态(系统),后者运行在用户态,中断的响应比较及时,而信号的相应一般会有延迟;
消息队列是由操作系统维护的以字节序为基本单位的间接通信机制。
每个消息是一个字节序列,相同标识的消息按着先进先出顺序组成一个消息队列(Message Queues)
共享内存是把同一个物理内存区域同时映射到多个进程的内存地址空间的通信机制。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。