操作系统网课笔记

操作系统

网课链接 https://www.bilibili.com/video/BV1uW411f72n?from=search&seid=7305616632390248505

内容参照这位兄弟的博客 https://blog.csdn.net/iwanderu/article/details/103934127 很细

参考的Github笔记链接 https://github.com/OXygenPanda/Deep_into_OperatingSystem

网课内容顺序 内存管理 - 进程管理 - ⽂件系统管理

第一章:概述

基本概念及原理

操作系统介绍

中断及系统调用

内存管理

进程及线程

调度

同步

文件系统

IO子系统



操作系统定义

1)用户角度控制软件

管理应用程序

为应用程序提供服务

杀死应用程序

2)资源管理

管理外设,分配资源



层次结构处于

硬件之上

应用程序之下



Linux界面属于外壳(shell) 而不是内核(kernel)



kernel操作系统内部组件

CPU调度器

物理内存管理

虚拟内存管理

文件系统管理

中断处理与设备驱动



OS kernel特征

1)并发

2)共享

互斥共享

同时共享

3)虚拟

CPU 进程

磁盘 文件

内存 地址空间

4)异步

程序执行不是一贯到底

但只要运行环境一样 结果就一样



 操作系统的结构	演变过程

1)简单的操作系统

单体内核 (内部通过函数调用访问,缺点,复杂,紧耦合,易受攻击)

2)微内核	

内核放最基本的功能,尽可能把内核功能(中断系统,消息传递等)移植到用户空间,缺点性能低。

3)外核

内核分为两块,一块负责和硬件打交道,另一块和应用打交道

4)虚拟机

VMs(虚拟机)->VMM(虚拟机监视器)->物理机硬件


第二章:操作系统基础操作

启动

DISK	存放OS Bootloader



POST(加电自检)寻找显卡和BIOS 

BIOS:基本IO处理系统	功能 启动电源后可以检测外设

将Bootloader从磁盘的引导扇区加载到一个地址上

然后段寄存器的指令指针寄存器跳转到这个地址上

Bootloader 加载OS	将OS从硬盘放到内存

跳转到OS的起始地址

OS开始工作



操作系统的接口

1)中断(来源于外设)异步 不知道什么时候会产生

来自不同的硬件设备的计时器和网络的中断

中断过程持续 但对用户时透明的

2)异常 (来源于不良的应用程序)同步 发生后就会执行

非法指令或者其他坏的处理状态

杀死异常或者重新执行

3)系统调用(来源于应用程序)同步或异步 

应用程序主动向操作系统发出服务请求

等待服务完成继续执行



内核可以被信任



中断处理过程

硬件

设置中断标记(CPU初始化)

​	将内部,外部事件设置中断标记

​	中断事件的ID(程序访问的中断向量地址)

软件

保留当前处理状态	

中断服务程序处理

清除中断标记恢复之前保留的处理状态



异常

保存现场

异常处理

杀死异常程序 / 重新执行异常程序

恢复现场



系统调用

程序访问主要是通过高层次的API接口而不是直接进行系统调用



通常情况下,存在与每个系统调用相关的序号,系统调用接口根据这些序号来维护表的索引



用户态:应用程序在执行的过程中,CPU执行的特权级的状态(很低,不能访问特殊机器指令和IO)

内核态:应用程序在执行的过程中,CPU执行的特权级的状态(高,操作系统可以执行CPU任何一条指令)

区别主要在权限问题



系统调用时涉及到特权级从用户态到内核态的转换,应用程序和操作系统有各自的堆栈,需要切换堆栈。这两个变化比函数调用的开销更

大,但更安全和可靠。而程序调用是在一个栈空间实现参数的调用和返回



跨越操作系统边界的开销

在执行时间上超过程序调用

开销:

1)建立中断/异常/系统调用号与对应服务例程映射关系的初始化开销

2)建立内核堆栈(操作系统和应用程序的堆栈不一样)

3)验证参数(安全问题)

4)内核态映射到用户态的地址空间,更新页面映射权限(内存拷贝开销)

5)内核态独立地址空间 TLB

第三章:连续式内存分配

计算机体系结构

CPU

内存

IO	



内存分层体系

寄存器

缓存

内存(主存)

磁盘(虚拟内存)



内存管理

抽象	逻辑地址空间

保护	独立地址空间

共享	访问相同内存

虚拟化	更多的地址空间



操作系统管理内存的不同方法

1)程序重定位

2)分段

3)分页

4)虚拟内存

5)按需分页虚拟内存

实现高度依赖硬件

必须知道内存架构

CPU有 MMU(内存管理单元)硬件组件负责处理CPU的内存访问请求



地址空间定义

物理地址空间	硬件支持的地址空间

逻辑地址空间	一个运行的程序所拥有的内存范围



地址生成

C为例

编译	汇编	链接	载入



逻辑地址与物理地址的映射

CPU方面

运算器ALU需要在逻辑地址的内存内容(CPU要逻辑地址)

CPU的内存管理单元MMU寻找在逻辑地址和物理地址之间的映射(然后MMU找逻辑和物理地址的关系) 没有则去内存找

控制器从总线发送在物理地址的内存内容的请求(关系找到后,去找对应物理地址)

内存方面

内存发送物理地址内存内容给CPU(物理地址找到了,给CPU)

操作系统方面

建立逻辑地址和物理地址之间的映射(确保程序不相互干扰)



地址安全检测

设置逻辑地址的基址和界限



内存碎片问题

1)外部碎片

没分配的

2)内部碎片

分配了没有用到的



分区的动态分配	内存管理方法

1)首次适配

从低地址开始找,碰到的第一个空间比n大的空闲块就使用它

需求

需要存在一个按地址排序的空闲块列表

分配需要找一个合适的分区

重分配需要检查,看看自由分区能不能与相邻的空闲分区合并(形成更大的空闲块)

优点

简单

易于产生更大的空闲块,向着地址空间的结尾

缺点

外部碎片的问题会加剧

不确定性

2)最佳适配

为了分配n字节,使用最小的可用空闲块,以致块的尺寸比n大

需求

按尺寸排列的空闲列表

分配需要寻找一个合适的分区

重分配需要搜索和合并于相邻的空闲分区

优点

避免分割大的空闲块

最小化外部碎片产生的尺寸

缺点

外部碎片

重分配慢

易产生很多没用的微小碎片

3)最差适配

为了分配n字节,使用最大的可用空闲块,以致块的尺寸比n大

需求

按尺寸排列的空闲列表

分配很快(获得最大的分区)

重分配需要合并于相邻的空闲分区,然后调整空闲块列表

优点

假如分配时是中等尺寸效果最好

缺点

重分配慢

外部碎片

易于破碎大的空闲块以至大分区不能被分割



1)压缩式碎片整理

重制程序以合并孔洞

要求所有程序是 动态可重置的

问题:何时重置;开销

2)交换式碎片整理

运行程序需要更多的内存

抢占等待的程序或回收它们的内存(把暂时不用的内容挪到磁盘里)

第四章:非连续式内存分配

连续内存分配的缺点

1)分配给一个程序的物理内存是连续的

2)内存利用率低

3)有外碎片/内碎片问题



非连续内存分配的优点

1)分配给一个程序的物理内存是非连续的

2)更好的内存利用和管理

3)允许共享代码和数据(共享库等)

4)支持动态加载和动态链接



非连续内存分配的缺点

如何建立虚拟地址和物理地址之间的转换

软件方案(开销大)



硬件方案

1)分段 Segment

分段地址空间

更好的分离和管理

分段寻址方案

段寄存器+地址寄存器

绝对地址 = 根据段号找到段表中的起始地址 + 段内地址 (如果段内地址超过限长则产生“地址越界”程序性中断事件达到存储保护)

s段号 + addr段内偏移

段表	逻辑地址和物理地址的映射关系	包含物理地址的起始地址和内存大小	有操作系统建立的

2)分页 Paging

与分段的区别	物理地址空间页帧的大小是固定的

大小是2的幂

帧Frame

f帧号	F位 共有2^F个帧

o帧内偏移	S位 每帧有2^S字节

内存物理地址 = 2^S * f + o





逻辑地址空间页帧的大小是固定的

大小是2的幂



一个程序的逻辑地址空间被划分为大小相等的页

页内偏移的大小 = 帧内偏移的大小

p 页号	P位 2^P个页

o页内偏移	S位 2^S字节

逻辑地址 = 2^S * p + o



