操作系统概念 复习

OS

摘要

本文是通过学习清华大学陈渝老师的《操作系统》课程所总结的一些概念,用于备考,本文的图片也都截取自视频中,有些图片清晰度不够请理解。

正文

1. OS的启动
	操作系统启动过程:BIOS自检->加载bootloader到内存->bootloader加载OS到内存->从OS起始位置开始执行指令
	OS位于DISK中
	bootloader(512字节,很小)也位于DISK中,bootloader一般位于磁盘的第一个主引导扇区,方便BIOS进行查找和加载。bootloader功能是用于从硬盘加载OS到内存中
	BIOS-基本IO处理系统:检测外设,加载软件到内存,位于ROM
	以x86为例
	bootloader一般会被加载到于内存0x7c00处
	一加电,BIOS会从CS:IP=0xf000:fff0处开始执行,然后开始自检,然后加载bootloader到0x7c00处,并跳转到CS:IP=0000:7c00,执行bootloader,bootloader将OS加载到内存后,跳转到OS在内存中的起始位置。


2. 操作系统与设备和程序的交互
	系统调用(来源于应用程序)-同步(但完成系统调用后的返回可能是异步的)
		应用程序主动向操作系统发起服务请求,应用程序请求操作系统提供服务
		处理:等待syscall完成后继续执行应用程序
	异常(来源于不良应用程序)-同步
		应用程序意想不到的行为(它可能本身也没有意识到),非法指令或者其他不良状态(内存出错等等)
		处理:可能杀死相应应用程序或者重新执行异常的指令
		具体过程:拿到异常编号->保存现场->异常处理->恢复现场
	中断(来源于外设)-异步
		来源于不同硬件设备的计时器和网络中断
		处理:对于应用程序而言是透明的,且可能在应用程序执行过程中发生很多次中断,但应用程序并不知道,由操作系统透明完成
		具体过程:
			硬件:设置中断标记,CPU获取对应中断号
			软件(OS):保存现场->根据中断号查表执行中断处理程序->清楚中断标记->恢复现场
	应用程序不能直接访问外设,需要内核在其中充当被信任的第三方,只有内核才能执行特权指令。同时也是方便应用程序通过内核提供的接口来更方便编写操作外设的程序。即不用关注和外设具体打交道的细节,其应该由操作系统来完成。
	跨越操作系统边界产生的开销:
	- 执行时间上的开销大于程序调用
		建立中断/异常/系统调用号与其对应服务例程映射关系的初始化开销
		建立内核堆栈所带来的开销(用户态和内核态对应不同堆栈)
		验证参数的开销(用户程序传递的参数需要验证其安全性、以及需要返回返回值给用户态时的内存拷贝开销)
		内核态映射到用户态地址空间(更新页面映射权限)
		内核态独立地址空间(TLB)


3. 计算机体系结构及内存分层体系
	体系结构:CPU 内存 IO
	广义内存结构:CPU内部寄存器->L1缓存->L2缓存(这前三部分均在微处理器内部)->主存(物理内存)->磁盘(虚拟内存) 速度递减 容量递增
	操作系统需要完成的任务,可由下图非常清晰的进行说明。
	操作系统管理内存的方法:
		程序重定位、分段、分页、虚拟内存、按需分页虚拟内存
		且高度依赖于硬件,需要知道内存架构和MMU(硬件组件负责处理CPU的内存访问请求)进行辅助

1

