文章目录
进程和线程
-
进程是系统进行资源调度和分配的基本单位。
-
线程是进程的子任务,是CPU调度和分派的基本单位。
-
一个程序至少有一个进程,一个进程至少
有一个线程,线程依赖进程的存在。 -
进程执行过程中拥有独立的内存单元,
而多个线程共享进程的内存。 -
线程自己基本上不拥有系统资源,
只拥有一点在运行中必不可少的资源
(如程序计数器,一组寄存器和栈)
程序计数器的作用
PC寄存器是用来存储指向下一条指令的地址,
也即将将要执行的指令代码。
由执行引擎读取下一条指令。
进程间的通信的几种方式
-
管道(pipe)及命名管道(named pipe):
其实就是在内存中开辟的一块大小固定的缓冲区,
半双工通信,管道可用于具有亲缘关系的父子进程间的通信,
有名管道除了具有管道所具有的功能外,
它还允许无亲缘关系进程间的通信,
写满时不能再写,读空时不能再读,
没写满时不能读,没读空时不能写; -
信号(signal):
信号用于通知接收进程某个事件已经发生;
可以在任何时候发给某一进程,
而无需知道该进程的状态 -
消息队列: 消息队列是消息的链接表,
它克服了上两种通信方式中信号量有限的缺点,
具有写权限得进程可以按照一定得规则
向消息队列中添加新信息;
对消息队列有读权限得进程则可以从消息队列中读取信息; -
共享内存:
可以说这是最有用的进程间通信方式。
它使得多个进程可以访问同一块内存空间,
不同进程可以及时看到对方进程中
对共享内存中数据得更新。
这种方式需要依靠某种同步操作,如互斥锁和信号量等; -
信号量: 主要作为进程之间及同一种进程的
不同线程之间得同步和互斥手段; -
套接字: 这是一种更为一般得进程间通信机制,
它可用于网络中不同机器之间的进程间通信,
应用非常广泛。
几种方式的比较:
- 管道:速度慢、容量有限
- 消息队列:容量收到系统限制,
且要注意第一次读的时候,
要考虑上一次没有读完数据的问题。 - 信号量:不能传递复杂信息,只能用来同步。
- 共享内存:能够很容易控制容量,速度快,但要保持同步,
比如一个进程在写的时候,另一个进程要注意读写的问题
相当于线程中的线程安全。
线程间通信
(1)同步
多个线程通过synchronized通讯,类似于共享内存
(2)while轮询
线程A不断改变条件,线程B不断查看条件是否满足需求
从而实现通讯。
效率不高,因为B一直在查看,没做别的
(3)wait/notify
进入阻塞,而不是像轮询一样一直占用CPU资源
(4)管道通信
通过管道,将一个线程的消息发送个另一个线程
什么是死锁?死锁产生的条件?
1). 死锁的概念
在两个或者多个并发进程中,如果每个进程持有某种资源
而又等待其它进程释放它或它们现在保持着的资源,
在未改变这种状态之前都不能向前推进,
称这一组进程产生了死锁。
通俗的讲,就是两个或多个进程
无限期的阻塞、相互等待的一种状态。
2). 死锁产生的四个必要条件
互斥:至少有一个资源必须属于非共享模式,
即一次只能被一个进程使用;若其他申请使用该资源
,那么申请进程必须等到该资源被释放为止;
占有并等待:一个进程必须占有至少一个资源,
并等待另一个资源,而该资源为其他进程所占有;
非抢占:进程不能被抢占,
即资源只能被进程在完成任务后自愿释放
循环等待:若干进程之间形成一种头尾相接的环形等待资源关系
死锁的处理基本策略和常用方法
- 解决死锁的基本方法主要有
- 预防死锁、避免死锁、检测死锁、解除死锁 、鸵鸟策略 等。
1.死锁预防 :
只要确保死锁发生的四个必要条件中至少有一个不成立
-
打破互斥条件:允许进程同时访问某些资源。
但是,有些资源是不能被多个进程所共享的,
这是由资源本身属性所决定的,
因此,这种办法通常并无实用价值。 -
打破占有并等待条件:可以实行资源预先分配策略
(进程在运行前一次性向系统申请它所需要的全部资源,
若所需全部资源得不到满足,则不分配任何资源,
此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,
才一次性将所申请资源全部分配给该线程)
或者只允许进程在没有占用资源时才可以申请资源
(一个进程可申请一些资源并使用它们,
但是在当前进程申请更多资源之前,
它必须全部释放当前所占有的资源)
但是这种策略也存在一些缺点:
在很多情况下,无法预知一个进程执行前所需的全部资源,
因为进程是动态执行的,不可预知的;
同时,会降低资源利用率,导致降低了进程的并发性。 -
打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。
也就是说,但一个进程占有了一部分资源,
在其申请新的资源且得不到满足时,
它必须释放所有占有的资源以便让其它线程使用。
这种预防死锁的方式实现起来困难,会降低系统性能。 -
打破循环等待条件:实行资源有序分配策略。
对所有资源排序编号,所有进程对资源的请求
必须严格按资源序号递增的顺序提出,
即只有占用了小号资源才能申请大号资源,
这样就不回产生环路,预防死锁的发生。
2.死锁避免 :
死锁避免的基本思想是动态地检测资源分配状态,
以确保循环等待条件不成立,从而确保系统处于安全状态。
所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源
不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。
资源分配图算法和银行家算法是两种经典的死锁避免的算法,
其可以确保系统始终处于安全状态。
其中,资源分配图算法应用场景为每种资源类型只有一个实例
(申请边,分配边,需求边,不形成环才允许分配),
而银行家算法应用于每种资源类型可以有多个实例的场景。
3.死锁解除
死锁解除的常用两种方法为进程终止和资源抢占。
所谓进程终止是指简单地终止一个或多个进程以打破循环等待,
包括两种方式:终止所有死锁进程和一次只终止一个进程直到取消死锁循环为止;
所谓资源抢占是指从一个或多个死锁进程那里抢占一个或多个资源,
此时必须考虑三个问题:
(I). 选择一个牺牲品
(II). 回滚:回滚到安全状态
(III). 饥饿(在代价因素中加上回滚次数,回滚的越多则越不可能继续被作为牺牲品,避免一个进程总是被回滚)
银行家算法
【银行家算法】
数据结构
-
可利用资源向量Available。
这是一个含有m个元素的数组,
其中的而每一个元素代表一类可利用资源数目,
其初始值是系统中所配置的该类全部可用资源的数目,
其数值随该类资源的分配和回收而动态的改变。 -
最大需求矩阵Max。这是一个n*m的矩阵,
它定义了系统中n个进程中的每一个进程对m类资源的最大需求。
如果Max[i,j]=K;则表示进程i需要Rj类资源的最大数目为K。 -
分配矩阵Allocation。
这也是一个n*m的矩阵,
它定义了系统中每一类资源当前已分配给每一进程的资源数。
如果Allocation[i,j]=K,
则表示进程i当前已分得Rj类资源的数目为K。 -
需求矩阵Need。
这也是一个n*m的矩阵,
用以表示每一个进程尚需的各类资源数。
如果Need[i,j]=K,则表示进程i还需要Rj类资源K个,方能完成任务。
上述三个矩阵间存在下述关系:Need[i,j]=Max[i,j]-Allocation[i,j]
- 系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。
若安全,才正式将资源分配给进程Pi,以完成本次分配;
否则,将本次的试探分配作废,恢复原来的资源分配状态
,让进程Pi等待。
安全性算法模块
- 设置两个向量
① 工作向量:Work=Available
(表示系统可提供给进程继续运行所需要的各类资源数目)
② Finish:表示系统是否有足够资源分配给进程
(True:有;False:没有).初始化为False
-
若Finish[i]=False&&Need<=Work,则执行3;否则执行4(i为资源类别)
-
进程P获得第i类资源,则顺利执行直至完成,并释放资源: Work=Work+Allocation; Finish[i]=true;转2
-
若所有进程的Finish[i]=true,则表示系统安全;否则,不安全!
思想
允许进程动态地申请资源,系统在每次实施资源分配之前,
先计算资源分配的安全性,
若此次资源分配安全(
即资源分配后,系统能按某种顺序来为每个进程分配其所需的资源
,直至最大需求,使每个进程都可以顺利地完成)
便将资源分配给进程,否则不分配资源,让进程等待。
【安全序列】与【系统安全】
所谓系统是安全的,是指系统中的所有进程能够按照某一种次序分配资源,
并且依次地运行完毕,
这种进程序列{P1,P2,…,Pn}就是安全序列。
如果存在这样一个安全序列,则系统是安全的;
如果系统不存在这样一个安全序列,则系统是不安全的。
防止死锁的思路也就有了,
我们能不能用一个算法去找有没有安全序列来作为我们的参考,
只要系统按照这个安全序列去分配资源,
就可以预防死锁发生呢?
进程的几种状态
-
创建状态:进程在创建时需要申请一个空白PCB,
向其中填写控制和管理进程的信息,完成资源分配。
如果创建工作无法完成,比如资源无法满足,
就无法被调度运行,把此时进程所处状态称为创建状态 -
就绪状态:进程已经准备好,已分配到所需资源,
只要分配到CPU就能够立即运行 -
执行状态:进程处于就绪状态被调度后,进程进入执行状态
-
阻塞状态:正在执行的进程由于某些事件
(I/O请求,申请缓存区失败)
而暂时无法运行,进程受到阻塞。
在满足请求时进入就绪状态等待系统调用 -
终止状态:进程结束,或出现错误,
或被系统终止,进入终止状态。
无法再执行
线程有几种状态?
-
新建(NEW):新创建了一个线程对象。
-
可运行(RUNNABLE):
线程对象创建后,其他线程(比如main线程)
调用了该对象的start()方法。
该状态的线程位于可运行线程池中,
等待被线程调度选中,获取cpu 的使用权 。 -
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
-
阻塞(BLOCKED):
阻塞状态是指线程因为某种原因放弃了cpu 使用权,
也即让出了cpu timeslice,暂时停止运行。
直到线程进入可运行(runnable)状态,
才有机会再次获得cpu timeslice 转到运行(running)状态。
阻塞的情况分三种:
(一). 等待阻塞:
运行(running)的线程执行o.wait()方法,
JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:
运行(running)的线程在获取对象的同步锁时,
若该同步锁被别的线程占用,
则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:
运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,
或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、
或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,
6. 或者因异常退出了run()方法,则该线程结束生命周期。
7. 死亡的线程不可再次复生。
在给定的时间点上,一个线程只能处于一种状态。
(内存管理)分页和分段有什么区别?
段式存储管理是一种符合用户视角的内存分配管理方案。
在段式存储管理中,将程序的地址空间划分为若干段(segment),
如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,
相互独立,互不干扰。
段式管理的优点是:没有内碎片
(因为段大小可变,改变段大小来消除内碎片)。
内部碎片是已经被分配出去的的内存空间
大于请求所需的内存空间
但段换入换出时,会产生外碎片
外部碎片是指还没有分配出去,
但是由于大小太小而无法分配给
申请空间的新进程的内存空间空闲块。
(比如4k的段换5k的段,会产生1k的外碎片)
页式存储管理方案
是一种用户视角内存与物理内存相分离的内存分配管理方案。
在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),
而物理内存划分为同样大小的帧,
程序加载时,可以将任意一页放入内存中任意一个帧,
这些帧不必连续,从而实现了离散分离。
页式存储管理的优点是:
没有外碎片(因为页的大小固定),
但会产生内碎片(一个页可能填充不满)。
两者的不同点:
-
目的不同:分页是由于系统管理的需要而不是用户的需要,
它是信息的物理单位;
分段的目的是为了能更好地满足用户的需要,
它是信息的逻辑单位,
它含有一组其意义相对完整的信息; -
大小不同:页的大小固定且由系统决定,
而段的长度却不固定,
由其所完成的功能决定; -
地址空间不同: 段向用户提供二维地址空间;
页向用户提供的是一维地址空间; -
信息共享:段是信息的逻辑单位,
便于存储保护和信息的共享,
页的保护和共享受到限制; -
内存碎片:
页式存储管理的优点是没有外碎片(因为页的大小固定),
但会产生内碎片(一个页可能填充不满);
而段式管理的优点是没有内碎片
(因为段大小可变,改变段大小来消除内碎片)。
但段换入换出时,会产生外碎片
(比如4k的段换5k的段,会产生1k的外碎片)。
虚拟内存
虚拟内存允许执行进程不必完全在内存中。
虚拟内存的基本思想是:
每个进程拥有独立的地址空间,
这个空间被分为大小相等的多个块,
称为页(Page),
每个页都是一段连续的地址。
这些页被映射到物理内存,
但并不是所有的页都必须在内存中才能运行程序。
当程序引用到一部分不在物理内存中的地址空间时
由操作系统负责将缺失的部分装入物理内存
并重新执行。
这样,对于进程而言,
逻辑上就有很大的内存空间,
虚拟内存实际上可以比物理内存大。
如果虚拟内存的页并不存在于物理内存中
会产生缺页中断,从磁盘中取得缺的页放入内存,
如果内存已满,
还会根据某种算法将磁盘中的页换出。
页面置换算法
-
FIFO先进先出算法:在操作系统中经常被用到,
比如作业调度(主要实现简单,很容易想到); -
LRU(Least recently use)最近最少使用算法:
根据使用时间到现在的长短来判断; -
LFU(Least frequently use)最少使用次数算法:
根据使用次数来判断; -
OPT(Optimal replacement)最优置换算法:
理论的最优,理论;就是要保证置换出去的是不再被使用的页,
或者是在实际内存中最晚使用的算法。
逻辑地址、物理地址、虚拟内存
-
物理地址:它是地址转换的最终地址,
进程在运行时执行指令和访问数据最后都要
通过物理地址从主存中存取,
是内存单元真正的地址。 -
逻辑地址:是指从应用程序角度看到的内存地址,又叫相对地址。
编译后,每个目标模块都是从 0 号单元开始编址,
称为该目标模块的相对地址或逻辑地址。
不同进程可以有相同的逻辑地址,
因为这些相同的逻辑地址可以映射到主存的不同位置。
用户和程序员只需要知道逻辑地址。 -
虚拟内存:虚拟内存是一些系统页文件,
存放在磁盘上,每个系统页文件大小为 4K,
物理内存也被分页,每个页大小也为 4K,
这样虚拟页文件和物理内存页就可以对应,
实际上虚拟内存就是用于物理内存的临时存放的磁盘空间。
页文件就是内存页,物理内存中每页叫物理页,
磁盘上的页文件叫虚拟页,
物理页+虚拟页就是系统所有使用的页文件的总和。
颠簸
颠簸本质上是指频繁的页调度行为,
具体来讲,进程发生缺页中断,这时,必须置换某一页。
然而,其他所有的页都在使用,
它置换一个页,但又立刻再次需要这个页。
因此,会不断产生缺页中断,导致整个系统的效率急剧下降,
这种现象称为颠簸(抖动)。
-
内存颠簸的解决策略包括:
-
如果是因为页面替换策略失误,
可以修改替换算法来解决这个问题; -
如果是因为运行的程序太多,
造成程序无法同时将所有频繁访问的页面调入内存,
则要降低多道程序的数量; -
否则,还剩下两个办法:
终止该进程或增加物理内存容量。
虚拟内存的应用与优点
虚拟内存很适合在多道程序设计系统中使用,
许多程序的片段同时保存在内存中。
当一个程序等待它的一部分读入内存时,
可以把CPU交给另一个进程使用。
虚拟内存的使用可以带来以下好处:
在内存中可以保留多个进程,
系统并发度提高
解除了用户与内存之间的紧密约束,
进程可以比内存的全部空间还大
操作系统中进程调度策略有哪几种?
-
FCFS(先来先服务,队列实现,非抢占的):
先请求CPU的进程先分配到CPU -
SJF(最短作业优先调度算法):
平均等待时间最短,但难以知道下一个CPU区间长度 -
优先级调度算法(可以是抢占的,也可以是非抢占的):
优先级越高越先分配到CPU,相同优先级先到先服务,
存在的主要问题是:低优先级进程无穷等待CPU,
会导致无穷阻塞或饥饿;解决方案:老化 -
时间片轮转调度算法(可抢占的):
队列中没有进程被分配超过一个时间片的CPU时间,
除非它是唯一可运行的进程。
如果进程的CPU区间超过了一个时间片,
那么该进程就被抢占并放回就绪队列。 -
多级队列调度算法:
将就绪队列分成多个独立的队列,
每个队列都有自己的调度算法,
队列之间采用固定优先级抢占调度。
其中,一个进程根据自身属性被永久地分配到一个队列中。 -
多级反馈队列调度算法:
与多级队列调度算法相比,
其允许进程在队列之间移动:
若进程使用过多CPU时间,
那么它会被转移到更低的优先级队列;
在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,
以防止饥饿发生。
进程同步与互斥
- 互斥:
指某一个资源同时只允许一个访问者对其进行访问,
具有唯一性和排它性。
但互斥无法限制访问者对资源的访问顺序,即访问是无序的 - 同步:是指在互斥的基础上(大多数情况下),
通过其它机制实现访问者对资源的有序访问。
大多数情况下,同步已经实现了互斥,
特别是所有写入资源的情况必定是互斥的。
少数情况是指可以允许多个访问者同时访问资源。
同步:体现的是一种协作性。互斥:体现的是排它性。
同步、异步、阻塞、非阻塞的区别
同步和互斥
-
同步体现的是一种协作性,互斥体现的是一种排他性
-
同步:就是并发的线程在一些关键点上可能需要互相等待与互通信息,
这种相互制约的等待与互通信息称为进程(线程)同步。 -
互斥:是指某一资源同时只允许一个访问者对其进行访问,
具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,
即访问是无序的。
同步、异步【关注的是消息通信机制】
-
同步: 就是指调用者会主动等待调用的返回结果。
-
异步: 就是指调用者不会主动等待调用结果,
而是在调用发生后,
被调用者通过状态、通知来通知调用者。
阻塞、非阻塞 【关注的是程序在等待调用结果(消息,返回值)时的状态】
-
阻塞: 是指调用结果返回前,当前线程会被挂起,即阻塞。
-
非阻塞: 是指即使调用结果没返回,也不会阻塞当前线程。
进程同步有哪几种机制:
-
信号量机制
一个信号量只能置一次初值,
以后只能对之进行p操作或v操作。
由此也可以看到,信号量机制必须有公共内存,
不能用于分布式操作系统,这是它最大的弱点。 -
自旋锁
旋锁是为了保护共享资源提出的一种锁机制。
调用者申请的资源如果被占用,
即自旋锁被已经被别的执行单元保持,
则调用者一直循环在那里看是否该自旋锁的保持着已经释放了锁,
自旋锁是一种比较低级的保护数据结构和代码片段的原始方式,
可能会引起以下两个问题;
(1)死锁
(2)过多地占用CPU资源
-
管程
信号量机制功能强大,但使用时对信号量的操作分散,
而且难以控制,读写和维护都很困难。
因此后来又提出了一种集中式同步进程——管程。
其基本思想是将共享变量和对它们的操作集中在一个模块中,
操作系统或并发程序就由这样的模块构成。
这样模块之间联系清晰,便于维护和修改,易于保证正确性。 -
会合
进程直接进行相互作用
- 分布式系统
由于在分布式操作系统中没有公共内存,
因此参数全为值参,而且不可为指针。
线程同步的方式
-
临界区:使用临界区对象。
拥有临界区对象的线程可以访问被保护起来的资源或代码段,
其他线程若想访问,则被挂起,
直到拥有临界区对象的线程放弃临界区对象为止
【只用于同一进程】 -
互斥量 Synchronized/Lock:
采用互斥对象机制,
只有拥有互斥对象的线程才有访问公共资源的权限。
因为互斥对象只有一个,
所以可以保证公共资源不会被多个线程同时访问 -
信号量 Semphare:它允许同一时刻多个线程访问同一资源,
但是需要控制同一时刻访问此资源的最大线程数量
信号量(semaphore)的数据结构为一个值和一个指针,
指针指向等待该信号量的下一个进程。
信号量的值 S 与相应资源的使用情况有关。
当 S 大于 0 时,表示当前可用资源的数量;
当 S 小于 0 时,其绝对值表示等待使用该资源的进程个数。
注意,信号量的值仅能由 PV 操作来改变。
执行一次 P 操作意味着请求分配一个单位资源,
因此S的值减1;当 S < 0 时,表示已经没有可用资源,
请求者必须等待别的进程释放该类资源,它才能运行下去。
而执行一个 V 操作意味着释放一个单位资源,
因此 S 的值加 1;若 S < 0,表示有某些进程正在等待该资源,
因此要唤醒一个等待状态的进程,使之运行下去。 -
事件(信号),Wait/Notify:
通过通知操作的方式来保持多线程同步,
还可以方便的实现多线程优先级的比较操作
局部性原理
(1). 时间上的局部性:最近被访问的页在不久的将来还会被访问;
(2). 空间上的局部性:内存中被访问的页周围的页也很可能被访问。
中断和轮询
-
中断的定义
指在计算机执行期间,
系统内发生任何非寻常的或非预期的急需处理事件,
使得CPU暂时中断当前正在执行的程序而
转去执行相应的事件处理程序,
待处理完毕后又返回原来被中断处继续执行
或调度新的进程执行的过程 -
轮询的定义
定时对各种设备轮流询问一遍有无处理要求
临界区和冲突解决
-
临界资源的定义:
一次仅允许一个进程使用的资源 -
临界区的定义:
每个进程中访问临界资源的那段程序 -
解决冲突:
如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入
任何时候,处于临界区内的进程不可多于一个
进入临界区的进程要在有限时间内退出,
以便其它进程能及时进入自己的临界区
如果进程不能进入自己的临界区,则应让出CPU,
避免进程出现“忙等”现象
缓冲区溢出
-
缓冲区溢出的定义:
指当计算机向缓冲区内填充数据时超过了缓冲区本身的容量,
溢出的数据覆盖在合法数据上 -
缓冲区溢出的危害:
程序崩溃导致拒绝服务、
跳转并且执行一段恶意代码 -
缓冲区溢出的原因:
程序中没有仔细检查用户输入的参数