页表 Page Table	逻辑页和物理页的映射关系	由操作系统建立

页号与帧号映射

页是连续的虚拟内存

帧是非连续的物理内存

不是所有的页都有对应的帧

有助于减少碎片



偏移量大小固定 

无需考虑分段大小不一致的问题



页表项

有标志位	三位	



分页机制的性能问题

1)空间的代价问题

​	页表可能很大

2)时间的开销问题

​	页表放内存

​	一次内存寻址访问两次内存

解决方法

缓存caching

间接(indirecion)访问



转换后备缓冲区/快表TLB	CPU的MMU内存管理单元保存的一段缓存 	解决时间问题	

将经常访问的页表项放到其中

TLB实际上是CPU的MMU内存管理单元保存的一段缓存,这段缓存保存的内容是页表的一部分,是经常访问到的那部分页表,其余不常

用的页表内容保存在内存中。

TLB未命中,也叫TLB miss,这种情况比较少见,因为一页很大,32位系统一页是4K,如果采用局部性原理,那么访问4k次才会遇到一

次TLB miss。



Miss可能有操作系统完成,也可能由硬件完成



二级页表	一级页表+二级页表

页号部分分成了2部分,p1和p2

p1存放着二级页表的起始地址,p2的作用就是之前的p。

p1找二级页表,p2找页

这里体现了二级页表的另一个好处,就是p1对应的位置是flags,假如说resident bit是0,那么整个二级页表都不用在内存中保存,这个是

一级页表无法实现的



多级页表	避免把全部的页表都存放在内存中

时间换空间



反向页表

页表来表示物理地址(页帧)号,而不是之前的逻辑地址(页号),能够减少页表尺寸,但是给映射关系的建立带来点困难

基于页寄存器page registers的方案

帧号和页号的映射

容量只与物理地址的大小有关



物理内存大小:4096 * 4096 KB = 16 MB
页面大小:4096 bytes = 4 KB
页帧数: 4K
页寄存器使用的空间(假设是8 bytes的register):8 * 4096 = 32 KB
页寄存器的额外开销:32 KB / 16 MB = 0.2%
虚拟内存的大小:任意
可以看出内存开销很小。

对反向页表减少内存开销的理解
链接 https://blog.csdn.net/LiQian06336/article/details/92971712


优点

转换表的大小相对于物理内存来说很小

转换表的大小跟逻辑地址空间的大小无关



缺点

需要的信息对调了,如何根据帧号找到页号呢

需要在反向页表里去找想要的页号



反向页表的实现:基于关联内存associative memory的方案

开销太大

如果帧数较少,页寄存器可以被放置在关联内存中

在关联内存中查找逻辑页号,成功了,帧号就被提取出来;失败了,页错误异常page fault。

限制这种方案的因素包括,大量的关联内存非常昂贵(难以在单个时钟周期内完成;耗电)



反向页表的实现:基于哈希查找hash的方案

出现哈希碰撞

这种方式仍然需要把反向页表放在内存中,做hash计算的时候还需要在内存中取数,仍然需要多次访问内存

第五章:虚拟内存

起因

经常出现内存不够

理想存储器	 更大 更便宜 更快	非易失性	还未实现



覆盖技术

程序太大,超过了内存的容量,可以采用手动的覆盖(overlay) 技术,只把需要的指令和数据保存在内存中

目标	

在较小的可用内存中运行较大的程序。常用于多道程序系统,与分区存储管理配合使用。

原理

把程序按照其自身逻辑结构,划分为若干个功能上相对独立的程序模块,那些不会同时执行的模块共享同一块内存区域,

按时间先后来运行。

必要部分(常用功能)的代码和数据常驻内存

可选部分(不常用功能)在其它程序模块中实现,平时存放在外存中,在需要时才装入内存

不存在调用关系的模块不必同时装入内存,从而可以相互覆盖,即这些模块共用一个分区

缺点

由程序员来把一个大的程序划分为若干个小的功能模块,并确定各个模块之间的覆盖关系,费时费力,增加了编程的复杂度

覆盖模块从外存装入内存,是以时间换空间



交换技术	操作系统完成 对程序员是透明的

程序太多,超过了内存的容量,可以采用自动的交换(swapping) 技术,把暂时不能执行的程序送到外存中

目标	

多道程序在内存中时,让正在运行的程序或需要运行的程序获得更多的内存资源

方法

可将暂时不能运行的程序送到外存,从而获得空闲内存空间

操作系统把一个进程的整个地址空间的内容保存到外存中(换出swap out),

而将外存中的某个进程的地址空间读入到内存中(换入swap in)。

换入换出内容大小为整个程序的地址空间

出现的问题

1)交换时机的确定:只有当内存空间不够或有不够的危险时换出

2)交换区的大小:必须足够大以存放所有用户进程的所有内存映像的拷贝,必须能对这些内存映像进行直接存取

3)程序换入时的重定位:因为换出换入后的内存位置不一定相同,所以最好采用动态地址映射的方法

覆盖与交换技术的比较

覆盖只能发生在那些(程序内)相互之间没有调用关系的程序模块之间,因此程序员必须给出程序内的各个模块之间的逻辑覆盖结构。

交换技术是以在内存中的程序大小为单位来进行的,它不需要程序员给出各个模块之间的逻辑覆盖结构。

交换发生在内存中程序与管理程序或操作系统之间,而覆盖则发生在运行程序的内部。



虚存技术(虚拟内存管理技术)

在有限容量的内存中,以更小的页粒度为单位装入更多更大的程序,可以采用自动的虚拟存储技术 

覆盖技术	增加程序员的负担

交换技术	增加了处理器的开销

目标

解决覆盖技术给程序员负担大和交换技术处理器开销大的问题。

像覆盖技术一样,不是把程序的所有内容都放在内存中,因而能够运行比当前的空闲内存空间还要大的程序。但做得更好,能由操作系统

自动完成,无需程序员介入

能像交换技术那样,能够实现进程在内存和外存之间的交换,因而获得更多的空闲内存空间。但能做得更好,只对进程的部分内容在内存

和外存之间进行交换。



程序的局部性原理

指程序在执行过程中的一个较短时间,所执行的指令地址和指令的操作数地址分别局限于一定区域,表现为

时间局部性	一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短的时间里

空间局部性	当前指令和领近的几条指令,当前访问的数据和领近的几个数据都集中在一个较小区域内



在页式或段式内存管理的基础上实现

1)装入程序,装需要执行的页面或段到内存

2)缺页或缺段,则由处理器通知操作系统调入内存,继续执行

3)操作系统将内存中不需要的页面或段调到外存

基本特征

1)大的用户空间(虚胖)

2)部分交换

3)不连续性



虚拟页式内存管理

大部分虚拟存储系统都采用虚拟页式存储管理技术,即在页式存储管理的基础上,增加请求调页的页面置换功能

页表表项

驻留位			在内存还是外存	

保护位			访问类型

修改位			在内存是否被修改	可据此决定是否将内容写回外存

访问位			是否被访问过



缺页中断执行过程
设计页面置换算法	即要替换什么样的页面


未被映射的页

二级存储器的页

交换空间(磁盘或者文件)

后备存储	backing store	

一个虚拟内存空间的页面可以被映射到一个文件(在二级存储中)的某个位置

代码段:映射到可执行二进制文件

动态加载的共享库程序段:映射到动态调用的库文件

其他段:可能被映射到交换文件



虚拟内存性能

有效存储器访问时间EAT

与页表命中率	page fault 处理时间 几率 和 dirty page 换出时间	几率有关

第六章:页面置换算法

操作系统设计	高效	整洁

局部页面置换算法(发生缺页中断,选择内存页面进行置换)

要求	减少页面换入换出操作	

1)最优页面置换算法(OPT,optimal)

计算页面下一次被访问的时间,选择需要时间最长才能访问到的页面进行置换	(老预言家了)

过于理想

2)先进先出算法(FIFO, First In First Out)

选择驻留时间最长的页面进行替换

通过维护一个链表实现

存在Belady现象 很少单独使用

3)最近最久未使用算法(LRU,Least Recently Used)

选择最久未被使用的及进行替换	过去推未来	程序局部性原理

需要记录每个页面使用的时间顺序	开销较大

实现	维护一个链表或栈

4)时钟页面置换算法(Clock)

LRU的近似,对FIFO的改进

基本思路:

需要用到页表项当中的访问位,当一个页面被装入内存时,把该位初始化为0。