4. 地址空间 & 地址生成
	物理内存空间——硬件支持的地址空间:从0到MAX(system)
	逻辑内存空间——一个运行的程序所拥有的内存范围:0-MAX(program)
	逻辑地址生成的过程:
	.c源程序(编译)->.s程序(汇编)->.o二进制文件(链接)->.exec可执行文件(载入内存)->运行
	其中,编译,是将c代码转换成汇编代码,但仍然是用符号(变量名)来隐式代表地址,然后汇编则是用将.s文件转换为二进制文件,此时已经使用该二进制文件的相对逻辑地址来表示对应的变量了,但如果存在.o文件互相使用对方变量的情况,这个则需要链接。然后再将多个.o的二进制文件链接为.exec可执行文件,此时如果存在多个.o文件的互相使用变量的情况也解决了,因为将多个.o文件链接后,作为一个整体来为每个变量分配逻辑地址空间了。最后载入内存,进行一个重定向操作,将逻辑地址映射为内存中的地址,但该地址,仍然不是物理地址,它只是该应用程序所看到的一个逻辑地址。
	物理地址的生成:
	CPU需要执行某一条指令,且该指令有一个逻辑地址,CPU也能拿到这个逻辑地址->CPU使用这个逻辑地址为参数向内存请求该指令的内容->首先MMU进行查找,自身是否存在一个该逻辑地址到物理地址的映射记录,如果没有,则在内存中去进行下一步查找,同样查找这样一个逻辑地址到物理地址的映射记录。如果找到了,则CPU根据物理地址拿到对应的指令进行执行。在这样一个流程中,OS所扮演的角色,就是将这样一个逻辑地址到物理地址的映射提前做好,无论是放到MMU或是内存中去。且OS需要保证该应用程序所访问的地址空间是合法的。具体如何验证合法,可参考下图。

2

5. 连续内存分配
	- 内存碎片:内部碎片和外部碎片
		内部碎片->已经分配给应用程序,但应用程序无法使用的部分
		外部碎片->内存分配单元之间没法使用的部分
	- 分配算法
		首个适配:适配第一个能放下该程序的空闲块->外部碎片
		最优适配:适配一个最合适放下该程序的空闲块。使得min(该空闲块大小-程序所需大小)->细小外部碎片
		最差适配:与最优适配相反,适配一个最大的空闲块,使得max(该空闲块大小-程序所需大小)->遇到需要很大空闲块的应用程序难以分配
	- 压缩式碎片整理
		将内存中的程序进行动态"挪动"位置变得更紧凑,使得碎片得到利用
	- 交换式碎片整理	
		即如果程序运行时需要更多内存空间,但空闲的内存空间不足以被使用
		抢占等待的程序,将其换到磁盘中,回收其内存供我们使用,该程序执行完后再换进到内存

6. 物理内存分配(非连续内存分配)
	- 连续内存分配:内存利用率低 碎片问题
	- 非连续:更好的内存利用和管理、运行共享代码与数据(共享库)、支持动态加载和动态链接。难以实现虚拟地址和物理地址的转换。
	- 分段:更好的分离和共享
		分段寻址,可通过下面第一张图来更好的理解。其中CPU根据段号查段表,一个段表项包含该段的起始物理地址以及该段的长度。由此判断此次访问的合法性。从而能够查找到物理内存中的位置。其中段表是在寻址前由OS建立完成。
		
	- 分页:
		1)划分物理内存至固定大小的帧frame
			物理内存被分割为大小相等的帧,一个内存物理地址分为两块,可理解为一个二元组(f,o),addr=f+o,f-帧号,o-帧内偏移,具体参考下面第二张图来理解
		2)划分逻辑地址空间至相同大小的页page
			页和帧可以对照起来理解,同时可以参考下面第三张图。一个逻辑地址同样划分为一个二元组(p,o),其中页内偏移和帧内偏移是相等的,即页内偏移是多少,帧内偏移就为多少,但总的位数可能与帧不同,即可能页号的位数和帧号不同。可参考下面第五张图理解。
		3)页寻址机制
			页表中存储了逻辑地址和物理地址的映射关系,可参考下面第四张图进行理解。且页表也同样是在最初始的时候就由OS建立完成。
		逻辑地址到物理地址的转换方案:页表、MMU/TLB

image.png

image.png

image.png

image.png

image.png

