2.进程与线程
2.1进程概述
(1)什么是进程?什么是线程?
进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
引入线程后,线程是调度的独立单位。
引入进程的目的是更好的使多道程序并发执行,提高资源利用率和系统吞吐量。
引入线程的目的是减小程序并发执行时的时空开销,提高操作系统的并发性能。
(2)进程和线程的比较
·调度:在传统的操作系统中,拥有资源和独立调度的基本单位都是进程,每次调度都要进行上下文切换,开销较大;在引入线程的操作系统中,线程是独立调度的基本单位,而线程切换的代价远低于进程。
·并发性:在引入线程的操作系统中,不仅进程可以并发执行,而且一个进程中的多个线程也可以并发执行,使操作系统有了更好的并发性,提高了系统吞吐量。
·拥有资源:进程是系统中拥有资源的基本单位,而线程不拥有资源,但可以访问其隶属进程的系统资源。
·系统开销:在创建、撤销或切换进程时的开销大于线程,线程切换只需要保存和设置较少寄存器内容。
·支持多处理机系统:对于单线程进程,进程只能运行在一个处理机上,对于多线程进程,可以将进程中的多个线程分散到各个处理机上执行。
·地址空间和其他资源:每个进程都拥有独立的地址空间和资源,而同一进程中的各线程共享进程资源,且对其他进程不可见。
·通信:进程间通信需要进程同步和互斥手段的辅助,以保证数据的一致性;线程间可以直接读写进程数据段来进行通信。
·线程是进程的一个组成部分,每个进程在创建时通常只有一个线程,需要时可以创建其他线程。
(3)进程的组成和特性?
进程由数据段、程序段和进程控制块(PCB)组成,PCB是进程存在的唯一标志,系统利用PCB来管理和控制进程。
进程特点:
动态性(进程最基本的特性):进程是程序的执行过程,是动态的概念
并发性:多个进程可以在同一时间间隔并发执行
独立性:进程拥有独立的地址空间和资源
异步性:由于系统资源有限,并发执行的进程在何时开始执行,执行的顺序与执行多长时间是不确定和不可预知的,进程以不可预知的速度向前推进。
结构性:进程由数据段、程序段和进程控制块组成,具有结构性
交互性:
(4)进程和程序的区别
程序是指令的集合。是静态的概念;而进程是程序的执行过程,是动态的概念。
程序可以作为资料永久保存;而进程具有生命周期,存在是暂时的。
程序与进程不是一一对应的,一个进程可以运行多个程序,一个程序也可以在多个进程中运行。
进程是有结构的——由程序段、数据段和PCB组成,而程序没有。
进程可以生成子进程,程序不可以。
进程具有并发性,程序没有。
进程是竞争计算机资源的基本单位。
(5)程序顺序执行时的特征
程序运行的顺序性
程序运行环境的封闭性
程序计算结果的可再现性
(6)程序并发执行的特征
间断性
失去封闭性
不可再现性
计算与结果不再一一对应
(7)程序输入一样运行结果为什么不一样?
程序在并发执行时,由于失去了封闭性,其计算结果已经和并发执行速度有关,从而使程序失去了可再现性。
(8)进程控制块的作用
系统借助PCB来控制和管理进程,PCB是系统感知进程存在的唯一标志。
PCB中包含:进程标识符——标志各个进程
进程控制和管理信息
进程当前状态、进程优先级——作为处理机调度的依据
处理机相关信息——当进程被切换时,处理及状态信息都必须保存在相应的PCB中,以便该进程重新执行时,能从断点继续执行。
资源分配清单
(9)什么是上下文?
执行过的指令和数据在相关寄存器和堆栈中的内容称为上文。
正在执行的指令和数据在相关寄存器和堆栈中的内容称为正文。
待执行的指令和数据在相关寄存器和堆栈中的内容称为下文。
2.2进程状态和管理
(1)进程的三种基本状态及基本转换
就绪状态:进程已经分配到除处理机之外的所有必要的资源,只要能够获得处理机,便可立即执行的状态。
运行状态:进程已经获得处理机,其程序正在执行。
阻塞状态:进程因发生某些事件而暂停执行的状态。
就绪->运行:调度程序选择一个新的程序运行
运行->就绪:运行进程用完了时间片、高优先级进程处于就绪状态,运行进程被中断
运行->阻塞:进程需要等待某一事件的发生
阻塞->就绪:进程等待的事件发生
不能由阻塞态直接转换成运行态:只有通过调度才能进入运行态,而只有位于就绪态的进程才会被调度。
不能由就绪态直接转换为阻塞态:进程进入阻塞态是主动请求的,所以只有处于运行态的进程才能发出进入阻塞态的请求。
(2)进程的五状态转换模型、单挂起进程模型和双挂起进程模型
挂起:把一个进程从内存转到外存
挂起的目的:提高处理机效率、为运行进程提供足够内存、用于调试
收容(Admit):让一个新建的进程进入就绪状态或就绪挂起状态
2.3进程通信及线程
(1)什么是进程通信?通信机制有哪些?
一个进程直接或通过某一机构发一条消息给另一个进程,且据此来控制其他进程,进程之间的这种信息交流称为进程通信。
通信机制
低级通信机制:一般只传送一个或几个字节的信息,达到控制进程执行速度的作用。优点:速度快;缺点:信息量小、效率低、编程复杂。
高级通信机制:可以传送任意字节的消息,进程之间交换的信息量较多且效率高,解决了一个进程将大批量数据传送至另一个进程的问题。
(2)什么是共享存储系统通信?
在内存空间中开辟一个区域,并连接进入多个进程的虚拟地址空间,一个进程在该空间写入数据后,另一个进程便可从中读出数据,此后便可像读写普通存储器一样读写该区域。
特点:效率高,一个进程可以分配多个共享存储区。
(3)什么是消息传递系统通信?
进程间通信以消息为单位进行传递,发送进程发送消息到消息队列,接收进程从中取出消息,程序员直接利用系统的通信原语进行通信。
(4)什么是管道通信?
管道:指用于连接读进程和写进程以实现进程通信的共享文件,称pipe文件。
发送进程以字符流形式将大量数据送入管道,接收进程从中取出数据的通信过程称为管道通信。
(5)什么是内核级线程和用户级线程?他们的区别是什么?
内核级线程:从操作系统视角能看到的线程,依赖于操作系统核心,由内核的内部需求进行创建和撤销,用来执行一个指定的函数。
用户级线程:有关线程管理(创建、撤销、切换)的所有工作全部由用户程序在用户态下执行,内核意识不到线程的存在。
区别
创建:用户级线程由应用程序通过线程库实现,所有的线程管理工作都由应用程序负责完成;内核级线程的管理工作由操作系统内核完成。
切换:用户级线程中线程切换在用户态下进行;内核级线程切换必须在核心态下完成
开销:用户级线程管理的系统开销小,效率高;内核级线程管理需要切换到核心态,管理成本高,开销大。
操作系统意识:操作系统意识不到用户级线程的存在;但是可以看到内核级线程
并发度:用户级线程当其中一个线程被阻塞后,整个进程会被阻塞,并发度不高;内核级线程被阻塞后,别的线程可以继续执行,并发能力强。
并行性:多个用户级线程不可在多处理机上并行运行;内核级线程可以。
(6)多线程进程优点
响应速度快:即使部分线程阻塞,其余线程可以继续运行
资源共享:共享所属进程的内存和资源
经济:创建和切换线程更加经济
多处理机资源的利用:多个线程可以分散到多个处理机上并行执行
(7)多线程模型
有些系统同时支持用户级线程和内核级线程,由于用户级线程和内核级线程的连接方式不同,形成了三种不同的多线程模型。
一对一模型:将每个用户级线程映射到一个内核级线程
优点:当一个线程被阻塞后,允许调度另一个线程运行,所以并发能力较强
缺点:每创建一个用户进程,就要创建一个内核进程,开销较大
多对一模型:将多个用户级线程映射到一个内核级线程
优点:线程管理是在用户空间进行的,效率高
缺点:如果一个线程在访问内核时发生阻塞,则整个进程都会被阻塞,在任何时刻只有一个线程能够访问内核。
多对多模型:将多个用户级线程映射到多个内核级线程
克服了一对一模型开销大的缺点和多对一模型并发度不高的缺点。拥有着两种模型的优点。
2.4处理器调度
(1)什么是处理器调度,处理器管理的工作是什么?
从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程的并发执行。
处理器管理的工作是对CPU资源进行合理的分配使用,以提高处理机利用率,并使各用户公平的得到处理机资源。
(2)处理器的调度级别?
高级调度(作业调度):从后备队列中按照一定的算法选择合适的作业调入内存,并为其创建进程
中级调度(交换调度):从挂起队列中按照一定的算法选择合适的进程调入内存
低级调度(进程调度):从就绪队列中按照一定的算法选择合适的进程调入CPU
高级调度发生的频率最低,低级调度发生的频率最高。
(3)什么是作业?作业的组成
作业是在一次计算过程中或一次事务处理过程中要求计算机系统完成的全部工作。
作业 = 程序 + 数据 + 作业说明书
(4)作业与进程的关系?
作业是任务实体,进程是完成任务的执行实体。
没有作业进程无事可做,没有进程作业无法完成。
作业的概念更多的用于批处理操作系统中,而进程则可以用于各种多道程序设计系统。
(5)进程调度的时机
不能进行进程调度与切换的情况:
在处理中断的过程中
进程在操作系统内核程序临界区中
在原语操作过程中
(6)进程调度的方式?
可剥夺方式(抢占方式):当某一个进程正在执行时,操作系统可以基于某种原则剥夺已分配给他的处理机资源,将处理机分配给其他进程,适用于分时实时操作系统。
不可剥夺方式(非抢占方式):进程调度程序一旦把处理机分配给某进程后,只有当这个进程执行完或者发生某事件而阻塞时,才把处理器分配给另外的进程。
(7)调度算法的评价指标和若干标准
作业周转时间:从作业提交时刻到作业完成时刻的时间间隔。
进程周转时间:进程进入时刻到进程完成这段时间的时间间隔,也就是作业周转时间减去作业在后备队列中的等待时间
平均周转时间:各进程周转时间之和/进程个数
带权周转时间=周转时间/实际运行的时间
平均带权周转时间=各进程带权周转时间之和/进程个数
等待时间指进程/作业处于等待处理机的状态的时间之和
系统吞吐量:单位时间内完成作业的数量(完成作业量/时间)
cpu利用率:指cpu忙碌的时间占总时间的比例
(8)调度算法
先来先服务(FCFS):每次调度时总是选择最先进入就绪队列的进程为其分配处理机,使其执行,该进程一直执行到完成或发生阻塞事件时为止。
优点:公平,实现简单 缺点:对短作业不利
短作业优先(SJF):从就绪队列中选出一估计运行时间最短的进程,将处理机分配给它,使它立即运行,该进程一直执行到完成或发生阻塞事件时为止,再重新进行调度。
优点:有利于短作业,在给定的时间内可以完成更多作业,增大了系统吞吐量,改善调度性能
缺点:对长作业不利,没有考虑作业的紧急程度
作业长短只根据用户提供的估计运行时间而定,不一定真正能做到短作业优先调度
高响应比优先(HRRN):优先权=(等待时间+要求服务时间)/要求服务时间
时间片轮转调度算法(RR):就绪进程按先来先服务的原则,把CPU分配给队首进程,并令其执行一个时间片,当执行的时间片用完时,将该进程放到队尾,依次执行下一个进程。
优点:保证所有的就绪进程在一定的时间内均能获得一时间片的执行时间
缺点:时间片的长短对其调度有着很大的影响,若时间片太短,则将频繁的进行进程切换,将增大系统的开销;若时间片太长,则退化成FCFS算法。
高优先级调度算法:优先选择就绪队列中优先级最高的进程分配处理机。
多级反馈队列调度算法:时间片轮转调度算法与高优先级调度算法的结合。
2.5进程同步与互斥
(1)同步与互斥的定义?
同步:指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调他们的工作次序而产生的制约关系。(执行时有先后顺序)
互斥:并发进程在竞争共享资源时,一个时刻只允许一个进程去使用,其他想要使用该资源的进程必须等待,直至该资源被释放。(不能同时进入临界区)
(2)什么是临界资源与临界区、进入区、退出区?
临界资源:一次只允许一个进程访问的资源
临界区:访问临界资源的那段代码
进入区:在临界区前面增加的一段用于进行临界资源检查的代码
退出区:将临界资源正被访问的标志恢复为未被访问的标志
(3)临界区的四个管理准则
空闲让进:其他进程均不处于临界区
忙则等待:已有进程处于临界区
有限等待:等待进入临界区的进程不能死等
让权等待:不能进入临界区的进程,应释放CPU
(4)进程同步机制有哪些?
单标志法:一个进程在访问完临界区后会把使用临界区的权限交给另一个进程。
int turn = 0;
//P0进程 //P1进程
while(turn != 0); while(turn != 1);
critical section; critical section;
turn = 1; turn = 0;
remainder section; remainder section;
该算法对于临界区的访问一定是按照P0->P1->P0->P1……的顺序来的。若P0此时允许进入临界区而P0一直不访问临界区,那么虽然临界区空闲,但并不允许P1访问。
该算法违背了“空闲让进”原则。
双标志先检查法:设置一个布尔型数组flag[],数组中各个元素用来标记各进程想进入临界区的意愿,每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,把自身flag设为true,之后开始访问临界区。
bool flag[2] = {false};
//P0进程 //P1进程
while(flag[1]);(1) while(flag[0]);(6)
flag[0] = true; (2) flag[1] = true;(7)
critical section;(3) critical section;(8)
flag[0] = false;(4) flag[1] = false;(9)
remainder section;(5) remainder section;(10)
(1)(6)(2)(7)……顺序执行两个进程会同时访问临界资源,违背了“忙则等待”原则。
双标志后检查法:双标志先检查法的改版,前一算法是先检查后上锁,这一算法是先上锁后检查。
bool flag[2] = {false};
//P0进程 //P1进程
flag[0] = true;(1) flag[1] = true;(6)
while(flag[1]); (2) while(flag[0]);(7)
critical section;(3) critical section;(8)
flag[0] = false;(4) flag[1] = false;(9)
remainder section;(5) remainder section;(10)
解决了忙则等待问题,但是违反了空闲让进和有限等待原则((1)(6)(2)(7)……顺序执行)
peterson算法:只能处理两个进程
bool flag[2];
turn = 0;//turn 表示优先让哪个进程进入临界区
//P0进程 //P1进程
flag[0] = true; flag[1] = true;
turn = 1; turn = 0;
while(flag[1] && turn == 1); while(flag[0] && turn ==0);
critical section; critical section;
flag[0] = false; flag[1] = false;
remainder section; remainder section;
遵循空闲让进、忙则等待、有限等待原则,违背了让权等待原则。
硬件同步机制
关中断:在进入临界区之前关中断,直到该进程访问完临界区再开中断。
优点:简单高效
缺点:滥用中断权力可能会导致严重后果
关中断时间过长会影响系统效率
不适用于多CPU系统,因为在一个处理器上关中断并不能防止进程在其他处理器上执行相同的临界区代码。
TSL/swap指令
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞,适用于多处理机环境
缺点:不满足让权等待原则,会出现忙等现象。
(5)信号量机制实现进程互斥
信号量:表示可以使用的资源个数
实现互斥是在同一进程中进行一对PV操作
实现同步是在一个进程P,一个进程V
经典问题
1)生产者-消费者问题
semaphore mutex = 1;
semaphore full = 0;
semaphore empty = 1;
producer(){
while(1){
P(empty);
P(muntex);
生产产品
V(full);
V(mutex);
}
}
consumer(){
while(1){
P(full);
P(mutex);
消费产品
V(empty);
V(mutex);
}
}
2)哲学家进餐问题
避免死锁办法:
最多允许4个人同时拿起筷子
当哲学家两边筷子都可用时才允许拿起筷子
对哲学家编号,奇号哲学家先拿左边筷子,偶号哲学家先拿右边筷子
//当两边筷子都可用时才拿起筷子
semaphore chopsticks[5] = {1,1,1,1,1};
semaphore mutex = 1;
P(){
while(1){
P(mutex);
P(chopsticks[i]);
P(chopsticks[(i + 1) % 5]);
V(mutex);
进餐;
V(chopsticks[i]);
V(chopsticks[(i + 1) % 5]);
思考;
}
}
3)读者-写者问题
读读允许,读写,写写不允许
//写进程优先
int count = 0;
semaphore mutex = 1;//更新count互斥
semaphore rw = 1;//读者写者互斥
semaphore w = 1;//实现写优先
writer(){
while(1){
P(w);
P(rw);
写;
V(rw);
V(w);
}
}
reader(){
while(1){
P(w);//没有写进程才读,如果有w < 0,该进程阻塞
P(mutex);
if(count == 0)
P(rw);
count++;
V(mutex);
读;
P(mutex);
count--;
if(count == 0)
V(rw);
V(mutex);
}
}
(6)死锁、饥饿与死循环的联系和区别
共同点:都是进程无法向前顺利推进的现象(故意设计的死循环除外)
区别:死锁一定是循环等待对方手里的资源导致的,因此如果有死锁现象,至少有两个进程同时发生死锁,发生死锁的进程一定处于阻塞态
可能只有一个进程发生饥饿。饥饿的进程可能处于阻塞态,也可能处于就绪态。
可能只有一个进程发生死循环,死循环的进程可能处在运行态,只不过无法像期望那样顺利推进。
死锁和饥饿是由于操作系统分配资源的策略不合理导致的,而死循环是由代码逻辑的错误导致的,死锁和饥饿是管理者(操作系统)的问题 ,而死循环是被管理者的问题。
2.6死锁
(1)死锁的概念
指两个或多个进程都无限的等待一个事件,而该事件又只能由这些等待进程之一来产生。
(2)死锁形成的四个必要条件
互斥使用
不可抢占
请求并保持
循环等待
(3)解决死锁问题的几个策略
死锁预防(预防火灾)
死锁避免(检测到煤气超标,自动切断电源)
死锁检测和解除(发现火灾,灭火)
死锁忽略(在太阳上不管火灾)
(4)银行家算法工作原理及缺点
安全序列:如果系统按照这种序列分配资源,则每个进程都能顺利完成。
主要思想是避免系统进入不安全状态,在每次进行资源分配时,他首先检查系统是否有足够的资源,如果有,先进行试分配,并对分配后的新状态进行安全性检查,若新状态安全,则正式分配上述资源;否则拒绝分配该资源。保证系统始终处于安全状态,从而避免了死锁现象的发生。
例题:
process | current_allocation | still_need | available |
---|---|---|---|
P0 | 0032 | 0012 | 2022 |
P1 | 1000 | 1130 | |
P2 | 1301 | 1310 | |
P3 | 0301 | 0032 | |
P4 | 0012 | 1023 |
(1)该状态是否安全,列出一个安全序列
当前状态work = [2,0,2,2]
P0 work = [2,0,5,4]
P3 work = [2,3,5,5]
P1 work = [3,3,5,5]
P2 work = [4,6,5,6]
P4 work = [4,6,6,8]
所以一个安全序列为P0,P3,P1,P2,P4
(2)P2请求1,0,1,0能否予以满足?
若将P2请求1,0,1,0予以满足,则系统资源剩余量为1,0,1,2,可以满足进程P0,P0完成后资源剩余量1,0,4,4,可满足进程P3,P4,假设让P3先完成,完成后资源剩余量为1,3,4,5,满足进程P1,P2,P4,假设先让P1完成,完成后资源剩余量2,3,4,5,满足P2,P4,假设让P2先完成,完成后资源剩余量4,6,5,6,最后P4可以完成,所以可以满足P2请求。
缺点:
请求矩阵R不容易得到
进程的个数不是固定的而是动态变化的
资源的个数也不是固定的