如果这个页面被访问(读/写),则把该位置为1;

把各个页面组织成环形链表(类似钟表面),把指针指向最老的页面(最先进来)

当发生一个缺页中断时,考察指针所指向的最老页面,若它的访问位为0,立即淘汰;若访问位为1,则把该位置为0,然后指针往下移动一

格。如此下去,直到找到被淘汰的页面,然后把指针移动到它的下一格。

没有LRU精准 但时间有优势

二次机会法(给个机会)

同时考虑used bit 和 dirty bit

00置换 	10/01变00	11变01(1指dirty)

5)最不常用算法(LFU, Least Frequently Used)

选择访问次数最少的页面进行替换

设置访问计数器

问题 一个页面一开始使用多后面少了

解决 定期寄存器右移



Belay现象

采用FIFO时,出现分配的物理页面数增加时,缺页率反而提高	





LRU,FIFO和Clock的比较

LRU算法和FIFO本质上都是先进先出的思路

如果内存当中的所有页面都未曾访问过,那么LRU算法就退化为FIFO算法

LRU算法性能较好,但系统开销较大,FIFO算法系统开销较小,但可能会发生Belady现象。因此,折中的办法就是Clock算法,在每一次

页面访问时,它不必去动态地调整该页面在链表当中的顺序,而仅仅是做一个标记,然后等到发生缺页中断的时候,再把它移动到链表末

尾。对于内存当中那些未被访问的页面,Clock算法的表现和LRU算法一样好;而对于那些曾经被访问过的页面,它不能像LRU算法那样,

记住它们的准确位置。



局部页面置换算法存在缺点

物理页帧大小的分配



全局置换算法要解决的问题

1)进程在不同阶段的内存需求是变化的

2)分配给进程的内存也需要在不同阶段有所变化

3)全局置换算法需要确定分配给进程的物理页面数



工作集模型	

W(t,deta) 大小为页面数量

一个进程当前正在使用的逻辑页面集合



常驻集(系统分配的页面个数)

常驻集是指在当前时刻,进程实际驻留在内存当中的页面集合。

工作集是进程在运行过程中固有的性质,而常驻集取决于系统分配给进程的物理页面数目,以及所采用的页面置换算法

如果一个进程的整个工作集都在内存当中,即常驻集 = 工作集,那么进程将很顺利地运行,而不会造成太多的缺页中断(直到工作集发生

剧烈变动,从而过渡到另一个状态)

当进程常驻集的大小达到某个数目之后,再给它分配更多的物理页面,缺页率也不会明显下降。



全局页面置换算法

1)工作集页置换算法

不属于工作集 就替换	(类似滑动窗口)

2)缺页率置换算法

可以使用缺页率算法 (PFF,page fault frequency) 来动态调整工作集的大小。

性能较好,但增加了系统开销



抖动问题

如果分配给一个进程的物理页面太少,不能包含整个的工作集,即常驻集∈工作集,那么进程将会造成很多的缺页中断,需要频繁地在内

存与外存之间替换页面,从而使进程的运行速度变得很慢,我们把这种状态称为“抖动”。

产生抖动的原因:随着驻留内存的进程数目增加,分配给每个进程的物理页面数不断减小,缺页率不断上升。所以OS要选择一个适当的进

程数目和进程需要的帧数,以便在并发水平和缺率之间达到一个平衡。



平均缺页时间 = 页缺失服务时间

第七章:进程和线程

进程(PROCESS)描述

1)定义

一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程

2)组成	包含正在运行程序所有的状态信息

程序的代码

程序处理的数据

程序计数器的值

一组通用寄存器的当前值,堆,栈

一组系统资源



进程与程序的联系

程序是产生进程的基础

程序的每次运行构成不同的进程

进程是程序功能的体现

通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。





进程与程序的区别

进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行,进程有核心态/用户态

进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可长久保存

进程与程序的组成不同:进程的组成包括程序,数据和进程控制块 (进程的状态信息)



3)特点

动态性:可动态地创建,结束进程

并发性:进程可以被独立调度并占用处理机运行

独立性:不同进程的工作不互相影响

制约性:因访问共享数据/资源或进程间同步而产生制约





4)控制结构

程序 = 算法 + 数据结构

进程控制块(process control block, PCB): 描述进程的数据结构,操作系统管理控制进程运行所用的信息集合。

操作系统为每个进程都维护了一个PCB,用来保存与该进程有关的各种状态信息,PCB是进程存在的唯一标志。





使用进程控制块可以实现

进程创建

进程终止

进程的组织管理



PCB包含信息

1)进程标识信息

如本进程的标识,本进程的产生者标识 (父进程标识) ;用户标识

2)处理机状态信息保存区,保存进程的运行现场信息

用户可见寄存器,用户程序可以使用的数据,地址等寄存器

控制和状态寄存器,如程序寄存器(PC),程序状态字(PSW)

栈指针,过程调用/系统调用/中断处理和返回时需要用到它。

3)进程的控制信息

调度和状态信息:用于操作系统调度进程并占用处理机使用;

进程间通信信息:为支持进程间的与通信相关的各种标识,信号,信件等,这些信息存在接收方的PCB中;

存储管理信息:包含有指向本进程映像存储空间的数据结构;

进程所用资源:说明由进程打开,使用的系统资源,如打开的文件等;

有关数据结构等连接信息:进程可以连接到一个进程队列中,或连接到相关的其它进程的PCB。



PCB的组织方式

链表:同一状态的进程其PCB成一链表,多个状态对应多个不同的链表	(较为通用)

各状态的进程形成不同的链表,例如就绪链表和阻塞链表

索引表:同一状态的进程归入一个index表(由index指向PCB),多个状态对应多个不同的index

各状态的进程形成不同的索引表,例如就绪索引表,阻塞索引表。





进程状态(STATE)

生命周期

进程创建

进程运行

进程等待

进程唤醒

进程结束 



引起进程创建的主要事件

1)系统初始化时

2)用户请求创建新进程

3)正在执行的进程执行了创建进程的系统调用



进程运行

内核选择就绪的进程

调度算法



进程等待(阻塞)	不占用CPU

进程只能自己阻塞自己,因为只有进程知道何时需要某种事件的发生

原因

1)请求并等待系统服务,无法马上完成

2)启动某种操作,无法马上完成

3)需要的数据没有到达



进程唤醒的原因

1)被阻塞进程需要的资源可被满足

2)被阻塞进程等待的事件到达

3)将该进程的PCB插入到就绪队列中



进程结束

情况

1)正常退出(自愿)

2)错误推出(自愿)

3)致命错误(强制性)

4)被其它进程所杀(强制性)	



状态变化模型

进程的三种基本状态:

进程在生命结束前处于且仅处于三种基本状态之一,不用系统设置的进程状态数目不同。

运行状态(running):当一个进程正在处理机上运行时

就绪状态(ready):一个进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行

(从运行态到就绪态 一般是CPU给的时间片没了)

等待状态(或阻塞状态blocked):一个进程正在等待某一事件而暂停运行时的状态,如等待资源,等待I/O完成

进程还有其它的基本状态,包括

创建状态(new),一个进程正在被创建,还没被转到就绪状态之前的状态

结束状态(exit),一个进程正在从系统中消失时的状态,这是因为进程结束或由于其它原因所导致





进程挂起管理

没有占用内存空间

处于挂起的进程映像在磁盘上

阻塞挂起状态	在外存中并等待某事件的出现

就绪挂起状态	在外存,只要进去内存,即可运行



内存到外存

阻塞到阻塞挂起	

没有进程处于就绪状态;或者就绪进程需要更多的内存资源

就绪到就绪挂起	

当高优先级阻塞(系统认为会很快就绪的)进程和低优先级就绪进程冲突时,系统会挂起低优先级就绪进程

运行到就绪挂起	

对于抢先式分时系统,当有 高优先级阻塞挂起进程 因为事件而变成 就绪挂起 时,系统可能会把正在运行的进程转到就绪挂起状态

在外存时的状态转换

阻塞挂起到就绪挂起:当阻塞挂起的进程因为相关事件出现时,系统会把阻塞挂起进程转化为就绪挂起状态。

解挂/激活(activate):外存到内存

就绪挂起到就绪:现在没有就绪进程;当前的就绪挂起进程的优先级高于就绪进程;

阻塞挂起到阻塞:当一个进程释放足够的内存时,系统会把一个高优先级的阻塞挂起进程(系统认为会很快出现所等待的事件发生) 