7. 页表
	页表结构如下图所示。其中标志位中的resident bit代表该页对应的物理帧是否存在,0代表不存在,1代表存在。
	可参考下面第二张图的地址转换实例进行理解。
	- 但这样的分页机制存在性能问题。
		1)时间上:因为访问一个内存单元,需要两次内存访问,第一次用于获得页表项内容,第二次用于访问具体的地址对应的数据内容。这样两次访问造成的开销很大。
		2)空间上:其次,页表可能会非常大,64位的机器如果每页1024字节,那一个页表的大小就为2^54这么多个页表项。
	- 解决方案:
		1)时间上:使用TLB来解决。如下面第三张图所示。这里可以补充,访问页表时,是通过页表基地址加上页表号乘以每个页表项大小来查询到对应页表号对应的页表项的,从而能够节省页表号的存储空间。
		2)空间上:使用多级页表来改善。其中二级页表的实现可参考下面第四张图。二级页表就是将逻辑地址空间分成了三部分,之前是一个二元组(p, o),现在变为三元组(p1, p2, o)来实现的。过程就是,通过p1即一级页表号作为index查询一级页表对应的页表项,然后该页表项中存的内容就是二级页表的基地址。然后通过二级页表的基地址和二级页表号就能查找到对应的帧号f了,进而完成了一个空间上的减压。还有一种情况,比如通过p1查询一级页表页表项时,该页表项的驻留位(resident bit)为0,即映射关系不存在的时候,该页表项对应的一整个二级页表都不需要在内存中进行存放,进而实现了空间上的节省。而对于之前只有一个页表的时候,即使该映射关系不存在,仍然需要保留该页表。
	- 多级页表:而多级页表就可以以此类推,见下面第五张图。但多级页表带来的后果就是时间开销很大,相当于用时间来换取空间。
	- 反向页表
		1)使用页寄存器来实现,可参考下面第六张图。即使用帧号作为index,页号作为内容。这样能使得页表项个数只和物理地址相关而和虚拟地址无关。但带来的后果就是难以在知道页号的情况下查找到对应的帧号,因为现在页表项已经反过来了。有一种解决方案是使用关联内存(和TLB类似)的技术,但仍然难以很好的处理。
		2)使用基于Hash的查找:参考下面第七张图。即通过页号可以hash到对应的帧号,但为了缓解hash冲突和提高效率,可以将hash的输入调整为PID和页号两个输入,从而用这俩作为输入可以hash到对应的帧号,并查询反向页表获得页寄存器并验证其存储的是否是该页号。但这样也会存在一些问题,如下面第八章图。可能存在hash冲突。且反向页表也存在于内存中,即也hash时也会产生一定的内存开销。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

8. 虚存技术
	起因:内存和硬盘的互相交接问题。程序过大、多道程序等等,从而有了覆盖技术和交换技术以及现在的虚存技术。
	- 覆盖技术
		产生于上世纪80-90年代,常用于多道程序系统,与分区管理配合使用,具体参考下面第一幅图。然后例子在第二幅图,B C之间是不会互相调用的,即通过相互覆盖的方式完成内存的共享。从而将190K的内存降低到110K的内存使用。但缺点是需要程序员的精心设计以及内存和硬盘的换入换出的开销。
	- 交换技术
		关于交换技术,可参考下面第三幅图。其交换的粒度为一个程序,相对较大。具体交换例子可参考下面第四幅图。其中交换技术都由OS来完成,不需要程序员参与,减轻其负担,同时交换技术所带来的问题以及交换技术和覆盖技术的对比,概述可为,交换技术发生在程序之间,而覆盖技术发生在程序内部,可参考下面第五幅图。
	- 虚存技术
		上述两个技术仍然存在一定的缺点:
			1)覆盖技术: 需要程序员自己把整个程序划分为若干个功能模块,并确定其覆盖关系,增加程序员负担
			2)交换技术: 进程为交换的单位,粒度大,需要把进程的整个地址空间换入换出,增加处理器的开销。
		而通过虚存技术则可较为完美的解决上述问题。
		虚存技术可以将程序的部分内容放入内存而非全部内存,且整个过程由操作系统完成。且能在内存和外存之间完成交换,且粒度更小。
		但其要求程序具有局部性:可参考下面第六张图。
		虚存的实现实在页式或段式内存上基础实现,可参考下面第七张图。而虚存技术的基本特质则可参考第八张图。其中不连续性可由OS进行优化,使得程序能继续运行。
	- 虚拟页式内存管理
		上述都是一些理论性的知识,具体怎么实现,这里从页式存储作为例子进行讲解。
		可参考下面第九张图,其中之前讲解页表中讲到过,页表项中除了帧号外,还存在几个bit用于其他用途,其中的驻留位即代表该虚拟地址空间所对应的物理地址空间是否存在,即实际内存中不存在该数据,该数据还在外存中未被调进内存,而在我们这里的虚存管理中,该产生的memory exception则代表缺页中断,需要OS从外存中将对应的数据调进内存。
		为了能够更好的实现虚存管理,在页表项中添加了几个较为重要的bit。可参考下面第十张图。每个位的作用也可在图上看出。
		然后是对于缺页中断的具体过程,可参考下面第十一张图。
		其中虚拟内存性能的描述可以通过下面第十二张图来表示。其中q就是对内存中页进行写操作的几率,即需要将该页写回硬盘,所以需要(1+q)

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

