6 CPU管理的直观想法
6.1 CPU的工作原理
(1)CPU-不断的自动的取指执行:
- 程序存放在内存里;
- 设置PC指针,CPU根据PC=50发出一个取指命令;CPU把50放在地址总线上,
- 内存将地址50的指令由总线传回CPU;
- CPU解释执行这个指令,从地址100的地方取出数值给ax;
- PC自动+1;
因此,只需要给一个初始地址,接下来CPU就开始不断的取指执行取指执行;
6.2 管理CPU的最直观的方法
1、设好PC初值就可以!!
2、存在什么问题???
(1)将一条IO指令(fprintf)替换为一条计算指令,IO指令10的6次方,**IO指令慢很多,**CPU工作是在电路上工作较快,IO指令需要磁盘,机械设备,会比电路慢很多;
**(2)假设有10的6次方条计算指令+一条IO指令,那么此时CPU就需要一直等待IO指令完成;**例如从磁盘上读取数据;此时CPU的效率就很低,这个时候10的6次条计算指令才50%的利用效率,如果几十条,效率就几近于0!!
3、解决方案—多个程序在内存中,多道程序交替执行
(1)一个CPU上交替的执行多个程序:并发,管理CPU从并发开始
4、并发处理,不只是改变寄存器PC的值,同时还需要更改相应的信息结构
5、运行的程序与静态程序不一样!—进入“进程”概念
(1)**CPU来回切换就需要记住运行程序的样子!!!**因此需要进程;
6.3 多进程图像–操作系统要管理CPU
(1)启动了的程序就是进程,所以是多个进程推进;操作系统只需要将这些进程记录号,要按照合理的次序推进**(分配资源,进行调度);**
1、多进程图像从启动开始到关机结束
2、!!!用户启动计算就就是启动了一堆进程,计算机管理就是管理进程!!!
6.3.1 多进程图像:多进程如何组织?—PCB
(1)PCB—Process Control Block—用来记录进程信息的数据结构
(2)多个进程对应的不同PCB在不同的位置等待队列;
6.3.2 多进程的组织:PCB+状态+队列
(1)将进程根据状态进行分类,操作系统做好这种控制,根据状态转换来进程切换
6.3.3 多进程如何交替:队列操作+调度+切换?
(1)比如一个进程需要启动磁盘读写(IO指令),
- 然后将其状态设置为阻塞态;
- 并将这个进程放在磁盘等待队列中,
- 调用**schedule();**schedule()函数中使用getNext进行调度,
- 找出下一个进程,并switch_to交换进行新的进程pNew;
(2)操作系统都是通过PCB感知进程!
(3)交替的三个部分:队列操作+调度+切换
(4)调度:
(5)切换:汇编代码实现精细控制
6.3.4 多进程如何影响–地址空间分离?
(1)因为多个进程会同时被放在内存中;例如进程1要求修改地址100的数值,结果进程2的代码就在地址100处,这样进程2就会被修改;就会导致进程2崩溃出错!
(2)DPL和CPL是为了保护操作系统的设置,这个进程都属于用户态,CPL=3;DPL=3;
(3)解决方法:限制对地址100的读写
(4)多进程的地址空间分离:内存管理的主要内容;
(5)进程带动内存的使用—即使相同,映射后也不会相同,实现共存
6.3.5 多进程图像如何合作?
(1)具体实例:生产者-消费者实例–共享缓冲区buffer,生产者进程往buffer里放内容,消费者往buffer里取内容;counter是判断buffer是否满了的重要依据;
(2)两个合作的进程都要修改counter,有可能右侧交替执行,导致counter不正确;
(3)解决办法:核心在于进程同步(合理的推进顺序)–不能想切换就切换
6.3.6 小结
7 操作寄存器完成切换
7.1 用户级线程(多进程切换)
7.1.1 资源不动而切换指令序列?资源->地址映射表(内存)
(1)在切换中,不更改(映射表)资源,只是更改指令;称为线程切换;
(2)实质就是映射表不变而PC指针变;
7.1.2 线程的意义—一个例子
(1)不依次下载网页文本,图片等,而是多线程同时下载文本和图片,此时,多进程对应的地址映射表不需要更换,因为本身就是在同一个地方进行操作;因此,线程切换在此处十分有意义!;
7.1.3 实现线程切换的核心代码
(1)char URL[],char buffer[1000]创建共享缓冲区;pthread_create()创建线程,每个线程执行一段程序;void GetData()下载从网站上数据包,Show()从缓冲区里取出内容,显示到显示器上;核心就是启动多个线程,执行相应函数;重点在于如何交替进行!!线程切换是Yield()函数,创建进程是Create()函数
7.1.4 Create?Yield–线程切换是重中之重!!
(1)注意,问题在于当D()第二个调用Yield()时,右侧Yield找到204,回到第一次切换的位置,继续执行,然后遇到B的},}在汇编中就是弹栈的意思,此时404被弹出,PC指向404因此出现错误!!!;
(2)因为只能在同一个线程里跳来跳去,左侧104和204是一个线程,右侧304和404是一个线程,**注意,A调用B不是线程切换,只是同一线程中的函数调用而已!!!**此处出现错误,是因为,两个不同的线程共用了同一个栈!!
(3)每个线程的函数调用,应该有自己的栈!
(4)一个栈到两个栈的伟大过渡!!!
(5)Yield()切换线程同时也要更换栈,栈的信息存储在全局的数据结构TCB中;下方的代码指的是红色的第二个出现的Yield();
(6)第二个Yield()执行时,每次都jmp 204跳走了,一直没有执行到Yield的}弹栈,204就没有从栈中被弹出来!!每次都直接Jmp到204,无法弹栈。因为刚好就是要返回Field()的下一条语句,就删除Jmp=204;指令,直接弹栈返回;
(7)Yield()因此只更新/切换栈就可以,执行弹栈,直接就回去了,不需要切换PC,用Yied()的}返回弹栈就可以
(8)总结: 用Yield()+栈相互配合,实现栈的切换,返回的PC压在栈中,所以栈切换,再一弹栈,PC就跟着切换了!!
7.1.5 两个线程的样子:两个TCB、两个栈、切换的PC在栈中
(1)创建线程ThreadCreate就是做出切换的样子,三样东西,分别是TCB,栈和切换的PC在栈中;
(2)创建线程ThreadCreate流程:
*** 首先申请一个TCB,**
*** 再申请一个栈stack,**
*** 再在栈中填入PC内容,**
*** tcb.esp=stack将TCB与栈关联;**
7.1.6 所有东西组合—用户级线程的完整样子
7.1.7 Yield是用户程序,是用户级线程
(1)操作系统感知不到这个线程切换的存在;缺点在于如果要访问硬件IO,就必须通过操作系统,因为操作系统就是管理硬件的,操作系统在切换进程时,就看不到之前的线程(存在于之前的进程中),因此会切换到别的进程中;
(2)核心级线程—线程就在内核中,TCB也在内核中,内核知道TCB的存在;
(3)之前的栈都是用户栈,field,create都没有进入到内核中;而在内核中,称为Schedule()为了和用户级进行区别,
(4)用户级线程是核心级线程的一个基础!
7.2 内核级线程
7.2.1 核心级线程的重要性
- 进程的切换分为两个部分,一部分是切换指令(线程的切换)和切换资源(映射表)(内存管理讲);进程必须在内核中,没有用户级进程这一说法,进程切换必须是在内核中进行,是内核级线程,但是用户级线程切换是内核级线程的一个基础;因为用户态是不可以访问计算机硬件资源的,只能是线程,只有在内核态才可以!
- 往往在计算机中,进程,用户级线程,内核级线程都存在,可以充分发挥
- 多核要充分发挥作用,必须支持核心级线程;多核就是多个CPU,多处理器是每个CPU有自己的一套映射,只有到内核中,不同线程可以执行在多个核,如果是多进程,就只有一套MMU,CPU也并行不起来;多核充分发挥就是要并行,同时执行;注意与并发区分,并发是同时启动,交替执行;
7.2.2 和用户级相比,核心级线程的不同之处
(1)两个栈到两套栈,核心级线程意味着要进入内核,在内核态中也有内核栈,用户态有用户栈,因此每个核心级线程需要一套栈(用户栈+内核栈);
(2)用户级线程是通过切换PCB来切换用户栈,核心级线程是通过切换TCB来切换一套栈(用户栈+内核栈);
(3)进入内核的唯一方法是中断;
(4)INT中断,也可以是硬件中断,中断出现之后就会到内核栈(这个由硬件自动完成);
7.2.3 一个具体实例
(1)sys_read是内核函数,开始执行内核操作;
(2)在内核中的切换如下;
(3)switch—to(cur,next),cur是当前的TCB,next是下一个内核级现成的TCB;
(4)找到TCB,切换TCB来切换栈,根据弹栈恢复切换PC指 针;
(5)一直执行的是用户态函数,偶尔需要到内核态执行函数,会返回,在中断进来的时候,右侧栈就是压入了之前的SS:SP。PC和CS段寄存器基址,切回来的时候,弹栈就会返回到用户态;????应该是一段包含iret()中断返回的代码;
(6)找到TCB,切换内核栈(套)内核栈关联着自己的线程的用户栈,再使用Iret()中断返回指令,回到用户栈;
7.2.4 内核线程switch_to的五段论
(1)核心级线程是两套栈的切换;
7.2.5 ThreadCreate!做成切换的样子
(1)需要有用户栈,内核栈和TCB;
(2)首先申请一段内存作为TCB;再申请一段内存作为内核栈并初始化;目前处于内核态,优先级很高,所以可以申请一段用户栈,指针连接号,将函数地址500和CS写入,准备好;
7.2.6 用户级线程、核心级线程的对比
7.3 内核级线程实现(代码)
7.3.1 核心级线程的两套栈,核心是内核栈
(1)fork会变成右侧 int 0x80中断指令;
(2)在INT 0x80执行中断时,PC自动+1指向mov res,%eax;在中断时,CPU会找到内核栈,并指向当前的用户栈;当前的CS:IP压入内核栈中;
7.3.2 切换五段论中的中断入口(第一段)和中断出口(第五段)
(1)system_call继续压栈(用户态寄存器)保护用户态现场;
(2)接下执行sys_fork,在sys_fork()执行过程中,可能有内核级切换的情况(中间的三段论);
(3)current是当前线程的PCB,cmpl看当前的PCB的state是否等于0;0表示就绪,**非0一定表示阻塞;**当前进程阻塞就会执行reschedule重新调度,schedule就是中间三段论,调度函数;
(4)第五段:中断出口:切换完成后,reschedule()重新调度,会首先将ret_from_sys_call压栈,然后会执行schedule调度函数,schedule是C函数,会有有括号}会有弹栈操作,完成后会调回ret_from_sys_call:系统中断/调用返回,执行中断返回函数;
7.3.3 切换五段论中的schedule和中断出口(最后一段)
(1)中断入口是一堆压栈Push,右侧左侧是int 中断自动push,右侧右侧是人工push用户寄存器;中断出口自然是一堆pop弹栈操作;
(2**)schedule()是调度找下一个核心级线程栈(调度算法),next是下一个TCB,TCB转换数据就可以,难点在于根据TCB完成核心栈的切换,根据核心栈完成用户栈的切换;中**间三段,稍后讲,schedule一定要记得返回ret_from_sys_call就是五段论中的最后一段,中断出口;
(3)ret_from_sys_call就是最后一段,一堆Pop,pop完成后iret一起Pop就是返回下一个核心线程的用户栈;
(4)五段论代码分布在操作系统不同地方;
7.3.4 切换五段论中的switch_to
(1)假定已经执行调度算法找到next,现在执行switch_to函数进行切换;
(2)基于TSS,任务结构切换,将其转化为现在的核心级栈切换(目前流行的);TSS代码就只有一句ljmp %0\t\t完成切换,但是执行效率低;
(3)tss是一个段segment,描述符就是指向段的指针,每一个段都有段描述符;有描述符就会有选择子TR,TR(Task Register)是一个固有寄存器用来找到描述符;
(4)TSS(n)就是下一个新的TR,指向新的TSS段;整个任务的内容都(所有CPU的寄存器信息)都用新的TSS扣上,原来的所有状态要定格在原tss;
(5)这里是内嵌汇编,**汇编的格式就是->代码:输出:输入;**ESP就是栈,TSS就是通过这样的方式,完成了栈ESP的切换;其实TSS就是TCB的一个子段;之前需要弹栈找EIP,这里也不需要,因为直接有EIP,就完成了EIP的切换;但是,这个方法慢,同时要换很多内容,同时,因为一条指令执行,不能通过指令流水实现(硬件加速),因此效率慢;最后通过iert中断返回函数,完成用户栈的切换;这便是中间的三段;
(6)小结:五段论核心代码3句话:
- int 0x80;中断
- switch_to中的ljmp;TSS跳转切换
- iret()中断返回;
7.3.5 第二个故事ThreadCreate()做出切换的样子
(1)ThreadCreate()创建新的核心级线程;
(2)核心:设置好TSS;PCB线程控制块+内核栈+TSS;
(3)fork是两股,父进程创建子进程;call _copy_process就是把父进程的参数进行拷贝;copy——process的参数与栈中的寄存器一一对应;
7.3.6 copy_process的细节
(1)get_free_page()获得了一页内存;这里不可以用malloc,malloc是用户态代码,现在在内核态;找到mem_map==0的那一页,返回给get-free—page()并进行强制转换,就有了PCB;
(2)接下来代码就是设置TSS,将TSS初始化好;其中esp0是内核栈,esp是用户栈;其中ss和esp是传入的参数,就是和父进程相同共用栈;用户栈一样,内核栈不同,PCB不同;
(3)上述代码就完成了
- 创建TCB;
- 创建内核栈,用户栈,并初始化;
- 关联栈和TCB;
**(1)eip就是Int 指令执行完的下一句地址,eip也是从父进程那里传入的参数;**与父进程一致;
(1)父子进程返回都在mov res,%eax,但是子进程的eax==0,父进程则不等于0;
7.3.6 第三个故事:给进程传递函数,执行我们的想要的代码
(1)exec(cmd)是系统调用,未执行exec之前,父子进程执行相同代码,一旦子进程进入exec,子进程再执行完返回时,就要执行LS->entry代码,子进程返回就是中断返回,中断返回要执行iret()指令,iret()就是要将栈中存储的eip赋值给真正的eip寄存器==PC;而我们需要让PC执行entry;联系两者,就是找到entry指令地址赋给ret(eip);在弹栈后,就顺利执行entry
(2)do_execve是一个参数压栈;参数就是EIP(%esp),%eax;其中esp就是当前执行指针;EIP是一个偏移;将EIP加给esp,赋给eax进行压栈pushl %eax;
(1)LS entry是一个可执行文件,在磁盘上,可以读取到其地址;
7.4 总结图(!重要!)
8 操作系统之“树”
分别创建打印AB的两个子进程
父进程wait()就是将自己设置为阻塞态,开始scheduel()两个打印的子进程;
9 CPU调度
9.1 CPU调度策略–常见策略
9.1.1 多进程图像与CPU调度
9.1.2 CPU调度(进程调度)直观想法
(1)FIFO先进先出(最简单);有很多方面考虑;
9.1.3 如何设计调度算法?
(1)切换时间完全是系统内耗;切换次数多就会造成系统内耗大;
9.2 各种调度算法
9.2.1 FCFS先来先服务(First Come,First Served)
9.2.2 SJF:短作业优先
9.2.4 轮转调度RR
(1)时间片T越小,响应时间越快;一个操作系统中进程数量n也不可以太大;
9.2.5 设置优先级
(1)前台任务更关心响应时间,采用RR轮转调度,减少响应时间;后台任务更关心周转时间,采用SJF短作业优先,前台后台采用不同的调度算法;通过设置优先级,前台任务优先于后台任务;
(2)问题:后台任务有可能一直被搁置!!–解决办法是将动态调整优先级
(3)但是后台任务一旦获得优先级,后台任务一般都是时间长的,这个时间前台的响应时间又无法得到保证,**处于矛盾;**所以后台任务也需要时间片,那后台任务的SJF??
(4)同时考虑前台任务先完成,和后台任务短作业优先;重点在于!!折中!!
9.3 一个实际的Schedule函数
9.3.1 Linux 0.11的调度函数schedule()
其目标在于找到下一个next,决定哪个进程下一个执行
(1)TCB的实现是数组,这里是从最后开始依次向前检索,判断TADK_RUNNING表示就绪态,如果是就绪,并且counter>c,将c赋值为counter;其实这个过程就是寻找最大优先级的过程!!
(2)每次调度给最大counter的那个进程**;—优先级算法;同时,counter是一个时间片,就综合了时间片和优先级算法;**
(3)如果就绪态的counter(时间片)都用完了counter==0,就将所有进程的counter右移一位(除以2)再加上counter的初值;对于就绪态就是counter初值,对于刚刚阻塞态(执行IO)进程,就是自己的counter/2+counter初值一定会大于刚刚的就绪态,会优先执行刚刚是阻塞态的进程;
9.3.2 counter的两个作用:时间片+优先级
(1)每次时钟中断就要修改counter;每次counter–,当counter==0就进行切换;是典型的时间片流转;
(1)第二段代码就是优先级的动态调整;因此一个进程在阻塞态中呆的时间越久,那counter
优先级就会越高;
(1)虽然代码简单,但是很好地权衡;
(2)分析counter时间片大小是否有界(保证响应时间);最大也就收敛到2P;
10 进程同步与合作
10.1 进程同步与信号量
多进程之间变得合理有序
10.1.1 一个例子,多进程共同完成一个任务
(1)两个进程需要合作完成;每个进程有自己的执行体系,但是不是随便执行,有时需要等待;等待信号就是进程同步,合理有序的向前推进,关键在于信号!;
10.1.2 生产者-消费者实例
(1)生产者和消费者共享一块缓存,生产者不断生产往里放,counter++;消费者不断取,counter–;多进程合作;
(2)注意是用户态程序
(1)信号表达信息有限,只能判断有还是没有;因此,使用信号量来表达更丰富的信息
**(1)此处还需要知道sleep的进程数量;不然就不能释放后来的进程;**counter不足以满足要求,还需要别的量记录此时睡眠的数量,决定发唤醒信号的次数;
10.1.3 从信号到信号量
(1)设计一个信号量来记录睡眠数量,决定唤醒信号的发送次数,而不是通过counter决定
(1)根据信号量sem来决定操作(等待还是执行);
10.1.4 信号量的定义(实现)
(1)信号量的实现,就是一个结构,value就是刚刚的变量,同时要关联一个队列PCB queue;*
(2)同时,通过右侧函数P来判断消费资源和V产生资源;
10.1.5 用信号量解生产者-消费者问题
(1)首先考虑,停止的条件,对于生产者,停止条件:缓冲区满,信号量emptyBUFFER_SIZE,empty表示空闲缓冲区,生产者要根据P(empty)判断是否满了(没有空闲缓冲区empty0);对应的,哪里会增加空闲缓冲区个数呢?对应的就是消费者V(empty);
(2)同样,消费者的停止条件,实际内容为0,定义一个信号量表示没有内容了,要停在0这个地方,因为P(full)判断是否为0;对应的,生产者增加fullV(full);
(3)P(mutex)表示互斥,生产者执行时,消费者不能进入,只有一个进程,因此引入mutex称为互斥信号量;
10.2 信号量临界区保护
!!依靠临界区保护信号量,依靠信号量来保证进程同步!!
(1)为什么要保护信号量?(2)怎么保护信号量?
10.2.1 共同修改信号量引出的问题
不是编程错误,而是因为共享数据没有保护信号量没有保护,造成的竞争错误
(1)右侧图说在增加空循环,但是每次调度情况不一样,空循环也无法解决;
10.2.2 解决方法—上锁
(1)称为“原子操作”不可分割,一旦获得权限就做完,别的进程不可以插手;进程1在进入修改empty的代码区时,进程2不可以进入它自己的修改empty的代码区;
10.2.3 临界区(Critical Section)
(1)读写信号量的代码一定是临界区;
(2)生产者P1在执行自己的临界区代码时,生产者P2一定不可以进入自己的修改信号量的临界区代码
(3)必须对临界区代码加以保护!
10.2.4 临界区代码的保护原则
1、进入临界区的第一个尝试—轮换法
(1)满足互斥进入要求,但是不满足有空让进的要求;
2、进入临界区的有一个尝试—标记法
3、进入临界区的再一次尝试—非对称标记
10.2.5 临界区Peterson算法
10.2.6 多个进程,面包店算法
10.2.7 阻止调度,阻止中断
1、开关中断法(只适用于单核,简单系统)
(1)只有中断才会有调度函数;cli()关中断,sti()开中断;
(2)多CPU不可以,中断是CPU每执行完一条指令后检查INTR中断寄存器中一个标记位是否为1来判断是否有中断;cli()就是CPU不检查INTR就是关闭中断;
(3)虽然关闭了当前运行CPU的中断,但是中断会都将其置1,那么右侧CPU就无法组织其响应中断;
2、硬件原子指令法
(1)上锁,就是检查一个变量是否符合数值执行;一个锁就对应一个变量;
(2)做一个修改mutex的指令,这个是原子指令(不需要别的变量来保护,硬件自己就保护好)(一条指令就不会被别的打扰);根据mutex来判断是否可以进入临界区;
10.3 信号量的代码实现
10.3.1 实验五练习
(1)大家可以写用户态代码,写信号量的系统调用来实现上层应用程序的进程同步!上述代码就是
(2)还是生产者-消费者例子,右侧producer.c是生产者程序,要申请一个信号量,要通过内核申请,一个信号量包括(value+PCB的一个队列指针),value是多个进程都可以看到的变量,应该放置在内核中,而且PCB是内核的数据结构,因此要通过系统调用进入内核;
(3)左侧就是在内核中定义一个信号量结构,里面的char name[20]就是信号量名字“empty",打开信号量,都可以看到value;
(4)右侧(2)就循环5次,在fd文件中写出5个数,每个数5个字节,sem_wait(sd)判断有无空闲缓冲区;sem_wait具体工作是:根据传入的sd找到value,并–,如果value<0,就是没有了;就设置为阻塞态,并加入一个等待队列,调用schedule函数切换next进程;
(5)cli()和sti()是开关中断,用来保护信号量value;
10.3.2 Linux0.11那里的代码实现
(1)bread用来读磁盘块;申请一块缓冲区buffer_head,将磁盘读取的内容通过总线DMA读入buffer,这个时候需要置为阻塞态,停在buffer,buffer自带一个信号量b_lock,如左下角,
(2)一个是while,一个是if(之前的)
(3)从sleep_on开始,睡眠就是将自己阻塞,然后放在队列中,调用schedule(),然后switch_to调用其他进程next;
*(4)tmp= p;*p=current;这两句是隐蔽的形成了队列,**如下图所示:
(1)struct task_struct *tmp;申请一个局部变量tmp;传入队列参数,就是一个指向队列的指针;struct task_struct **p是一个指针的指针;让局部变量等于 *p;因此如右侧,temp就指向了task_struct; *p=current,(红色的current);符合常识,就是当前进程的PCB指向队列首端;
(1)唤醒工作由中断执行;磁盘中断执行end_request(1),unlock_buffer,执行bh->b_lock=0(锁开了);执行wake_up唤醒函数;
(2)wake_up就是将 * *p当前队首,的进程的state==0.让其变为就绪状态,这是唤醒了队首的进程;一个进程被唤醒就会从之前停下的状态开始执行,就是执行右侧两句话,tmp指向阻塞队列中的下一个进程,tmp->state=0就唤醒了下一个进程;下一个被唤醒的进程也会从之前停下的地方,就会唤醒下下个进程,所有阻塞队列中的进程都会被唤醒;
(3)while()就是将阻塞队列中进程全部唤醒的机制,而if是将阻塞队列中队首进程唤醒的机制;
(4)为什么要唤醒所有进程?如果if第一个进程总是先执行,会导致后面进程(优先级更高)会一直等待;所有唤醒之后,就让优先级最高的进程执行;
(5)此处while()就是判断所有唤醒进程,选择最高优先级进程进入,执行sleep_on,然后将bh->b_lock=1,所以低优先级进程继续睡眠;如果是if第一个进程进入直接执行,别的进程就无法唤醒执行;
(6)while不需要记录阻塞队列中等待进程的个数;
10.4 死锁处理
10.4.1 生产者-消费者中死锁的存在
(1)**如果将申请empty和mutex次序更换,就会发生死锁;**empty空闲区的个数;
(2)mutex是1,P(mutex)会将其变为0,假设empty=0;P(empty)会变成-1,生产者会阻塞;
对于消费者开始执行,P(mutex)从0->-1,消费者也变成阻塞;mutex是全局信号量,用来互斥两个进程;
(3**)左侧想要改变阻塞态,需要右侧V(empty);右侧V(empty)需要上方mutex,mutex需要执行左侧V(Mutex);形成循环死锁;—环路等待;**
(4)假设再来一个进程,需要mutex也会一起被锁;如果进程都被锁,计算机就死机了;
10.4.2 死锁的成因及条件
10.4.3 死锁处理方法
1、死锁预防
2、死锁避免—判断请求是否引起死锁
(1)判断是否引起死锁,就是判断是否有一条可以走通的路;
(2)找出安全序列的银行家算法(Dijkstra提出)
(1)但是其时间复杂度很高,m是资源数量,n是进程数,每次有新进程都有很大的时间消耗;
3、死锁检测+恢复:发现问题再处理
(1)这种情况实现回滚是比较麻烦的;