转为阻塞进程。



状态队列

由操作系统来维护的一组队列,用来表示系统当中所有进程的当前状态

不同的状态分别用不同的队列来表示 (就绪队列,各种类型的阻塞队列等)

每个进程的PCB都根据它的状态加入到相应的队列当中,当一个进程的状态发生变化时,它的PCB从一个状态队列中脱离,加入到另一

个状态队列里。



线程(THREAD)	进程的一条执行流程		TCB线程控制块

进程 = 资源管理 + 线程

多进程开销大		线程通信 创建等

线程特点

1)实体间能够并发地执行	

2)实体之间共享相同的地址空间





线程的优点

一个进程中可以同时存在多个线程

各个线程之间可以并发的执行

各个线程之间可以共享地址空间和文件等资源

线程的缺点

一个线程崩溃,该进程的所有线程崩溃



不同操作系统对线程的支持

现代的基本多进程多线程



线程与进程的比较:

1)线程是资源分配的单位,线程是CPU调度单位;

2)进程拥有完整的资源平台,而线程只占有必须的资源,如寄存器,栈。

3)线程同样由就绪,阻塞,执行三种基本状态,同样具有状态之间的转换关系。

4)线程能减少并发执行的时间和空间开销:

(线程的创建时间/终止时间/(同一进程内)切换时间更短

同一进程内各线程共享内存和文件资源,可直接进行不通过内核的通信)


线程的实现

1)用户线程	在用户空间实现,例如POSIX Pthreads, Mach C-threads, Solaris threads	

在用户空间实现的线程机制,不依赖于操作系统的内核

由一组用户级的线程库来完成线程的管理,包括创建/终止/同步/调度

优点

不需要操作系统内核了解用户线程的存在,可用于不支持线程技术的多进程操作系统

每个进程都需要它私有的线程控制块TCB列表,来跟踪记录它各个线程的状态信息(PC/栈指针/寄存器),TCB由线程库函数来维护

用户线程的切换由线程库函数实现,无需 用户态/核心态切换,所以速度快

允许每个进程有自定义的线程调度算法

缺点

如果一个线程发起系统调用而阻塞,则整个进程都在等待

如果一个线程开始运行,除非它主动交出CPU,否则该线程所在进程的其它线程都无法运行

由于时间片分配给的是进程,所以与其它进程相比,在多线程执行时,每个线程得到的时间片较少,执行会较慢



2)内核线程	在内核中实现,例如Windows, Solaris, Linux	

是指在操作系统的内核中实现的一种线程机制,由操作系统的内核来完成线程的创建,终止和管理。



由内核维护进程和线程的上下文信息,也就是进程/线程控制块PCB/TCB;

线程的创建/终止/切换都是通过系统调用或内核函数来实现(内核实现),所以系统开销大;

在一个进程中,如果某个内核线程发起系统调用而阻塞,不会影响其它内核线程的运行;

时间片分配给线程,多线程的进程能获得更多的CPU时间;



3)轻量级进程	在内核中实现,支持用户线程,例如Solaris	

内核支持的用户线程。一个进程可以有一个或多个轻量级进程,每个轻量级进程由一个单独的内核线程来支持(Solaris/Linux)



上下文切换

上下文切换上停止当前运行的进程(从运行态改变成其它状态)并且调度其它进程(转变成运行态)

必须在切换之前储存许多部分的进程上下文

必须能够在之后恢复他们,所以进程不能显示它曾经被暂停过

必须快速(因为上下文切换非常频繁)



进程控制

参考文章链接 https://blog.csdn.net/hanzheng6602/article/details/79971887

1)创建进程 fork()	复制当前进程 生成子进程

特点	调用一次 返回两次

fork调用失败返回-1,子进程中fork的返回值是0,而父进程中fork的 返回值则是子进程的id

用法

一个进程创建一个自身的副本,这样每个副本都 可以在另一个副本执行其他任务的同时处理各自的某个操作。 这是网络服务器的典型用法

一个进程想要执行另一个程序。既然创建新进程的唯一办法是调用fork, 该进程于是首先调用fork创建一个自身的副本,然后另一个副

本(通常为子进程)调用exec把自身替换成新的程序。 这是shell之类程序的典型用法

在父进程调用fork以后要用变量记录返回值即子进程的pid,否则就找不到子进程了,而子进程可以通过getppid找到父进程



vfork( )	

一个创建进程的系统调用,不需要创建一个同样的内存映像

一些时候称为轻量级fork()

子进程应该几乎立即调用exec()

现在不再使用如果我们使用Copy on Write(COW)技术



写时复制

一般情况下,子进程和父进程共享地址空间(页表),当有一方需要对数据内容进行修改的话,才对各自拷贝一份副本(拷贝修改数据的

页表)。由于代码段肯定是不会改变的,所以代码段一直享用同一块地址空间



2)系统调用 exec() 加载程序取代当前运行的程序

原进程pcb中信息是通过三级映射将虚拟空间映射在内存空间,exec删除进程pcb信息,从磁盘把新代码调入内存,再形成pcb里的映射。

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程

序。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

调用成功就执行新代码,调用失败返回-1



Exec( )调用允许一个进程加载一个不同的程序并且在main开始执行(事实上 _start)

它允许一个进程指定参数的数量(argc)和它字符串参数数组(argv).如果调用成功

它是相同的进程,但是它运行了一个不同的程序!

代码, stack(栈)& heap(堆) 会重写



3)wait()系统调用是被父进程用来等待子进程的结束

执行原因

一个子进程向父进程返回一个值,所以父进程必须接受这个值并处理



wait()系统调用担任这个要求 

它使父进程去睡眠来等待子进程的结果

当一个子进程调用exit()的时候,操作系统解锁父进程,并且将通过exit()传递得到的返回值作为wait调用的一个结果(连同子进程的pid一

起)如果这里没有子进程存活,wait()立刻返回

当然,如果这里有为父进程的僵尸等待,wait()立即返回其中一个值(并且解除僵尸状态)



4)进程结束,调用exit()

这个系统调用:

将这程序的“结果”作为一个参数

关闭所有打开的文件,连接等等

释放内存

释放大部分支持进程的操作系统结构

检查是否父进程是存活着的

​	如果是的话,它保留结果的值直到父进程需要它;在这种情况里,进程没有真正死亡,但是它进入了僵尸(zombie/defunct)状态

​	如果没有,它释放所有的数据结构,这个进程死亡

清理所有等待的僵尸进程



进程终止是最终的垃圾收集(资源回收)

第八章:CPU调度

上下文切换

从一个进程/线程到另一个

保存当前进程/线程在PCB/TCB中的执行上下文(CPU状态)

读取下一个进程/线程的上下文



CPU调度

从就绪队列中挑选一个进程/线程作为CPU将要运行的下一个进程/线程

调度程序 	挑选进程/线程的内核函数

从一个状态到另一个状态的时候会发生调度



内核运行调度程序的条件(满足其一即可)

一个进程从运行状态切换到等待状态

一个进程被终结了



CPU调度方式

(1)不可抢占

调度程序必须等待事件结束(效率低,不采用)

(2)可以抢占

调度程序在中断被响应后执行

当前的进程从运行切换到就绪,或者一个进程从等待切换到就绪

当前运行的进程可以被换出



注意,以上一般指用户态;内核态也可能涉及到是否抢占。





调度原则

1)调度策略

2)程序执行模型

执行模型:程序在CPU突发和I/O中交替

每个调度决定都是关于在下一个CPU突发时将哪个工作交给CPU

在时间分片机制下,线程可能在结束当前CPU突发前被迫放弃CPU

3)比较调度算法的准则

CPU使用率:CPU处于忙状态所占时间的百分比

吞吐量:在单位时间内完成的进程数量

周转时间(等待+响应):一个进程从初始化到结束,包括所有等待时间所花费的时间		

等待时间:进程在就绪队列中的总时间

响应时间:从一个请求被提交到产生第一次响应所花费的时间

4)吞吐量vs延迟

对快的评价指标:传输文件时的高带宽;玩游戏时的低延迟(这两点相互独立)

减少响应时间:及时处理用户的输出并尽快将输出提供给用户

减少平均响应时间的波动:在交互系统中,可预测性比高差异/低更重要

增加吞吐量:减少开支(操作系统开销/上下文切换)	系统资源的高效利用(CPU,I/O设备)

减少等待时间:减少每个进程的等待时间