9. 页面置换算法
	功能:当缺页中断发生,需要调入新的页面而内存已满时,选择内存中那个物理页面被置换。
	目标:尽可能地减少页面的换进换出次数(即缺页中断的次数)
	
- 局部替换算法
	1) 最优页面置换算法
		基本思路:当缺页中断发生时,对于保存在内存中的每一个逻辑页面,计算在它的下一次访问之前,还需等待多长时间,从中选择等待最长的做比被置换的页面。
		但实际的系统中是无法实现的,因为OS无法预测未来的情况,但该算法可用作评价其他算法的依据。
	2) 先进先出算法
		基本思路:选择在内存中驻留时间最长的页面并将其淘汰。
		但性能较差,调出的页面可能是要经常访问的页面,并且有Belady现象(给其分配物理页帧越多反而可能出现缺页次数增加的情况)。FIFO算法很少单独使用。
	3) 最近最久未使用算法(Least Recently Used, LRU)
		基本思路:当一个缺页中断发生时,选择最久未被使用的那个页面,并淘汰。
		本质是对最优页面置换算法的近似,即通过过去推测未来。即如果近一小段时间某些页被频繁访问,那么其在未来被频繁访问的几率也很大。反之亦然。
		算法:需要记录每个页使用时间先后顺序,开销较大。
			- 使用链表:最近刚刚使用的页面作为链头,每次访问时,找到相应页号,将其摘下放到链表头部;每次缺页中断发生时,淘汰链尾页面。
			- 使用栈:访问页即压栈,如果栈内存在相同的页号,则将其弹出,淘汰时选择栈底淘汰。
	4) 时钟页面置换算法
		Clock页面置换算法。LRU的近似,对FIFO的一个改进
		补充:页表项中除了页帧号外的几个bit中,还存在一个access bit,用于表示该页是否被访问,如果被访问则置1,且该操作是由硬件直接完成的,不用软件的参与。且OS会定期将其清0.
		基本思路:通过该访问位,将页面组织成环形链表,将指针指向最老的页面(最先进来),当发生缺页中断时,检查指针所指的最老页面,若其access bit为0,则立即淘汰,若为1,则将其置为0,然后指针后移,直到找到需要被淘汰的页面,然后指针后移。
	5) 二次机会法
		在Clock算法基础上,增加dirty bit为判断依据。下面使用(access, dirty)进行说明
		(0, 0)->replace;
		(0, 1)->(0, 0);
		(1, 0)->(0, 0);
		(1, 1)->(0, 1)
		遵循上述规则,实质即为允许脏页在第一次时钟指针碰到时进行留下,让脏页在内存中停留更多时间,即再给与一次机会。
	6) 最不常用算法(Least Frequently Used, LFU)
		基本思路:当缺页中断发生时,选择访问次数最少的那个页面,并将其淘汰。
		LRU和LFU的区别:LRU考虑时间,LFU考虑频率
		实现:添加计数器(开销大,自己思考)
	
	- Belady现象:在采用FIFO算法时,有时会出现分配的物理页面数增加,缺页率反而提高的异常现象。
	- 原因:FIFO的置换特征与进程访问内存的动态特征时矛盾的,与置换算法的目标时不一致的(即替换较少使用的页面),因此被换出去的页面不一定是进程不会访问的。 

	- LRU FIFO Clock的比较(参考下图)
		LRU算法性能好,但开销大;FIFO开销小但可能存在BeLady现象,所以折衷使用CLock算法。

