一、进程与线程
进程
- 程序:是静态的,就是个存放在磁盘里的可执行文件,就是一系列的指令集合。
- 进程:是动态的,是程序的一次执行过程(同一个程序多次执行会对应多个进程)
1. 进程的组成
1. PCB(进程控制块)
-
PID:当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的 “身份证号”—— PID (Process ID,进程ID)
-
创建一个进程就是创建实体中的PCB,撤销进程实质上是撤销进程实体中的PCB。
-
PCB是进程存在的唯一标。
-
从不同角度进程有不同的定义:
- 进程是程序的一次执行过程。
- 进程是一个程序及数据,在处理机上顺序执行时所发生的活动。
- 进程是具有独立功能的程序,在数据集合上运行的过程(强调动态性),它是系统进行资源分配和调度的一个独立单位。
操作系统要记录的东西:
- 操作系统要记录PID、进程所属用户ID(UID):基本的进程描述信息,可以让操作系统区分各个进程
- 还要记录给进程分配了哪些资源(如:分配了多少内存、正在使用哪些I/O设备、正在使用哪些文件):可用于实现操作系统对资源的管理
- 还要记录进程的运行情况(如:CPU使用时间、磁盘使用情况、网络流量使用情况等):用于实现操作系统对进程的控制、调度
这些信息都被保存在一个数据结构PCB(进程控制块)中
操作系统需要对各个并发运行的进程进行管理,但凡管理时所需要的信息,都会被放在PCB中
2. 程序段、数据段
进程三部分所包含的信息:
- PCB:操作系统通过PCB来管理进程,因此PCB中包含操作系统对其进程管理所需要的各种信息。
- 程序段:存放程序代码的地方。
- 数据段:程序运行时使用产生的运算数据。
- PCB 是给操作系统用的。
- 程序段、数据段是给进程自己用的
程序是如何跑起来的
- 一个进程实体(进程映像)由PCB、程序段、数据段组成。
- 进程是动态的,进程实体(进程映像)是静态的。
进程实体反应了进程在某一时刻的状态(如:x++后,x=2)
2. 进程的组织
进程的组成讨论的是一个进程内部由哪些部分构成的问题,而进程的组织讨论的是多个进程之间的
组织方式问题(在一个系统中,通常有数十、数百乃至数千个PCB。为了能对他们加以有效的管理,应该用适当的方式把这些PCB组织起来)
进程组成的两种方式
链接方式
操作系统会持有不同的指针,而不同的指针指向不同状态的队列。
索引方式
指针指向的是索引表,而不是队列的对头。
3. 进程的特征
- 程序是静态的,进程是动态的
4. 进程的状态与转换
进程的三种基本状态(运行态,就序态,阻塞态)
进程是程序的一次执行,在这个执行过程中,有时进程正在被CPU处理,有时又需要等待进程的状态,会随时变化。
另外两种状态
- 进程的创建:操作系统为该进程分配所需要的内存空间并创建初始化PCB。
- 进程的撤销:进程结束时需要撤销进程,将分配给进程的资源回收、撤销进程PCB。
进程状态的转换
从一个状态到另一个状态,就需要进程状态的转换。
- 注意:
- 不能由阻塞态直接转换为运行态,也不能由就绪态直接转换为阻塞态(因为进入阻塞态势进程主动请求的,必然需要进程在运行时才能发出这种请求。)
- 处于阻塞态的进程,并没有接收到处理机资源,因此他不可能做出主动的行为。
- 遇到不可修复的错误,由运行状态跳转到结束状态。
5. 进程的控制
进程控制的主要功能:对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
简化理解:进程控制就是要实现进程状态转换
如何实现进程控制
- 用原语实现进程控制。
- 原语的特点:执行期间不允许中断,只能一气呵成,这种不可被中断的操作称为原子操作。
原语使用关中断指令和开中断指令实现。
用户在使用开关中断指令时,能保证用户进程。不被中断,可见开/关中断指令的权限非常大,必然只允许在核心态下执行的特权指令。
进程控制相关的原语
进程创建的原语
进程终止的原语
阻塞与唤醒的原语
进程切换的原语
原语的总结
进程控制会导致进程状态的转换。无论哪个进程控制原语,要做的无非三类事情:
- 更新PCB中的信息
a. 所有的进程控制原语一定都会修改进程状态标志
b. 剥夺当前运行进程的CPU使用权,必然需要保存其运行环境
c. 某进程开始运行前必然要恢复期运行环境 - 将PCB插入合适的队列
- 分配/回收资源
6. 进程间的通信
进程通信:就是指进程之间的信息交换。
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立,为了保证安全,一个进程不能直接访问另一个进程的地址空间。但是进程之间的信息交换又是必须实现的 。为了保证进程间的安全通信,操作系统提供了一些方法。
1. 共享存储
两个进程不能访问对方的地址空间,操作系统为两个进程分配共享空间。两个进程就可以依靠共享空间来进行通信。
- 两个进程对共享空间的访问必须是互斥的。(两个进程不能同时访问共享空间。)
- 基于数据结构的共享:共享空间里面只能存放固定的数据结构。(比如共享空间里只能放一个长度为10的数组。)这种共享方式速度慢、限制多,是一种低级通信方式
- 基于存储区的共享:操作系统只负责在内存中画出一块共享存储区;数据的形式、存放位置都由进程控制决定,而不是操作系统。相比之下,这种共享方式速度更快,是一种高级通信方式
2. 管道通信
“管道”:是指用于连接读写进程的一个共享文件。其实就是在内存中开辟一个大小固定的缓冲区
- 注意:
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
- 各进程要互斥地访问管道。(进程1再访问管道的时候,进程2是不可以访问的。)
- 数据以字符流的形式写入管道,当管道写满时,就不能继续再写(写进程的write()系统调用将被阻塞),等待读进程将数据取走,当读进程将数据全部取走后,管道变空,读进程就不能继续再读。(此时读进程的read()系统调用将被阻塞)。
- 如果没写满,就不允许读。如果没读空,就不允许写。
- 数据一旦被读出,就从管道中被抛弃,抛弃的数据无法找回,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。(如果另一个读进程把该读进程的数据读取后,该进程就无法再读取到他所要读取的正确数据。)
3. 消息传递
进程间的数据交换以格式化的消息为单位。进程通过操作系统提供的 “发送消息/接收消息”两个原语进行数据交换 。
- 格式化消息分为消息头和消息体
- 消息头包括:发送进程ID、接受进程ID、消息类型、消息长度等格式化的信息(计算机网络中发送的“报文”其实就是一种格式化的消息)
直接通信方式
每个进程都会有一个消息缓冲队列,如果另外进程1要给该进程2发送消息,进程1就会准备好格式化消息(消息头和消息体),该消息由发送原语发送给进程2,该消息就会被挂载到进程2的消息缓冲队列中,进程2会通过接收原语,把消息缓冲队列中的消息一次读取。
间接通信方式
系统会为进程们管理一个信箱,信箱里面会放着很多格式化消息体(消息头里面会保存该消息,是发送给哪个进程的消息),如果进程1要给进程2发送消息,进程1会通过发送原语将消息发送到信箱里,进程2会通过接收原语,从信箱当中读取属于自己的消息。
============================================================
线程
1. 线程的概念
传统的进程是程序执行流的最小单位,引入线程后,线程成为了程序执行流的最小单位,内核级线程才是处理机分配的单位
- 可以把线程理解为“轻量级进程”。
线程是一个基本的CPU执行单元,也是程序执行流的最小单位。
引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
引入线程后的变化
2. 线程间的切换
类比:桌子类似于CPU,书类似于线程
切换进程运行环境= 陌生人要用你的桌子,有一个不认识的人要用桌子,你需要你的书收走,他把自己的书放到桌上
同一进程内的线程切换=你的舍友要用这张书桌,可以不把桌子上的书收走
3. 线程的属性
4. 线程的实现方式
4.1 用户级线程
- 用户级线程由应用程序通过线程库实现。所有的线程管理工作都由应用程序负责(包括线程切换)
- 用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预。
- 在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。(用户级线程对用户不透明,对操作系统透明)
可以这样理解,“用户级线程” 就是 “从用户视角看能看到的线程”
4.2 内核级线程
- 内核级线程的管理工作由操作系统内核完成。线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
可以这样理解,“内核级线程” 就是 “从操作系统内核视角看能看到的线程”
4.3 多线程模型
在同时支持用户级线程和内核级线程的系统中,可采用二者组合的方式:将n个用户级线程映射到m个内核级线程上( n >= m)
- 重点重点重点:
操作系统只“看得见”内核级线程,因此只有内核级线程才是处理机分配的单位。
例如:左边这个模型中,该进程由两个内核级线程,三个用户级线程,在用户看来,这个进程中有三个线程。但即使该进程在一个4核处理机的计算机上运行,也最多只能被分配到两个核,最多只能有两个用户线程并行执行。
在同时支持用户级线程和内核级线程的系统中,由几个用户级线程映射到几个内核级线程的问题引出了 “多线程模型” 问题。
一对多模型
多对一模型:多个用户及线程映射到一个内核级线程。每个用户进程都对应一个相同的内核级线程。
- 优点:
- 用户级线程的切换在用户空间即可完成,不需要切换到核心态
- 线程管理的系统开销小,效率高
- 缺点:
- 当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。
- 多个线程不可在多核处理机上并行运行
一对一模型
一对一模型:一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。
- 优点:
- 当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。
- 多线程可在多核处理机上并行执行。
- 缺点:
- 一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
多对多模型
多对多模型:n 用户及线程映射到 m 个内核级线程(n >= m)。每个用户进程对应 m 个内核级线程。
特点:克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
5. 线程总结
二、处理机调度
处理机调度导图
当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是调度研究的问题。
在多道程序系统中,进程的数量往往是多于处理机的个数的,这样不可能同时并行地处理各个进程。处理机调度,就是从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程的并发执行。
1. 调度的三个层次
高级调度
由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序。
高级调度(作业调度):按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。
高级调度是辅存(外存)与内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。
中级调度
引入了虚拟存储技术之后,可将暂时不能运行的进程调至外存等待,等它重新具备了运行条件且内存又稍有空闲时,再重新调入内存。
这么做的目的是为了提高内存利用率和系统吞吐量。
中级调度(内存调度):就是要决定将哪个处于挂起状态的进程重新调入内存。
- 注意:一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高
暂时调到外存等待的进程状态为挂起状态。值得注意的是,PCB并不会一起调到外存,而是会常驻内存。PCB中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的PCB来保持对各个进程的监控、管理。被挂起的进程PCB会被放到的挂起队列中。
挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态
五状态模型 ——>七状态模型
- “挂起”和“阻塞”的区别,
- 两种状态都是暂时不能获得CPU的服务
- 但挂起态是将进程映像调到外存去,而阻塞态下进程映像还在内存中。
有的操作系统会把就绪挂起、阻塞挂起分为两个挂起队列,甚至会根据阻塞原因不同再把阻塞挂起进程进一步细分为多个队列
低级调度
低级调度(进程调度):其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。
进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。
进程调度的频率很高,一般几十毫秒一次
三种调度的对比
2. 进程调度的时机
非剥夺调度方式:又称非抢占方式。即,只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。(实现简单,系统开销小但是无法及时处理紧急任务,适合于早期的批处理系统)
剥夺调度方式:又称抢占方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。(可以优先处理更紧急的进程,也可实现让各进程按时间片轮流执行的功能(通过时钟中断),适合于分时操作系统、实时操作系统)
- “狭义的进程调度”与“进程切换”的区别:
- 狭义的进程调度指的是从就绪队列中选中一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程,后一种情况就需要进程切换)
- 进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。
广义的进程调度包含了选择一个进程和进程切换两个步骤。
进程切换的过程主要完成了:
- 对原来运行进程各种数据的保存
- 对新的进程各种数据的恢复
(如:程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)
注意:进程切换是有代价的,因此如果过于频繁的进行进程调度、切换,必然会使整个系统的效率降低,
使系统大部分时间都花在了进程切换上,而真正用于执行进程的时间减少
调度算法的评价指标
周转时间,是指从作业被提交给系统开始,到作业完成为止的这段时间间隔。
三、同步与互斥
进程具有异步性的特点,也就是进程以各自独立的不可预知的速度向前运行。
进程的同步就是用来解决异步问题。
同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调他们的工作顺序,进程间的直接制约关系,就是源于它们之间的互相合作。
一段时间内只允许有一个进程使用的资源称为临界资源,许多物理设备都属于临界资源,此外还有许多变量数据内存缓冲区也属于临界资源。
对于临界资源的访问必须互斥的进行。互斥亦称间接制约关系,进程互斥,只当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源进场结束访问释放了该资源后,另一个进程才能去访问临界资源。
为了实现对临界资源的互斥访问,同时保证系统整体性,需要遵循以下原则。
- 空闲让进,临界区空闲时可以允许一个请求进入临界区的进程,立刻进入临界区。
- 忙则等待,当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
- 有限等待,请求访问的进程应保证能与有限时间内进入临界区。
- 让权等待,当进程不能进入临界区时,应立刻释放处理机,防止进程忙等待。
四、死锁、
死锁的概念
死锁:在并发环境下,各进程因竞争资源而造成一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象。
饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。
死循环:某进程执行过程中一直跳不出某个循环的现象。
产生死锁需要同时满足4个条件
- 互斥条件:只有对必须互斥使用的资源的争夺,才会导致死锁(像内存、扬声器这样可以同时使用的资源不会导致死锁。)
- 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求的进程被阻塞,但又对自己已有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等带链,链中的每一个进程,以获得的资源同时被下一个进程所请求。
注意:发生死锁时,一定有循环等待,但是发生循环等待时未必死锁。
死锁处理的策略
- 预防死锁,破坏死锁产生的4个必要条件中的一个或几个。
- 避免死锁,用某种方法阻止系统进入不安全状态,从而避免死锁。
- 死锁的检测和解除,允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。
死锁的处理策略——预防死锁
破坏互斥条件
互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。
如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如: SPOOLing技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备…
缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件。
破坏不剥夺条件
不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)
该策略的缺点:
- 实现起来比较复杂。
- 释放已获得的资源可能造成前一阶段工作的失效,因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。
- 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
- 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。
破坏请求和保持条件
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。
缺点:
有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造 成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿。
破坏循环等待条件
循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。
原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。
缺点:
- 不方便增加新的设备,因为可能需要重新分配所有的编号;
- 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费;
- 必须按规定次序申请资源,用户编程麻烦。