吞吐量是操作系统的计算带宽;响应时间是操作系统的计算延迟。

5)公平的目标

举例

保证每个进程占用相同的CPU时间(并不公平,因为一个用户可能比其它用户运行更多进程)

保证每个进程都有相同的等待时间

公平通常会增加平均响应时间



调度算法

1)FCFS 先来先服务,first come,first served

优点

简单

缺点

平均等待时间波动较大(没有抢占)

花费时间少的任务可能排在花费时间长的任务后面

可能导致I/O和CPU之间的重叠处理(CPU密集型进程会导致I/O设备闲置时,I/O密集型进程也在等待)

2)SPN(SJF) SRT: 短进程优先(短作业优先)短剩余时间优先,shortest process next(shortest job first) shortest remaining time

SRT考虑抢占

选择下一个最短的进程(短任务优先),即按照预测的完成时间排序将任务入列

注意,它可以是抢占的也可以是不抢占的,对于抢占类型的,才是SRT短剩余时间优先

优点

平均周转时间最少

缺点

可能导致饥饿(连续的短任务流会使长任务饥饿;

短任务可用时的任何长任务的CPU时间都会增加平均等待时间);

(可能违背公平原则)



需要预估下一个CPU突发的持续时间

解决办法:询问用户

如果用户欺骗就杀死进程

用户根据历史时间分配预估未来时间的分配(也就是根据之前该进程所花的时间来预测它将要花多少时间)

加权求值



3)HRRN 最高响应比优先,highest response ratio next

在SPN调度的基础上改进(考虑了等待时间,改善饥饿现象);

不可抢占;

关注进程等待了多长时间;

防止无限期推迟;

R = (w + s) / s (选择R值最高的进程) ,其中

w:waiting time等待时间 	s:service time执行时间



4)Round robin 轮循,使用时间切片和抢占来轮流执行任务

在一个叫做 量子(或时间切片)对离散单元中分配处理器

如果时间片结束了,那么就接换到下一个准备好的进程(processes execute every (n-1)q time units)

优点:

公平

缺点

额外的上下文切换花销;

如果时间量子太大,则等待时间过长,极限情况下退化成FCFS;

如果时间量子太小,虽反应迅速,但是开销大;吞吐量由于大量的上下文切换开销而受到影响;

目标

选择一个合适的时间量子;

经验规则:维持上下文切换开销处于1%以内。



5)Multilevel feedback queues	多级反馈队列	优先级队列中的轮循

多级队列

兼顾到RR和SRT的优点。

就绪队列被划分为独立的队列,比如说前台队列是负责交互的进程,后台队列负责批处理进程

每个队列可以有自己的调度策略,比如说前台队列可以是RR,后台队列可以是FCFS

有一点要注意,就是调度必须在队列间进行

固定优先级,例如先处理前台进程,后处理后台,但是可能会造成饥饿;

时间切片,每个队列都得到一个确定的能够调度其进程的CPU总时间,例如,80%给前台,20%给后台



多级反馈队列

对于I/O密集型任务,放在高优先级队列,这里的时间片比较短

对于CPU密集型任务,放在低优先级队列,这里的时间片比较长

(我们希望交互性好,占用资源多的可以放在后面)

一个进程可以在不同的队列中移动

例如,时间量子的大小随优先级级别的增加而增加;如果任务在当前的时间量子中没有完成,那么就降到下一个优先级

优点

CPU密集型任务的优先级下降很快(占用时间长放后面再来)

I/0密集型任务停留在高优先级



6))Fair share scheduling, 公平共享调度

站在用户的角度实现公平共享CPU资源。因为有的用户可能开的进程多,有的用户进程少。

公平

一些用户组比其他用户组更重要

保证不重要的组无法垄断资源

未使用的资源按照每个组所分配的资源的比例来分配

没有达到资源使用目标的组获得更高的优先级



不同调度模型的评价准则

确定性建模:确定一个工作量,然后计算每个算法的表现

队列模型:用来处理随机工作负载的数学方法

实现/模拟:建立一个允许算法运行实际数据的系统



实时系统

定义	正确性依赖于其时间和功能两方面的一种操作系统

性能指标

时间约束的及时性(deadlines)

速度和平均性能相对不重要

主要特性

时间约束的可预测性

分类

强实时系统	需要在保证的时间内完成重要的任务,必须完成

弱实时系统	要求重要的进程的优先级更高,尽量完成,并非必须



任务(工作单元job):一次计算,一次文件读取,一次信息传递等

属性:取得进展所需资源;定时参数

周期任务	任务有规律地重复

周期 p 

执行时间 E = 最大执行时间

使用率U = e / p;



硬时限

如果错过了最后期限,可能会发生灾难性或非常严重的后果

保证确定性

软时限

理想情况下,时限应该被最大满足。如果有时限没有被满足,那么就相应地降低要求

尽最大努力去保证



可调度性

表示一个实时系统是否可以满足deadline要求

决定实时任务执行的顺序

静态优先级调度:运行之前优先级就是确定的

动态优先级调度:优先级在运行中是动态变化的



实时系统调度算法

RM(Rate Monotonic)速率单调调度

最佳静态优先级调度

通过周期安排优先级

周期越短,优先级越高

先执行周期最短的任务

EDF(Earliest Deadline First)最早期限调度

最佳的动态优先级调度

Deadline越早,优先级越高

先执行Deadline最早的任务





多处理器调度的原因

多处理器的CPU调度更加复杂(多个相同的单处理器组成一个多处理器;它的优点是负载共享)

还有对称多处理器(SMP)(每个处理器运行自己的调度程序;需要在调度程序中同步)





优先级反转

优先级反转可以发生在任何基于优先级的可抢占的调度机制

当系统内的环境强制使高优先级任务等待低优先级任务时发生


优先级反转的持续时间取决于其它不相关任务的不可预测的行为

在这种情况下,高优先级可能比低优先级任务晚完成。

A要C  C被B抢占 A比B慢



优先级继承

低优先级任务即成高优先级任务的优先级,这个依赖于他们共享的资源。此时T3的优先级会动态的得到提升,此时B无法抢占C



优先级天花板:“拥有资源”的优先级和“所有可以锁定该资源任务中优先级最高的那个任务”的优先级相同(C拥有A的资源,所以它的优先

级提升到A)

除非当前进程的优先级高于系统中所有被锁定的资源的优先级的上线,否则任务尝试执行临界区的时候会被阻塞

持有最高优先级上限信号量锁的任务,会继承被该锁所阻塞的任务的优先级

第九章:同步

线程相对独立

不和其他线程共享资源或状态

确定性	输入状态决定结果 

可重现	能够重现起始条件,I/0

调度顺序不重要



合作线程

在多个线程中共享状态

不确定性

不可重现



不确定性和不可重现意味着bug可能时间歇性发生的



并行/并发优点

共享资源

效率

模块化	易于扩展



Race Condition 竞态条件

原子性问题



Atomic Operation 原子操作



相关概念

1)临界区(Critical section)

临界区是指进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域

2)互斥(Mutual exclusion)

当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源

3)死锁(DEADLOCK)

两个或以上的进程,在相互等待完成特定任务,而最终没法将自身任务进行下去

4)饥饿(Starvation)

一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行



解决并发问题的一些方法

方法1	禁用硬件中断

没有中断 就没有并发

缺点

一旦中断被禁用,线程就无法被停止,整个系统都会为你停下来

可能导致其他线程处于饥饿状态要是临界区可以任意长怎么办

无法限制响应中断所需的时间(可能存在硬件影响)



方法2	基于软件的解决方法

1)使用两个共享数据项

do{
	flag[i] = true;
	turn = j;
	while(flag[j] && turn == j);
	CRITICAL SECTION
	flag[i] = false;
	REMAINDER SECTION
}while(true);

满足互斥 

有限等待

前进

2)Bakery 算法

3)Dekker算法

4)Bakery算法



软件实现需求

复杂: 需要两个进程的共享数据项

需要忙等待: 浪费CPU时间

没有硬件保证的情况下无真正的软件解决方案: Perterson算法需要原子的LOAD和STORE指令



方法3	更高级的抽象	

操作系统提供更高级的编程抽象来简化并行编程

例如,锁,信号量

从硬件原语中构建



锁是更高等级的编程抽象

互斥可以使用锁来实现

通常需要一定等级的硬件支持



Test-and-Set 测试和置位

- 从内存中读取值
- 测试该值是否为1(然后返回真或假)
- 内存值设置为1