- 全局页面置换算法
	1) 工作集模型:如果局部性原理成立,如何证明其存在性,如何对其进行定量分析?这就是工作集模型
	工作集:一个进程当前正在使用的逻辑页面集合,使用二元函数W(t, Δ)表示
		- t是当前的执行时刻
		- Δ称为工作集窗口,即一个定长的页面访问的时间窗口
		- W(t, Δ) = 在当前时刻t之前的Δ时间窗口当中的所有页面所组成的集合(随着t的变化,该集合也在变化)
		- |W(t, Δ)| 指工作集大小,即页面数目
	工作集的大小变化:进程开始执行后,随着访问新页面逐步建立较稳定的工作集。当内存访问的局部性区域的位置大致稳定时,工作集大小也大致稳定。局部性区域的位置改变时,工作集快速扩张和收缩过渡到下一个稳定值。
	
	2) 常驻集:指当前时刻,进程实际驻留在内存中的页面集合。
	工作集是进程在运行过程中固有的性质。而常驻集取决于系统分配给进程的物理页数目,以及所采用的页面置换算法。
	如果一个进程的整个工作集都在内存当中,即常驻集包含工作集,当么进程将很顺利的进行,而不会造成太多的缺页中断(直到工作集发生剧烈变动)
	当进程常驻集的大小达到某个数目之后,再给它分配更多的物理页面,缺页率也不会明显下降了。即该进程不需要更多的物理页了。
	即我们希望工作集和常驻集之间的差距越小越好,才能产生更少的缺页中断。
	
	3) 工作集替换算法:可参考下图2的例子进行理解。
	4) 基于缺页率的页面置换算法
		常驻集大小可变。再进程运行过程中动态调整常驻集大小。
		可采用全局页面置换的方式,当发生缺页中断时,被置换的页面可以是在其他进程当中的,各个并发进程竞争的使用物理页面。
		性能较好但会增加系统开销。具体实现则可以使用缺页率算法(PPF, page fault frequency)来动态调整常驻集大小。
		缺页率:缺页次数/内存访问次数 或 缺页平均时间间隔的倒数。其中影响缺页率的因素有:
			- 页面置换算法、分配的物理页面数、页面本身大小、程序编写方法
		可参考下图3
		
		一个交替的工作集计算明确的试图最小化缺失
		算法:参考下图4、5
	5) 抖动问题
		如果分配给一个进程的物理页面太少,不能包含整个工作集,即常驻集被包含于工作集,那么进程将会造成很多的缺页中断,需要频繁的在内存和外存之间替换页面,从而使得进程此的运行速度变得很慢,这种状态称为 抖动。
		原因:随着驻留内存的进程数目增加,分配给每个进程的物理页面数不断减小,缺页率不断上升,所以OS要选择一个适当的进程数目和进程所需的帧数,以便在并发水平和缺页率之间达到平衡。
		需要在多道程序并行时找到最佳值。参考下图6。

image.png

image.png

image.png

image.png

image.png

image.png

10. 进程:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。存在内核态和用户态。
	组成:
        - 程序代码
        - 程序处理的数据
        - 程序计数器中的值,指示下一条将运行的指令
        - 一组通用寄存器的值,堆、栈
        - 一组系统资源(打开的文件等等)
	特点:
		- 动态性:可动态创建、结束进程
		- 并发性:进程可以独立调度并占用处理机运行;并发并行
		- 独立性:不同进程的工作不互相影响
		- 制约性:因访问共享数据/资源或进程间同步而产生制约
	OS为每个进程维护了一个PCB,用于保存与该进程有关的各种状态信息。PCB是进程存在的唯一标志。
	PCB:组织方式-链表(通用)、索引表
		- 进程标识信息(父进程标识、用户标识)
		- 处理机状态信息保存区(保存进程的运行现场信息)
			- 用户可见寄存器、控制和状态寄存器(PC,PSW)、栈指针
		- 进程控制信息
			- 调度和状态信息、进程间通信信息、存储管理信息、进程所用资源(打开的文件等等)、有关数据结构连接信息(进程连接到一个进程队列)
	进程生命周期:创建、运行、等待、唤醒、结束
        创建:系统初始化、用户请求创建、正在运行的进程执行了创建进程的系统调用
        等待:请求并等待系统服务、启动某操作无法马上完成、需要的数据没用到达。进程只能自己阻塞自己,因为只有进程自己才知道何时需要等待某事件的发生。进程只能被别的进程或者OS唤醒。
        结束:正常退出(自愿)、错误退出(自愿)、致命错误(强制退出)、被kill(强制退出)
	进程状态:running、ready、blocked
	进程挂起:进程没有占用内存空间、处于挂起状态的进程映像在磁盘上。
		- 阻塞挂起:进程在外存等待某事件出现
		- 就绪挂起:进程在外存,只要进入内存即可运行
	从内存转到外存:阻塞->阻塞挂起、就绪->就绪挂起、运行->就绪挂起
	外存中:可能存在 阻塞挂起->就绪挂起
	OS维护状态队列,不同状态对应不同队列,PCB根据其状态放入对应队列,当状态发生变化,PCB所处队列也发生变化。