交换

- 交换内存中的两个值


```
bool TestandSet(bool *target){
		bool rv = *target;
		*target = true;
		return rv;
}

void Exchange(bool *a, bool *b){
		bool tmp = *a;
		*a = *b;
		*b = tmp;
}
```

两种都可以解决



可选的实现内容:

有忙等待

无忙等待(通过睡眠实现)

第十章:信号量和管程

信号量 semaphore		可以管理多个锁	计数器

P	Prolaag 	   减1	如果sem<0,等待,否则继续			类似LOCK

V	Verhoog  	加1	如果sem<=0,唤醒一个等待的P			

荷兰语的增加和减少。



信号量的性质

信号量必须是整数,初始值一般是大于0

信号量是一种被保护的变量(初始化后,唯一改变一个信号量值的方法只能是P()和V();操作只能是原子操作)

P()能阻塞,V()不会阻塞

信号量假定是公平的(FIFO;如果一直有V()操作,则不会有进程被P()所阻塞)



信号量包括两种类型:

二进制信号量:0或1

计数信号量:任何非负整数



用途

互斥

条件同步	(例如线程A需要B执行到某一时刻才能继续执行)



信号量的使用

用二进制信号量实现lock功能,也就是互斥

用二进制信号量实现线程同步(调度约束)



同步问题出现的场景

一个线程等待另一个线程处理事情,例如生产者-消费者模型,此时互斥(锁机制)是不够的

例如生产者-消费者模型就需要一个有界缓冲区

一个或多个生产者产生数据并将数据放在缓冲区中

单个消费者每次从缓冲区取出数据

在任何一个时间只有一个生产者或消费者可以访问缓冲区



生产者-消费者模型的正确性要求

在任何一个时间只能有一个线程操作缓冲区(互斥)

当缓冲区为空时,消费者必须等待(调度/同步约束)

当缓冲区满了时,生产者必须等待(调度/同步约束)



 生产者-消费者模型实现策略

利用一个二进制信号量实现互斥,也就是锁的功能

用一个计数信号量fullbuffers来约束生产者

用一个计数信号量emptybuffers来约束消费者



信号量的实现	用过等待队列

注意,sem>0,说明计算机能满足所有需求,不需要把线程弄到等待队列里

P()

标记减一,如果标记小于0了,说明资源不够,需要把线程加入队列

V()

标记加一,如果标记小于等于0,说明等待队列中有元素,唤醒一个队列中的元素并执行

信号量的用途	互斥和条件同步(注意等待的条件是独立的互斥);

信号量的缺点	读/开发代码困难;容易出错(使用的信号量被另一个线程占用,完了释放信号量);不能够处理死锁







管程	monitor	共享变量加操作函数

管程的目的:分离互斥和对条件同步的关注

管程的定义:一个锁(临界区)加上0个或若干个条件变量(等待/通知信号量用于管理并发访问的共享数据)

管程实现的一般方法

收集在对象/模块中的相关共享数据

定义方法来访问共享数据



管程的组层

(1)Lock()

Lock:Acquire()	如果锁可用,就抢占锁(上锁)

Lock:Release()	释放锁,如果这个时候有等待者,就唤醒

Lock操作保证互斥

(2)Contion Variable

如果条件不能满足就wait(),一旦条件满足了,signal()会唤醒那些在队列中的线程并继续执行



管程的实现

(1)wait()

numWaiting就是计数器,统计有多少个线程处于等待队列中

release()因为当前线程在睡眠,就必须把锁打开一下,以便就绪状态的线程去执行,如果不release可能会造成死锁

schedule()的意思是,当前线程在wait()了,在队列里睡眠了,此时就需要选择一个就绪态的线程去执行

就绪态的线程执行完毕后就可以再上锁。

(2)signal()

如果等待队列中有元素,那么就把队列的头元素取出来(并删掉)并唤醒wakeup,此时这个线程就是就绪状态了,它下一步操作就是

wait()里的schedule(),执行这个线程

注意,signal仅在等待队列中有元素的时候才对numberWaiting执行(减法操作),

而信号量不一样,在P()和V()一定会有对信号量的加减操作的





经典同步问题

读者-写者问题

哲学家问题

不太理解

第十一章:死锁

死锁问题

一组阻塞的进程持有一种资源等待获取另一个进程所占有的一个资源



系统模型

进程使用资源过程

请求

使用

释放



如何使用可重复使用的资源

- 在一个时间只能有一个进程使用且不能被删除
- 进程获得资源,后来释放由其他进程重用
- 处理器,IO通道,主和副存储器,设备和数据结构,如文件,数据库和信号量
- 如果每个进程拥有一个资源并请求其他资源,死锁可能发生

如何使用资源

- 创建和销毁

- 在IO缓存区的中断,信号,消息,信息

- 如果接收消息阻塞可能会发生死锁

- 可能少见的组合事件会引起死锁

  

判断死锁

如果图中不包含循环:

- 没有死锁

如果图中包含循环:

- 如果每个资源类只有一个实例,那么死锁
- 如果每个资源类有几个实例,可能死锁



死锁特征

死锁出现一定会出现以下四个条件,但是出现以下四个条件不一定死锁

1)互斥: 在一个时间一个资源只能由一个进程使用

2)持有并等待: 进程保持至少一个资源正在等待获取其他进程持有的额外资源

3)无抢占: 一个资源只能被进程资源释放,进程已经完成了它的任务之后

4)循环等待: 存在等待进程集合{P0,P1,...,Pn},P0正在等待P1所占用的资源,P1正在等待P2占用的资源...Pn-1在等待Pn的资源,Pn正在等待P0所占用的资源





死锁处理方法

常见方法

- 确保系统永远不会进入死锁状态

- 运行系统进入死锁状态,然后恢复.

- 忽略这个问题,假装系统中从来没有发生死锁,用于大多数操作系统,包括UNIX(常用)

  

Deadlock Prevention	死锁预防(打破死锁出现的某一条件)

限制申请方式

互斥 - 共享资源不是必须的,必须占用非共享资源

占用并等待 - 必须保证当一个进程请求的资源,它不持有任何其他资源

- 需要进程请求并分配其所有资源,它开始执行之前或允许进程请求资源仅当进程没有资源
- 资源利用率低,可能发生饥饿

无抢占 - 

- 如果进程占有某些资源,并请求其他不能被立即分配的资源,则释放当前正占有的资源
- 被抢占资源添加到资源列表中
- 只有当它能够获得旧的资源以及它请求新的资源,进程可以得到执行

循环等待 - 对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请



Deadlock Avoidance	死锁避免	(申请时判断是否会出现死锁)

需要系统具有一些额外的先验信息提供

- 最简单和最有效的模式是要求每个进程声明它可能需要的每个类型资源的最大数目

- 资源的分配状态是通过限定提供与分配的资源数量,和进程的最大需求

- 死锁避免算法动态检查的资源分配状态,以确保永远不会有一个环形等待状态

- 当一个进程请求可用资源,系统必须判断立即分配是否能使系统处于安全状态

- 系统处于安全状态指:针对所有进程,存在安全序列

- 序列<P1,P2,...,Pn>是安全的: 针对每个Pi,Pi要求的资源能够由当前可用的资源+所有的Pj持有的资源来满足,其中j<i.

  - 如果Pi资源的需求不是立即可用,那么Pi可以等到所有Pj完成
  - 当Pi完成后,Pi+1可以得到所需要的资源,执行,返回所分配的资源,并终止.
  - 用同样的方法,Pi+2,Pi+3和Pn能获得其所需的资源.

- 如果系统处于安全状态→无死锁

- 如果系统处于不安全状态→可能死锁         unsafe > deadlock

- 避免死锁: 确保系统永远不会进入不安全状态

  

Deadlock Detection	死锁检测 (允许进入死锁状态)

定期调用检测算法来搜索是否存在循环

复杂度较大且需要知道每一个进程需要的最大资源个数

数据结构:

- Available: 长度为M的向量表示每种类型可用资源的数量
- Allocation: 一个nxm矩阵定义了当前分配给各个进程每种类型资源的数量,如果Alocation[i, j] = k, 进程Pi拥有资源Rj的k个实例
- Request: 一个nxm矩阵表示各进程的当前请求.如果Request[i, j] = k,表示进程Pi请求k个资源Pj的实例

检查算法的使用

何时,使用什么样的频率来检测依赖于:

- 死锁多久可能会发生
- 多少进程需要被回滚   one for each disjoint cycle

如果检测算法多次被调用,有可能是资源图有多个循环,所以我们无法分辨出多个可能死锁进程中的哪些"造成"死锁



Recovery from Deadlock	死锁恢复

终止所有的死锁进程

在一个时间内终止一个进程直到死锁消除

终止进程的顺序应该是:

- 进程的优先级
- 进程运行了多久以及需要多少时间才能完成
- 进程占用的资源
- 进程完成需要的资源
- 多少进程需要被终止
- 进程是交互还是批处理

选择一个受害者 - 最小的成本

回滚 - 返回到一些安全状态,重启进程到安全状态

饥饿 - 同一进程可能一直被选作受害者,包括回滚的数量



IPC	Inter-Process Communication 进程间通信

通信模型

如果P和Q想通信,需要:

- 在它们之间建立通信链路
- 通过send/recevie交换消息

通信链路的实现

- 物理(例如,共享内存,硬件总线)
- 逻辑(例如,逻辑属性)



直接通信

进程必须正确的命名对方:

- send(P, message) - 发送消息到进程P
- receive(Q, message) - 从进程Q接收信息

通信链路的属性

- 自动建立链路

- 一条链路恰好对应一对通信进程

- 每对进程之间只有一个链路存在

- 链路可以是单向的,但通常是双向的

  

间接通信

定向从消息队列接收消息

- 每个消息对垒都有一个唯一的ID
- 只有它们共享了一个消息队列,进程才能够通信

通信链路的属性

- 只有进程共享一个共同的消息队列,才建立链路
- 链接可以与许多进程相关联
- 每对进程可以共享多个通信链路
- 链接可以是单向或者双向

操作

- 创建一个新的消息队列
- 通过消息队列发送和接收消息
- 销毁消息队列

原语的定义如下:

- send(A, message)

- receive(A, message)

  

消息传递方式

阻塞	同步	发送完成才会返回

非阻塞	异步	不管是否成功都返回



通信链路缓冲

通信链路缓存大小

0容量 - 0 message : 发送方必须等待接收方	类似同步发送方式

有限容量 - n messages的有限长度 : 发送方必须等待,如果队列满

无限容量 - 无限长度 : 发送方不需要等待	类似异步



进程间通信方式

1)信号Signal		软件中断

通知事件处理

接收到信号时会发生什么

- catch: 指定信号处理函数被调用
- ignore: 依靠操作系统的默认操作(abort, memory dump, suspend or resume process)
- mask: 闭塞信号因此不会传送(可能是暂时的,当处理同样类型的信号)

不足      不能传输要交换的任何数据	一般是bit

2)管道	pipe	

实现从一个进程输出到另一个进程输入的重定向

用来数据交换

子进程从父进程继承文件描述符

进程不知道(或不关心)从键盘,文件,程序读取或写入到终端,文件,程序.

例如: $ ls | more (两个进程, 管道是缓存,对于ls来说是stdout,对于more来说是stdin )

父进程帮助子进程建立通信关系

3)消息队列

消息队列按FIFO来管理消息

- message: 作为一个字节序列存储
- message queues: 消息数组
- FIFO & FILO configuration

4)共享内存

每个进程都有私有地址空间

在每个地址空间内,明确地设置了共享内存段

优点	快速,方便地共享数据

不足	必须同步数据访问

是最快的方法

一个进程写另一个进程立即可见

没有系统调用干预

没有数据复制

不提供同步

5)套接字 Socket

其他通信机制不同的是,它可用于不同机器间的进程通信。

第十二章:文件管理

基本概念

1)文件系统和文件

文件系统: 一种用于持久性存储的系统抽象

- 在存储上: 组织,控制,导航,访问和检索数据
- 在大多数计算机系统包含文件系统
- 个人电脑,服务器,笔记本电脑
- ipod,tivo,机顶盒,手机,电脑
- google可能也是由一个文件系统构成的

文件: 文件系统中的一个单元的相关数据在操作系统中的抽象

文件系统的功能

- 分配文件磁盘空间
  - 管理文件块(哪一块属于哪一个文件)
  - 管理空闲空间(哪一块是空闲的)
  - 分配算法(策略)
- 管理文件集合
  - 定位文件及其内容
  - 命名: 通过名字找到文件的接口
  - 最常见: 分层文件系统
  - 文件系统类型(组织文件的不同方式)
- 提供的便利及特征
  - 保护: 分层来保护数据安全
  - 可靠性,持久性: 保持文件的持久即使发生崩溃,媒体错误,攻击等

文件属性: 名称,类型,位置,大小,保护,创建者,创建时间,最久修改时间...

文件头: 在存储元数据中保存了每个文件的信息,保存文件的属性,跟踪哪一块存储块属于逻辑上文件结构的哪个偏移



2)文件描述符

文件使用模式

使用程序必须在使用前先"打开"文件

```
f = open(name, flag);
...
... = read(f, ...);
...
close(f);
```

内核跟踪每个进程打开的文件

- 操作系统为每个进程维护一个打开文件表
- 一个打开文件描述符是这个表中的索引



需要元数据来管理打开文件

- 文件指针: 指向最近的一次读写位置,每个打开了这个文件的进程都这个指针
- 文件打开计数: 记录文件打开的次数 - 当最后一个进程关闭了文件时,允许将其从打开文件表中移除
- 文件磁盘位置: 缓存数据访问信息
- 访问权限: 每个程序访问模式信息
- 用户视图: 持久的数据结构
- 系统访问接口:   
  - 字节的集合(UNIX)        
  - 系统不会关心你想存储在磁盘上的任何的数据结构
- 操作系统内部视角:
  - 块的集合(块是逻辑转换单元,而扇区是物理转换单元)
  - 块大小<> 扇区大小: 在UNIX中, 块的大小是 4KB



当用户说: 给我2-12字节空间时会发生什么?

1. 获取字节所在的快
2. 返回快内对应部分

如果要写2-12字节?

1. 获取块
2. 修改块内对应部分
3. 写回块

在文件系统中的所有操作都是在整个块空间上进行的: 

`getc()` `putc()` 即使每次只访问1字节的数据,也会缓存目标数据4096字节(一个磁盘块)

用户怎么访问文件: 在系统层面需要知道用户的访问模式

- 顺序访问: 按字节依次读取(几乎所有的访问都是这种方式)

- 随机访问: 从中间读写

  不常用,但是仍然重要,如: 虚拟内存支持文件,内存页存储在文件中

  更加快速,不希望获取文件中间的内容的时候也必须先获取块内所有字节

- 内容访问: 通过特征

  数据库是建立在索引内容的磁盘访问上进行访问的

文件内部结构

- 无结构: 单词,比特的队列

- 简单记录结构: 列,固定长度,可变长度

- 复杂结构: 格式化的文档(word, PDF), 可执行文件, ...



多用户系统中的文件共享是很必要的

访问控制:

- 谁能够获得哪些文件的哪些访问权限

- 访问模式: 读,写,执行,删除,列举等

文件访问控制列表(ACL):

- <文件实体, 权限>

UNIX模式:

- <用户|组|所有人,读|写|可执行>

- 用户ID识别用户,表明每个用户所允许的权限及保护模式

- 组ID允许用户组成组,并指定了组访问权限

指定多用户,客户如何同时访问共享文件:

- 和过程同步算法相似

- 因磁盘IO和网络延迟而设计简单

UNIX文件系统(UFS)语义:

- 对打开文件的写入内容立即对其他打开同一文件的其他用户可见

- 共享文件指针允许多用户同时读取和写入文件

会话语义:

- 写入内容只有当文件关闭时可见

锁:

- 一些操作系统和文件系统提供该功能



3)目录

文件以目录的方式组织起来

目录是一类特殊的文件: 每个目录都包含了一张表



典型操作

1. 搜索文件
2. 创建文件
3. 删除文件
4. 枚举目录
5. 重命名文件
6. 在文件系统中遍历一个路径



操作系统应该只允许内核模式修改目录: 确保映射的完整性,应用程序能够读目录(ls)

文件名的线性列表,包含了指向数据块的指针: 编程简单,执行耗时





Hash表 - hash数据结构的线性表:

减少目录搜索时间,碰撞,固定大小



名字解析: 逻辑名字转换成物理资源(如文件)的过程:

- 在文件系统中: 到实际文件的文件名(路径)

- 遍历文件目录直到找到目标文件



举例: 解析"/bin/ls":