image.png

image.png

线程:进程当中的一条执行流程。线程=进程-共享资源 TCB
	- 优点:各个线程并发执行,具有各自独立的寄存器和堆栈。其余资源共享
	- 缺点:一个线程崩溃会导致其所属进程的所有线程崩溃。
进程是资源分配单位,线程是CPU调度单位。

用户线程:在用户空间实现的线程的机制,不依赖于OS的内核,由一组用户及的线程库函数来完成线程的管理,包括线程的创建、终止、同步、调度等等。其调度算法也可由进程自定义。
	- 缺点:
		阻塞性的系统调用如何实现。一个线程发起系统调用,整个进程都会处于等待状态。
		一个线程开始运行后,除非其主动交出CPU的使用权,否则其他线程无法运行。
		时间片分配给进程,所以在多线程执行时,每个线程的执行时间较短,效率低。
		
内核线程:在操作系统内核实现的一种线程机制。由内核完成线程的相关操作。线程的各个操作都是通过系统调用/内核完成,系统开销较大。且时间片也直接分配给线程。

轻量级进程:一个进程有一个或多个轻量级进程,每个轻量级进程由一个内核线程来支持。
进程创建:fork/exec函数。
fork开销巨大,可用vfork + exec来减少开销
现在使用Copy On Write(写时复制)技术,需要写的时候,才复制对应的地址空间,即不用担心fork的开销。
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

wait()系统调用,父进程等待子进程结束。
为何需要等待:子进程PCB的回收工作。其余工作可以由OS完成,但PCB的回收难以进行。
exit()系统调用:
	- 将该程序的结果作为参数
	- 关闭所有打开的文件、连接
	- 释放内存
	- 释放大部分支持进程的操作系统结构
	- 检查是否父进程存活
		- 如果存活,则保留结果直到父进程需要,此时该进程并未真正死亡,进入僵尸态
		- 如果没有,则其释放所有数据结构,进程死亡。
		init进程会定期扫描PCB链表,检查是否存在进程处于僵尸状态,如果存在,则init进程会充当父进程将其回收
	- 清理所有等待的僵尸进程 

fork和exec的更直观表示,参考下图2.
其中exec的系统调用,因为需要加载硬盘中的可执行程序,所以当时进程的状态可能是从running态转到blocked态,等待加载完成。即执行exec时,进程可能处于不同状态。

image.png

image.png

11. 调度
	内核运行调度程序的条件
		- 一个进程从运行状态切换到等待状态
		- 一个进程被终结
	内核态和用户态分别都有,抢占式调度和非抢占式调度策略
	吞吐量:单位时间内完成的进程数量-OS的计算带宽
	周转时间:一个进程从初始化到结束,包括等待时间所花费的时间
	等待时间:进程在就绪队列中的总时间
	响应时间:从一个请求被提交到产生第一次响应花费的时间-OS的计算延迟
	调度算法:
		FCFS(先来先服务):
			* 简单
			* 平均等待时间波动大、花费时间少的任务可能排在时间长的任务后、可能导致I/O和CPU的重叠处理(CPU密集型进程会导致I/O设备闲置时,IO密集型进程也在等待)
		短任务优先:按照进程预测的完成时间进行入队(可以为抢占的或非抢占的)
			* 平均等待时间最优
			* 可能导致长任务饥饿、需要预知未来
		最高响应比优先:R=(w+s)/s w:等待时间 s:执行时间(不可抢占)
			* 关注了进程的等待时间、防止无限期等待
			* 也需要预估执行时间
		轮询调度算法:按时间片进行轮询 
			* 存在额外的上下文切换
			* 时间片设置若太小——等待时间过长、极限退化为FCFS
			* 若太大——反应迅速但吞吐量受到影响
			* 经验规则——维持开销为1% 
		多级反馈队列算法:
			就绪队列被划分成独立的队列,每个队列有其自己的调度策略。调度在队列间进行。参考下2图
		FFS:强调公平策略

image.png

image.png

实时系统——实时调度算法:硬实时、软实时
	概念:任务、属性、released、relative deadline、absolute deadline
	静态优先级调度、动态优先级调度
	
多处理器调度
优先级反转

image.png

12. 同步、互斥
	独立的线程:确定性(输入确定结果确定)、可重现(能重现起始条件)
	合作线程:共享状态、不确定、不可重现
	
	临界区:进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域。
	互斥:当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源
	死锁:两个或以上的进程在相互等待完成特定任务,而最终没法将自身任务进行下去
	饥饿:一个可执行的进程被调度器持续忽略,以至于虽然处于可执行状态却不被执行。
	
实现临界区的保护:
	1、禁用硬件中断(时间中断->上下文切换)
		需要临界区较小、且无法解决多核CPU下的互斥问题
	2、基于软件的解决方法
		较为复杂、需要忙等待、需要硬件保证
	3、更高级的抽象
		test-and-set:从内存中读值->判断值是否为1(返回true or false)->内存值设为1
		exchange:交换内存中的两个值
		具体锁的实现参考下面代码
		1)test-and-set实现
            lock_value = 0; // lock
            lock_acquire() {
                while(test-and-set(lock_value)) ;
            }
            lock_release() {
                lock_value = 0;
            }
            其中对于lock_acquire,如果value为1,即锁无人占用,test-and-set返回false,且将value置为1,所以跳出while,执行临界区代码
            如果value为1,即锁正在被占用,test-and-set返回true,while循环中等待。

            上述代码仍然存在忙等待的问题,可以加入一个等待队列数据结构,如下图。
		2)exchange实现
			lock_value = 0; // lock
			// 线程Ti
			int key;
             do {
                 key = 1; // 代表该进程是否需要进入临界区
                 while (key == 1) exchange(key, lock_value);
                 // 临界区代码
                 lock_value = 0;
                 ...
             }
		缺点:忙等待、饥饿问题、死锁问题

image.png

13. 信号量、管程(semaphore)
	信号量:
		P()-sem减1,如果sem<0,等待,否则继续
		V()-sem加1,如果sem<=0,唤醒一个等待的进程P
		P()能阻塞、V()不会阻塞且操作信号量只能通过P/V()操作,V()唤醒一般会采用FIFO
		利用多值sema来实现同步、二进制sema实现互斥,具体参考下图
		其中V()操作可以交换顺序、但P()操作不行,因为P()操作会导致阻塞
	信号量的实现:
		利用硬件,见下图2
	信号量不能处理死锁问题
	
	管程(monitor):分离互斥和条件同步的关注
		一个锁、0个或多个条件变量.且同一时刻只有一个线程进入管程
		条件变量实现可参考下图3
		注意:唤醒操作,如果唤醒多个线程,仍然只会有一个线程获得锁。

image.png

image.png

image.png

读者写者问题

image.png

image.png

哲学家就餐问题

image.png

image.png

image.png

image.png