读取root的文件头(在磁盘固定位置)

读取root的数据块: 搜索bin项

读取bin的文件头

读取bin的数据块: 搜索ls项

读取ls的文件头



当前工作目录

- 每个进程都会指向一个文件目录用于解析文件名

- 允许用户指定相对路径来代替绝对路径



一个文件系统需要先挂载才能被访问

一个未挂载的文件系统被挂载在挂载点上



4)文件别名

两个或多个文件名关联同一个文件

实现

- 硬链接: 多个文件项指向一个文件		

- 软链接: 以快捷方式指向其他文件
  - 通过存储真实文件的逻辑名称来实现



如果删除一个有别名的文件会如何呢? 这个别名将成为一个悬空指针

添加一个间接层: 目录项数据结构

链接: 已存在文件的另外一个名字(指针)

链接处理: 跟随指针来定位文件

我们如何保证没有循环呢?

- 只允许到文件的链接, 不允许在子目录的链接

- 每增加一个新的链接都用循环检测算法确定是否合理
- 限制路径可遍历文件目录的数量



5)文件系统种类

1. 磁盘文件系统: 文件存储在数据存储设备上,如磁盘; 例如: FAT,NTFS,ext2,3,ISO9660等
2. 数据库文件系统: 文件根据其特征是可被寻址的; 例如: WinFS
3. 日志文件系统: 记录文件系统的修改,事件; 例如: journaling file system
4. 网络,分布式文件系统: 例如: NFS,SMB,AFS,GFS
5. 特殊,虚拟文件系统



虚拟文件系统

分层结构

顶层: 文件/文件系统API

上层: 虚拟(逻辑)文件系统 (将所有设备IO,网络IO全抽象成为文件,使得接口一致)

底层: 特定文件系统模块

目的: 对所有不同文件系统的抽象

功能:

- 提供相同的文件和文件系统接口

- 管理所有文件和文件系统关联的数据结构

- 高效查询例程,遍历文件系统

- 与特定文件系统模块的交互

数据结构

1)卷控制块(UNIX: "superblock")

- 每个文件系统一个

- 文件系统详细信息

- 块,块大小,空余块,计数,指针等

2)文件控制块(UNIX: "vnode" or "inode")

- 每个文件一个

- 文件详细信息

- 许可,拥有者,大小,数据库位置等

3)目录节点(Linux: "dentry")

- 每个目录项一个(目录和文件)

- 将目录项数据结构及树形布局编码成树形数据结构

- 指向文件控制块,父节点,项目列表等



持续存储在二级存储中  在分配在存储设备中的数据块中

当需要时加载进内存

- 卷控制块: 当文件系统挂载时进入内存

- 文件控制块: 当文件被访问时进入内存

- 目录节点: 在遍历一个文件路径时进入内存



数据块缓存

数据块按需读入内存

- 提供 `read()` 操作

- 预读: 预先读取后面的数据块

数据块使用后被缓存:

- 假设数据将会再次被使用

- 写操作可能被缓存和延迟写入



两种数据块缓存方式:

- 普通缓冲区缓存

- 页缓存: 同一缓存数据块和内存页

分页要求

当需要一个页时才将其载入内存

支持存储

一个页(在虚拟地址空间中)可以被映射到一个本地文件中(在二级存储中)



打开文件的数据结构

打开文件描述

- 每个被打开的文件一个

- 文件状态信息

- 目录项,当前文件指针,文件操作设置等

打开文件表

- 一个进程一个

- 一个系统级的

- 每个卷控制块也会保存一个列表

- 所以如果有文件被打开将不能被卸载

一些操作系统和文件系统提供该功能

调节对文件的访问

强制和劝告

强制 - 根据锁保持情况和需求拒绝访问

劝告 - 进程可以查找锁的状态来决定怎么做



文件分配

- 大多数文件都很小

  ​	需要对小文件提供强力的支持

  ​	块空间不能太小

- 一些文件非常大

  ​	必须支持大文件(64-bit 文件偏移)

  ​	大文件访问需要相当高效



分配方式

- 连续分配

- 链式分配

- 索引分配

指标

- 高效	如存储利用(外部碎片)

- 表现    如访问速度



1)连续分配

文件头指定起始块和长度

位置/分配策略: 最先匹配,最佳匹配,...

优势: 文件读取表现好;高效的顺序和随机访问

劣势: 碎片;文件增长问题

2)链式分配

文件以数据块链表方式存储

文件头包含了到第一块和最后一块的指针

优势: 创建,增大,缩小很容易;没有碎片

劣势: 不可能进行真正的随机访问;可靠性

3)索引分配

为每个文件创建一个名为索引数据块的非数据数据块(到文件数据块的指针列表)

文件头包含了索引数据块

优势: 创建,增大,缩小很容易;没有碎片;支持直接访问

劣势: 当文件很小时,存储索引的开销大;处理大文件难(链式索引,多级索引)



空闲空间列表

跟踪在存储中的所有未分配的数据块

数据结构

用位图代表空闲数据块列表: 11111101101110111 如果 i = 0表明数据块i是空闲的,反之是分配的

使用简单但是可能会是一个big vector:

160GB disk → 40M blocks → 5MB worth of bits

然而,如果空闲空间在磁盘中均匀分布,那么再找到"0"之前需要扫描 磁盘上数据块总数 / 空闲块的数目

需要保护

- 指向空闲列表的指针

- 位图

  ​	必须保存在磁盘上;在内存和磁盘拷贝可能有所不同;不允许block[i]在内存中的状态为bit[i]=1而在磁盘中bit[i]=0

- 解决:

  ​	在磁盘上设置bit[i] = 1; 分配block[i]; 在内存中设置bit[i] = 1

还有链式列表和分组列表





多磁盘管理	RAID

通常磁盘通过分区来最大限度减小寻道时间

- 一个分区是一个柱面的集合

- 每个分区都是逻辑上独立的磁盘
- 分区	硬件磁盘的一种适合操作系统指定格式的划分
- 卷	一个拥有一个文件系统实例的可访问的存储空间(通常常驻在磁盘的单个分区上)

使用多个并行磁盘来增加

- 吞吐量(通过并行)
- 可靠性和可用性(通过冗余)

RAID - 冗余磁盘阵列

- 各种磁盘管理技术
- RAID levels: 不同RAID分类,如RAID-0,RAID-1,RAID-5

实现

- 在操作系统内核: 存储,卷管理
- RAID硬件控制器(IO)

RAID-0

数据块分成多个子块, 存储在独立的磁盘中: 和内存交叉相似

通过更大的有效块大小来提供更大的磁盘带宽

RAID-1

可靠性成倍增长

读取性能线性增加(向两个磁盘写入,从任何一个读取)

RAID-4

数据块级磁带配有专用奇偶校验磁盘: 允许从任意一个故障磁盘中恢复

条带化和奇偶校验按byte-by-byte或者bit-by-bit: RAID-0,4,5: block-wise ;RAID-3: bit-wise

RAID-5

每个条带快有一个奇偶校验块,允许有一个磁盘错误

RAID-6

两个冗余块,有一种特殊的编码方式,允许两个磁盘错误



磁盘调度

读取或写入时,磁头必须被定位在期望的磁道,并从所期望的扇区开始

寻道时间: 定位到期望的磁道所花费的时间

旋转延迟: 从扇区的开始处到到达目的处花费的时间

平均旋转延迟时间 = 磁盘旋转一周时间的一半

寻道时间是性能上区别的原因

对单个磁盘,会有一个IO请求数目

如果请求是随机的,那么会表现很差



FIFO

- 按顺序处理请求

- 公平对待所有进程

- 在有很多进程的情况下,接近随机调度的性能

最短服务优先

- 选择从磁臂当前位置需要移动最少的IO请求

- 总是选择最短寻道时间

skan

- 磁臂在一个方向上移动,满足所有为完成的请求,直到磁臂到达该方向上最后的磁道

- 调换方向

- 有时被成为elevator algorithm

c-skan

限制了仅在一个方向上扫描

当最后一个磁道也被访问过了后,磁臂返回到磁盘的另外一端再次进行扫描

c-loop(c-skan改进):

磁臂先到达该方向上最后一个请求处,然后立即反转

N-Step-SCAN

将磁盘请求队列分成若干个长度为N的子队列,磁盘调度将按FCFS算法依次处理这些子队列。

而每处理一个队列时又是按SCAN算法,对一个队列处理完后,再处理其他队列。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值