14. 死锁问题
	资源分配图(一组顶点V和边E的集合)
	V有两种类型:P{P1...Pn}-进程集合 R{R1.。Rn}-资源集合
	E: 进程申请资源Pi->Rj	进程拥有资源Pi<-Rj
	死锁-->图中出现环  图中没有环-->没有死锁 即环是死锁的必要条件
	
	死锁出现,则下面四个特征均满足,反之不成立,即四个必要条件
	1)互斥:一个时间只有一个进程使用资源
	2)持有并等待: 进程保持持有一个资源并等待获取其他进程持有的额外资源
	3)不可抢占: 资源只能被拥有的进程主动释放
	4)循环等待: 存在等待进程集合{P0...Pn},P0等待P1占用的资源...Pn-1等待Pn占用的资源,Pn等待P0占用的资源。
	
	死锁预防:限制进程使得上述四个条件不完全满足
	互斥-使得共享资源其他进程也能访问。但会导致系统无法正常运行,无法确定执行
	占用并等待-进程要么拥有所有资源要么不拿资源,但会导致资源利用率低且易导致饥饿
	不抢占-进程可以抢占其他进程的资源,但和互斥条件冲突,只能将其他进程kill
	循环等待-对资源进行排序,要求每个进程按照资源的顺序进行申请
	
	死锁避免:如果分配给进程资源可能导致死锁,则拒绝分配资源
	动态检查资源分配状态,确保不会存在环形等待状态。
	当进程请求资源,系统判断分配资源是否能够使得系统处于安全状态。
	安全状态:针对所有进程存在安全序列。且包含死锁状态。
	序列<P1...Pn>是安全的:针对每个Pi,Pi要求的资源能够得到满足。即Pi所需的资源可以由Pj持有的资源满足,j<i。因为执行到Pi时,Pj已经释放资源。
	银行家算法:见下图,开销大,且需要提前知道进程所需的资源个数,难以在系统中使用,一般用于调试
	假定已经分配给进程资源,如果分配之后,处于不安全的状态,则不分配
	
	死锁检测:
	1)简化资源分配图,结点只为进程,如果Pi需要资源m,同时m被资源Pj持有,则Pi->Pj即Pi等待Pj。定期判断等待图中是否存在环
	2)见下图检测算法,开销大,难以在平时系统中用到
	
	死锁恢复:
	终止死锁中的进程,选择一个进程kill直至死锁消除,选择哪个进程进行kill的顺序,遵循以下规则:
	1)进程优先级 2)进程运行时间 3)进程占用的资源 4)进程完成需要的资源 5)多少进程需要终止 6)进程时交互还是批处理

image.png

image.png

image.png

image.png

image.png

15. IPC(进程间通信)
	消息传递分为阻塞(同步)和非阻塞(异步)
	缓存消息的容量分为0(发送方需要等待接收方)、有限(如果队列满,发送方需要等待)、无限(发送发无需等待)
	
	信号(Signal):可理解为软件中断
	信号处理:Catch、Ignore、Mask、默认(结束)
	但无法传输需要交换的数据
	
	管道(Pipe):依靠于父子进程
	消息队列(MQ):
		多个进程之间通信,FIFO,buffer
	共享内存:直接通信
		需要同步互斥机制
		实现:同一物理内存映射到不同进程的地址空间

16. 文件系统: 一种用于持久性存储的系统抽象
	文件: 文件系统中一个单元的相关数据在OS中的抽象 
	文件属性:名称、类型、位置、大小、保护、创建者、创建时间、最近修改时间...
	文件描述符
		OS为每个进程维护一个打开文件表,文件描述符是该表的索引
	元数据数据来管理打开文件:
		文件指针、文件打开次数、文件磁盘位置、访问权限
	用户视图:持久的数据结构
	系统访问接口:字节集合,系统不关心数据结构
	OS内部视角:块的集合(块是逻辑转换单元而扇区是物理转换单元)
	
虚拟文件系统:

	卷控制块(superblock):一个文件系统一个、文件系统详细信息、块、块大小、空余块、计数/指针等。在文件系统挂载时进入内存
	文件控制块(inode):每个文件一个、文件详细信息、许可、拥有者、大小、位置。在文件被访问时进入内存
	目录节点(dentry):每个目录项一个、指向文件控制块、父节点、项目列表。遍历文件路径时进入内存
	
	数据块缓存
	
	打开文件的数据结构
	
	文件分配
		连续分配、链式分配、索引分配
	空闲空间列表
	多磁盘管理-RAID
		冗余磁盘阵列 RAID 0/1/3/4/5
	磁盘调度
		磁臂粘着:磁臂停留某处处理请求
		SCAN C-SCAN N-STEP-SCAN F-SCAN(磁盘请求队列分为两个,N=2)

image.png

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值