操作系统学习

0.操作系统概论

第0部分搬运自知乎小浩

1.什么是操作系统:

一句话:搞管理的软件

操作系统的目标:方便、有效(提高系统资源的利用率,提高系统的吞吐量)、可扩充(模块化,层次化,微内核)、开放

 

2.操作系统的作用

一句话:便利用户和硬件交互和管理和分配计算机资源(包括硬件和软件)

 

操作系统最核心最关键的部分是kernel,那我们来了解下kernel

3.kernel是什么

它是基于硬件的第一层软件扩充,是最接近硬件的软件,无法用准确的具体概念来描述它,它是依托于操作系统这个概念而存在的,所以我们可以直接理解为kernel就是操作系统最基本的部分

 

4.kernel的作用

提供操作系统的最基本的功能,比如进程管理,文件系统管理,中断和I/O控制,内存管理,这些也是操作系统的功能,只不过内核提供的功能更加的基础,这些功能是实现操作系统功能的基础。

这个关系可以这么来理解,C是B的基础,B是A的基础,C是最基础的东西

 

5.OS kernel的特征:

并发:指一段时间内多个程序运行;而并行是指一个时间点上多个程序运行(并行)

异步:程序的执行不是一步到底的,而是走走停停,向前推进的速度不可预知,但只要运行环境相同,OS要保证程序运行的结果也相同

共享:概念: 系统中资源可供内存中多个并发执行的进程共同使用。分为互斥共享方式和同时访问方式

共享以进程的并发为条件,系统不能对共享有效管理,影响进程的并发

虚拟:利用多道程序设计技术,让每一个用户都觉得有一个计算机专门为他服务

多道程序设计:允许多个程序(作业)同时进入一个计算机系统的内存,启动并进行交替计算的方法,引入多道程序设计技术的根本目的是为了提高CPU的利用率,充分发挥计算机系统部件的并行性,

 

6.操作系统kernel的分类

单内核

单内核就是把它从整体上作为一个大的过程来实现,同时也运行在一个单独的地址空间上,其特点是高性能,单内核结构在硬件之上定义了一个高阶的抽象界面,应用系统调用来实现操作系统的功能,例如进程管理,文件系统,和存储管理等等,这些功能由多个运行在核心态的模块来完成。

 

微内核

​ 微内核结构由一个非常简单的硬件抽象层和一组比较关键的原语或系统调用组成,这些原语仅仅包括了建立一个系统必需的几个部分,如线程管理,地址空间和进程间通信等。微核的目标是将系统服务的实现和系统的基本操作规则分离开来。

 

混合内核:

​ 混合内核它很像微内核结构,只不过它的的组件更多的在核心态中运行以获得更快的执行速度。

 

外内核:

​外内核系统,也被称为纵向结构操作系统,是一种比较极端的设计方法。外内核这种内核不提供任何硬件抽象操作,但是允许为内核增加额外的运行库,通过这些运行库应用程序可以直接地或者接近直接地对硬件进行操作。
它的设计理念是让用户程序的设计者来决定硬件接口的设计

 

以下内容基于清华操作系统网课,作者整理总结

1.操作系统的启动

DISK:存放OS

BIOS:基本I/O处理系统(让计算机开机能检测外设,加载相应软件执行)

Bootloader:加载OS,将OS从硬盘放到内存中,使CPU执行操作系统

BIOS:1:首先从特定地址执行CS:IP = 0xf000:fff0(CS是段寄存器,IP是指令寄存器)

             2:下一步进行POST(加电自检):寻找显卡和执行BIOS,确认外设可以正常工作

             3:下一步将Bootloader从硬盘放到内存中0x7c00

Bootloader一般放在硬盘的第一个主引导扇区,仅占512字节

Bootloader进入内存后掌握CPU控制权,之后第一步:找到硬盘起始扇区以及硬盘操作系统起始扇区以及操作系统长度;然后将这块区域从硬盘中读到内存中;第三步将CPU控制权交给OS,跳到OS起始地址执行。

2.操作系统与设备和程序交互(中断,异常,系统调用)

为什么程序不直接访问硬件,而是通过操作系统:

在计算机运行中,内核(OS)是被信任的第三方,只有内核可以执行特权指令,为了方便应用程序,屏蔽底层复杂性和差异性,给上层提供简单的接口,使应用程序通用可移植。

 

系统调用(来源应用程序):应用程序向操作系统发送服务请求

异常(来源不良应用程序):非法指令或其他坏的处理状态(如内存出错)

中断(来源外设):来自不同的硬件设备的计时器和网络的中断

 

处理时间:

中断:异步。因为应用程序不知道何时中断(从内存读取文件慢,可以暂时中断,等数据读取完毕再停止中断,需要保存与恢复机制)

异常:同步。是指执行到特定指令一定异常,操作系统必须要处理(操作系统根据异常编号查找解决方案,必要时重新执行)

系统调用:可能异步,也可能同步。当其发出请求时,返回时间不确定,则可以在等待返回期间进行其他操作。(系统调用会存在在一张表里,需要用到某些系统调用时,我们可以通过索引号查表得方式来解决,存在与每个系统调用相关的序号,系统调用接口根据这些序号来维护表的索引。)

什么是异步:当这种情况发生时,cpu可以把他放在一边,然后自己去处理别的事情,而同步则是当情况发生时,cpu不能做别的事,必须等待当前问题被解决。

 

响应:

中断:持续,对用户应用程序透明

异常:杀死或重新执行意想不到的应用程序指令

系统调用:等待和持续

 

中断处理

中断在硬件中处理:设置中断标记[CPU初始化]

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

2.产生中断号发给OS,从而OS可以找到对应处理历程。

中断在软件中处理:

1.保存当前处理状态

2.中断服务程序处理

3.清除中断标记

4.恢复之前保存的处理状态

 

异常处理

异常在软件中处理:异常编号

1.保存现场

2.异常处理(杀死异常程序,重新执行异常指令)

3.恢复现场

 

系统调用处理

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

Win32API用于Windows

POSIX API 用于POSIX-based systems(包括UNIX,LINUX,Mac OS X的所有版本)

Java API用于JAVA虚拟机(JVM)(不是系统调用)

对于程序而言,系统调用的执行过程是透明的,不用关心内部细节,只需要知道系统调用会返回的结果就行了,大量的细节被隐藏在API中,并通过运行程序支持的库来管理。

和异常处理机制相似的是通常情况下,系统调用会存在在一张表里,需要用到某些系统调用时,我们可以通过索引号查表得方式来解决,存在与每个系统调用相关的序号,系统调用接口根据这些序号来维护表的索引。

 

系统调用还涉及到内核态和用户态切换的问题

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

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

 

应用程序和操作系统有各自的堆栈,这两个变化比函数调用的开销更大,但更安全可靠。(程序调用是在一个栈空间实现参数的调用和返回),内核态和用户态OS也有属于自己得不同的堆栈,信息跨堆栈传递会吃掉很多性能

跨越操作系统边界的开销

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

建立中断/异常/系统调用号与对应服务例程映射关系的初始化开销(需要在开始前建立映射表);

建立内核堆栈(操作系统和应用程序的堆栈不一样,退出需要保存堆栈,执行需要恢复堆栈);

验证参数(操作系统不信任应用程序,会检查数据);

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

 

3.计算机体系结构以及内存分层体系

1.计算机体系结构

CPU:控制程序或软件执行

内存:防止程序代码以及处理的数据

外设:配合程序发挥作用

2.内存层次结构

CPU可以访问寄存器和cache,这两部分都在CPU内部,操作系统无法直接进行管理,速度快容量小

主存/物理内存:放置操作系统本身以及需要执行的代码,容量比寄存器和cache大,但是速度慢

磁盘:放置主存放不下的数据,防止主存掉电数据丢失,速度比主存慢,但是容量大。

 

操作系统内存管理需要完成的四个目标:

抽象:应用程序不需要考虑底层细节,不用考虑物理内存与外设,只需要访问连续的地址空间即可(逻辑地址空间)

保护:就是维护程序地址的独立性,OS给一个程序分配一段内存空间,这个程序就在这段空间上运行,既不能跑到别的程序得空间去,也不能让别的程序跑进来,这就是对内存空间独立性的保护

共享:让内存可以被多个程序共同使用,程序内存之间有相互独立的,也会有共享使用的部分。

虚拟:最需要内存的数据放入内存中,暂时不需要的数据临时放置在硬盘上,营造了一种大内存的虚拟现象。

 

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

程序重定位,分段,分页,虚拟内存,按需分页虚拟内存

 

4.地址空间与地址生成

1.逻辑地址与物理地址

物理地址空间:硬件支持的地址空间(起始0到MaxSys)

逻辑地址空间:一个运行的程序所拥有的内存范围(起始0到MaxProg)

函数位置,变量形式即为逻辑地址

 

逻辑地址空间映射到物理地址空间

c程序(.e file)通过编译变为汇编程序(.s file)

汇编程序通过汇编器变为机器语言(.o file)起始地址从0开始,将函数和符号名转换成地址

机器语言通过linker工具将多个(.o file)变为可执行程序(.exe file)此时可执行程序仍在硬盘中

可执行程序通过loader应用程序,将执行程序放入内存中运行,完成逻辑地址分配,逻辑地址会有一定偏移量,可能为0或特定值,程序依照偏移量执行

CPU中有内存管理单元(MMU)管理逻辑地址与物理地址的映射关系

 

CPU执行某条指令过程:

首先计算逻辑单元(ALU)会需要指令内容,发送请求,即逻辑地址

第二步,MMU查找对应物理地址,若差找不到则去内存map中找

第三步,找到以后CPU控制器给主存发送物理地址请求

第四步,主存通过总线发给CPU

第五步,CPU执行指令

操作系统的工作是建立逻辑地址和物理地址映射关系,由CPU缓存。同时要保证应用程序之间互不干扰,访问的地址空间是合法的

基址寄存器存开始地址,界限寄存器存地址长度,CPU查找时若地址越界则内存异常,否则去相应物理地址

 

2.连续内存分配

操作系统需要将应用程序从硬盘加载到内存中,需要在内存中分配一个连续区域,在应用程序访问数据时,也需要在内存中分配空间

1.内存碎片

内存碎片问题:空间内存不能被利用

1.外部碎片:在分配空间之间未使用的内存

2.内部碎片:在分配空间内未使用的内存

 

2.分区的动态分配

1.首次分配:

为了分配n字节,选择第一个空闲内存空间大于n的内存块进行分配

特点:

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

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

    重分配需要检查,看是否自由分区能合并于相邻的      空闲分区

优势:简单

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

劣势:已产生外部碎片

           不确定性

2.最佳适配:

寻找整个内存空间中最适合内存请求的空闲内存块,(大小大于需求,且与需求差值最小)

特点:

    为了避免分裂大空间块

    为了最小化外部碎片尺寸

    需要按尺寸排列的空闲块列表

    分配需要寻找合适分区

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

优势:当大部分分配是最小尺寸时非常有效

           比较简单

劣势:外部碎片较小,进一步利用难

          重分配慢

          已产生很多无用小空闲块

3.最差适配:

寻找整个内存空间中与内存请求差值最大的空闲内存块

特点:

    避免有太多微小碎片

    需要按尺寸排列的空闲块列表

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

    重分配时需要合并相邻空闲块

优势:当分配是中等尺寸时最佳

劣势:重分配慢,易产生外部碎片

          易于破碎大空间块,导致大分区无法被分配

 

3.压缩式与交换式碎片整理

紧致算法(compaction):把非空闲内存块地址变为连续

重置程序以合并孔洞

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

 

交换式碎片整理

运行程序需要更多的内存,则可以抢占等待的程序,将其移至硬盘上

 

3.非连续内存分配

将在逻辑地址空间内连续的块映射到不连续的物理地址中

 

连续分配的缺点:分配给一个程序的物理内存是连续的,内存利用率低,有内外碎片问题

非连续分配的优点:程序的地址空间是非连续的,能更好的利用和管理内存,允许共享代码和数据,支持动态加载和动态链接

非连续分配的缺点:需要建立虚拟地址和物理地址之间的转换(软件方案,硬件方案)

硬件方案:

分段:更好的分离和共享

1.程序的分段地址空间

2.分段寻址方案

段访问机制:一个段表示一个内存块

程序内访问内存地址需要一个一维的二元组(s,addr)s表示段号,addr表示段内偏移,根据段号查找段表,寻找所在物理地址,段表中还存储了段起始地址和长度,段表索引index由段号决定,段表由操作系统建立

s与addr分开存储称为段寄存器+地址寄存器实现方案

s与addr合并存储称为单地址实现方案

 

分页

分页地址空间

页的页帧大小固定,是2的幂,比如512,4096,8192字节,划分逻辑地址空间与物理内存大小相同

frame是物理页,page是逻辑页

建立方案:页表,MMU,TLB

 

物理帧frame:物理内存被分割为大小相等的帧,物理内存地址是一个二元组(f,o),f是页号,有F位,共有2的F次方帧,o是帧内偏移,有S位,每帧有2的S次方字节,物理地址为2的S次方*f+o

假如地址空间为mbit,帧大小为nbit,则从右向左n位为帧内偏移值,前m-nbit为帧号

 

逻辑帧page:程序的逻辑地址空间被划分为大小相等的页,页内偏移的大小=帧内偏移的大小,页号大小和帧号大小可能不等,逻辑内存地址是一个二元组(p,o),p为页号,有P位,共有2的P次方帧,o为页内偏移,有S位,每帧有2的S次方字节,逻辑地址为2的S次方*p+o

 

页寻址机制

程序执行CPU首先寻址,页表存储的是以页号为索引的内容,首先找到基地址,根据页号找到帧号,可以找到对应物理地址,页表也是由操作系统在分页机制建立前建立

1.页映射到帧

2.页是连续的虚拟内存

3.帧是非连续的物理内存

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

好处是可以有效利用内存空间

 

页表:

每个运行的程序都有一个页表,属于程序运行状态,会动态变化

页表中有存在位resident bit,若为0则表示该逻辑页在物理内存中不存在,若为1则查找该逻辑页对应的物理帧号,然后偏移与逻辑偏移相同,查找地址

PTBR:页表基址寄存器

页表性能问题:访问一个内存单元需要2次内存访问,一次用于获取页表项,一次用于访问数据,当机器位数大,则页表大小会很大;当应用程序多时,页表也会很多。当页表大时,则无法放入CPU中,如果放在内存中,则每次寻址需要访问两次内存

解决方案:缓存(Caching)或间接访问(Indirection)

 

页表缓存(TLB)

缓存近期访问的页帧转换表项

TLB(Translation Look-aside Buffer)使用associative memory(关联访问)实现,具备快速访问性能,但是代价大,容量小,可以通过在写程序时尽量具有访问局部性,将访问集中在一个区域中,能有效减少TLB缺失

 

TLB存储的是(逻辑页号p,物理页号f)(key,value)格式,当需要寻址时,首先根据p查找TLB,寻找f值,找到后根据o可以直接获取物理地址。如果TLB未命中,且物理地址存在,对应的表项被更新到TLB中(与CPU有关,X86 CPU完全由硬件完成,不需要操作系统,但有些CPU会需要操作系统完成)

 

多级页表

一级页表中储存二级页表起始地址,每个页表项对应一个二级页表,二级页表存储frame number

好处是对于不存在对应关系的页表可以不用存储在一级页表,节省空间

多级页表则同理,建立页表树

“以时间换空间”:通过增加了访问次数,增加了访问开销,但是节省了存储空间,而增加的时间可以通过TLB缓解。

 

反向页表

大地址空间问题:有大地址空间,则前向映射页表变得繁琐(如64位操作系统即便用五级页表也占用很大空间)

考虑让页表不与逻辑地址空间大小对应,而是与物理地址空间大小对应。(逻辑地址空间增长速度大于物理地址空间)

 

基于页寄存器的方案:

帧号为索引,寄存器内包括Resident bit(此帧是否被占用),Occupier(对应的页号p),protection bits(保护位)

优势:转换表小占内存小,且与逻辑地址空间大小无关

劣势:根据帧号可以找到页号,那么如何根据页号找到帧号,以及如何找到需要的页号

 

基于关联内存的方案:

使用<key,vaule>形式存储页号和帧号,类似TLB,但是设计成本过大(占空间大,无法放入CPU,放入内存则会增加访问次数,时间长,耗电)。

 

基于哈希查找的方案:

需要硬件帮助计算哈希函数h(PID,p),加入PID(当前运行程序标识,为了减小哈希冲突),可以算出在帧表中的对应帧号,页i被放置在f(i)的位置,f为哈希函数

查找页i操作:1.计算哈希函数并将其作为页寄存表索引

2.获取对应页寄存器

3.检查寄存器标签是否包含i,包含则成功

 

5.虚拟内存

程序规模增长速度远大于存储器容量的增长速度

理想的存储器:更大、更快、更便宜的非易失性存储器

内存不够用怎么办:如果因为程序太大,可以手动覆盖,把需要的指令和数据保存在内存中;如果因为数据太多,可以采用自动交换技术,把暂时不能执行的程序放到外存中;如果想在有限内存中,以更小的页粒度为单位装入更大的程序,可以采用自动的虚拟存储技术

 

覆盖技术

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

原理:把程序按照自身逻辑结构,划分为若干功能上独立的程序模块,把那些不会同时执行的模块共享同一块内存区域,按时间先后来运行。

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

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

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

 

内存分配时,只要保证每部分包含的最大的子代码块能运行即可

缺点:由程序员划分程序模块,并确定模块间覆盖关系,费时费力,增加了编程复杂度;且模块从外存装入内存,以时间换空间。

 

交换技术

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

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

操作系统把一个进程的整个地址空间内容保存到外存(换出swap out),将外存中的某个进程的地址空间读入到内存中(换入swap in),换入换出内容的大小为整个程序的地址空间(导入导出消耗大)。

 

交换过程中几个问题:

交换时机的确定:何时交换?仅当内存不够或将要不够的时候换出,频繁换入换出会很大影响性能。

交换区的大小:必须足够大,能对这些内存映像进行直接读取

程序换入时重定位:最好采用动态地址映射(如页表虚定位映射)

 

覆盖与交换的比较

覆盖只能发生在没有调用关系的程序模块间,因此程序员必须给出程序内模块间逻辑覆盖结构

交换技术是以内存中的程序为单位进行的。

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

 

虚存技术

覆盖技术需要程序员管理覆盖关系,增加了程序员负担;交换技术需要把整个进程地址空间换入换出,增加了处理器开销。所以需要使用虚存技术

目标:自动的,把程序的部分内容进行交换。

 

程序的局部性原理:

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

时间局部性:指令的执行核数据的访问集中在一个较短时间内

空间局部性:当前指令及其邻近指令集中在一个较小区域内

 

虚存特征:

用户空间大:把物理内存与外存结合,作为虚拟内存空间提供给用户。

部分交换:只对部分虚拟地址空间进行交换。

不连续性:物理内存分配不连续,虚拟内存空间使用不连续

 

虚拟页式内存管理

在页式存储管理的基础上,增加请求调页和页面置换功能

当程序要调入内存时,只装入部分页面即可启动程序运行,在运行过程中,如果发现要运行的程序或访问的数据不在内存中时,发出缺页中断请求,系统将外存中对应页面调入内存,使程序能够继续运行。如果内存不够则进行页面置换。

 

虚拟页式内存管理页表表项

在逻辑页号与物理页帧号之间还有如下几位

驻留位:为1则表示该页位于内存中,为0则表示该页在外存中,访问时将导致缺页中断

保护位:表示该页允许的访问,如只读、可读写、可执行等。

修改位:表示该页在内存中是否被修改过,当系统回收时决定是否将其写回外存。

访问位:表示该页是否被访问过(读或写),用于页面置换算法(不用则换出)

 

缺页中断

如果访问的页的驻留位为0则产生缺页异常,操作系统处理步骤:

1.查找内存中是否有空闲物理页,有的话进入第四步,没有的话进入第二步

2.用页面置换算法替换内存中物理页,根据修改位判断是否需要写入外存

3.被替换出去的物理页对应逻辑页驻留位置零

4.把硬盘数据存入内存中

5.将驻留位置1,把物理页帧号改为存入的帧号f

6.重新运行被中断的指令

 

后备存储

未被映射的页存储在磁盘或者文件中,有特定的交换分区用于存储换入换出的数据

虚拟地址空间的页面映射到后备存储中存储的物理地址。

 

虚拟内存性能

平均访问时间EAT = 10(1-p)+5000000p(1+q)

p为缺页概率,q为内存页被修改概率,10为访问内存时间,5000000为访问外存时间

 

6.页面置换算法

当缺页中断发生时需要调入新的页面,选择内存中物理页换出

目标:尽可能减少页面换入换出次数(减少缺页中断次数)

页面锁定:对必须常驻内存的关键部分,或时间关键的应用进程,进行锁定,在页面置换时不对这些部分进行置换。实现方法通过在页表中添加锁定标志位。

 

局部页面置换算法

最优页面置换算法

缺页中断时,对于保存的每一个逻辑页,计算该页到下一次被访问的等待时间,将最长等待时间页面换出。但实际中难以预测未来访问时间。

可用作其他算法的性能评价依据,与其他算法结果相比较。

 

先进先出算法(FIFO)

选择在内存中驻留时间最长的页面换出,使用链表,链首驻留时间最长,发生缺页中断优先换出链首

缺点:性能较差,换出的页面可能是经常访问的页面,并有Belady现象,该算法很少单独使用

 

最近最久未使用算法(LRU)

将当前物理内存中最久未被使用的页面作为被置换页面。

原理是程序局部性原理。

实现方式两种:链表维护或者栈(下面讲解以链表为例)。

链表:首先将当前内存中的所有页面都用链表维护起来。如果当前需要的页面在链表中存在,那么将其移到链表的头部,如果没有在链表中,那么将链表结尾的页面作为被置换页面,然后移到头部。

栈:访问页时先将页号压入栈顶,然后查找是否有相同的,有则抽出,没有就淘汰栈底的页。

该算法效率高于FIFO但是算法的实现成比较高,比如说要维护链表。
 

时钟页面置换算法

以类似循环队列的方式组织当前内存所有页面,设置一个指针在这个循环中流动,寻找将要被替换的页面。

以页表的一个表示页表属性状态的比特位为标准决定是否被替换,即used bit,该位表示页面是否被访问。算法执行逻辑是这样的:每当有页放入内存,将其used bit初始化为0,当其被访问时将其置1(一般放入时即访问时,所以放入时即为1)。指针指向最老被放入内存的页,如果used bit位为1,则表示该页面最近被使用过,那么将该页的used bit位置为0,然后指针继续向下寻找,直到找到used bit位为0的页,将其作为被置换页。

该算法不再精确的记录和维护所有页面的最近被使用时间,节省了维护链表的开销,极大的提高了效率。该算法达不到LRU效率,但是接近LRU效果。

 

二次机会法

是时钟页面置换算法的兄弟版,唯一的不同点就在于页面是否被置换的判别标准上,clock仅仅使用used一个位作为判别标准,但是该算法使用used和dirty这两个位作为判别标准(dirty位表示着该页面是否被修改过),只有当这两个位都为0时,才会置换当前页。

used位,dirty位迭代

(0,0)>换出

(1,0)>(0,0)

(0,1)>(0,0)

(1,1)>(0,1)

比clock方法更好


最不常用法(LFU)

顾名思义,就是将当前内存中被使用次数最少的页面置换出去。算法实现成本比较低,只需要额外维护一个计数器,页面每次被访问就使计数器加一即可。但是设计的逻辑不能完全符合进程访问内存的特性的,导致算法本身的执行效率不是很高。比如说,在某一段时间内,某一页面被频繁访问,导致其计数器次数很高,即使后面很长一段时间不被使用,也不会被作为置换页被替换掉。

解决方法:可以通过定期减少所有计数器计数次数的方式来淘汰老页面。

LFU与LRU区别

LRU注重的是上一次被访问的时间,LFU注重的是被访问的次数或频率



Belady现象

在FIFO算法中,有时分配的物理页面增加时,缺页率反而提高

产生原因:FIFO置换特征与进程访问内存动态特征矛盾,换出的页面不一定是不会访问的页面

 

LRU、FIFO、Clock的比较

LRU和FIFO本质上都是先进先出,LRU针对的是页面最近访问时间排序,每次访问动态调整;FIFO针对页面存入时间排序,排序固定不变。如果内存中所有页面都未访问,则LRU算法退化为FIFO算法

Clock算法实际上是对LRU算法的近似,因为Clock算法只用了1-2个bit表示访问时间,本质上其实也是类似FIFO的算法,只不过用了bit模拟访问时间先后顺序,所以可以有效模拟逼近LRU算法,开销也不大。

只有程序有局部性的特点,LRU及Clock算法才能发挥效果,否则会退化为FIFO算法

LRU算法性能较好,但开销大;FIFO算法开销小,但会发生Belady现象。这种算法为Clock算法,Clock算法本质上也是FIFO算法,根据bit位状况记录访问变化,所以开销小,性能接近LRU算法。

 

全局页面置换算法

局部页面置换算法问题

程序对物理页帧的需求是动态变化的。

 

工作集页置换算法

工作集:一个进程当前正在使用的逻辑页面集合W(t,Δ)

t为当前时刻,Δ为工作集窗口(一个定长的页面访问的时间窗口),W表示当前时刻(t-Δ,t)时间窗口当中所有页面组成的集合

|W(t,A)|为工作集大小,即页面数目

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

 

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

如果进程的所有工作集都在常驻集(内存)中,那么将不会造成太多缺页中断。

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

 

工作集页面替换算法:设置窗口长度Δ,随着时间移动,将不在工作集中的页面移出内存。

缺页率页面置换算法(PFF):常驻集大小可变,在进程运行过程中,动态调整常驻集大小(根据缺页率大小变化)多个进程同时运行时,可以置换其他进程中的页,竞争使用物理页面。

优点:性能好。     缺点:增加了系统开销

缺页率:缺页次数/内存访问次数或缺页的平均时间间隔的倒数。

影响缺页率因素:页面置换算法,分配给进程的物理页面数目,页面大小,程序编写方法

调整常驻集大小方法:计算两次缺页中断的时间差,如果时间差大则减小常驻集(移出没有在两次缺页中断中访问的页面,加入本次缺失的页),如果时间差小则增大常驻集(加入该次缺失的页)。

 

抖动问题

如果常驻集属于工作集,则会有很多内存中断,需要频繁替换页面,这种状态称为“抖动”。

随着内存进程增加,分配给每个物理页面数不断减小,缺页率不断上升。所以OS要选择适当的进程数目和进程帧数,以便在并发水平和缺页率间达到平衡。

MTBF是指平均页缺失时间,表示内存中发生页缺失的平均时间长度,PFST是指页缺失时操作系统服务的时间(进行页面置换等一系列操作的时间)。当缺页平均时间=缺页服务时间时,效果最优

 

7.进程

1.进程描述

什么是进程?

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

进程包括:

程序代码,程序处理的数据,程序计数器中的值,指令下一条将运行的指令,一组通用的寄存器的当前值,堆,栈,一组系统资源。总之进程包含了正在运行的程序的所有状态信息

进程与程序的联系(多对多映射):

程序是产生进程的基础,程序的每次运行构成不同进程,进程是程序功能的体现,通过多次执行,一个程序可以对应多个进程;通过调用关系,一个进程可包括多个程序。

进程与程序区别:

进程是动态的,程序是静态的;程序是有序代码的集合,进程是程序的执行;进程是暂时的,程序是永久的;进程是一个状态变化的过程,程序可以长久保存;进程包括程序、数据和进程控制块(进程状态信息),进程有核心态/用户态。

进程特点:

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

并发性:进程可以被独立调度并占用CPU运行(并发是指一段时间内多进程在执行,并行是指在一个时刻有多个进程执行)(一个CPU只能执行一个进程,只能做到并发;有多个CPU时才能做到并行)

独立性:不同进程的工作不互相影响(CPU交替执行多个进程,进程结果不受影响)(页表保证进程独立性,给不同进程分配不同页表,不会访问到其他进程地址)

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

 

进程控制结构

程序=算法+数据结构

描述进程的数据结构:进程控制块(PCB),操作系统为每个进程提供了一个PCB,用来保存与该进程的基本情况以及运行变化的过程,PCB是进程存在的唯一标志。

进程控制块:操作系统管理控制进程运行所用的信息集合。

创建进程时生成一个PCB,终止进程时回收PCB,通过对PCB组织管理来实现进程的组织管理。

PCB含有三大类信息:

1.进程标识信息:进程标识,进程产生者标识,用户标识。

2.处理机状态信息保存区(保存数据,状态,执行到什么位置):保存进程的现场信息(用户可见寄存器,用户程序可使用的数据,地址等寄存器)(状态控制寄存器,如程序计数器(PC),程序状态字(PSW))(栈指针,过程调用/系统调用/中断处理和返回时需要用到)

3.进程控制信息:

                        调度和状态信息,用于操作系统调度进程并占用CPU使用。

                        进程间通信信息,为支持进程间的与通信相关的各种标识,信号,信件等,这些信息

                                                     存在接收方的进程控制块中。

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

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

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

                                                     的PCB。

 

PCB组织方式

链表:同一状态的进程的PCB构成一个链表,多个状态对应多个不同链表(就绪链表,阻塞链表)

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

 

2.进程状态

1.进程生命周期管理

进程创建→进程运行→进程等待→进程唤醒→进程结束

进程创建:系统初始化时,用户请求创建一个新进程,正在运行的进程执行了创造进程的系统调用

进程运行:CPU只能执行就绪进程,运行时选择一个进程运行

进程等待(阻塞):1.请求并等待系统服务,无法马上完成;2.启动某种操作(等待其他进程),无法马上完成;3.需要的数据没有到达。进程只能自己阻塞自己,因为只有进程自身知道何时需要等待某种事件的发生。

进程唤醒:1.被阻塞进程需要的资源可被满足;2.被阻塞进程等待的事件到达;3.将该进程的PCB插入到就绪队列。进程只能被别的进程或操作系统唤醒。

进程结束:1.正常退出(自愿);2.错误退出(自愿);3.致命错误(强制性的);4.被其他进程所杀(强制性的)

 

2.进程状态变化模型

进程的三个基本状态:运行状态(running)、就绪状态(ready)、等待状态(阻塞状态blocked)

其他基本状态:创建状态(New)进程正在被创建、结束状态(Exit)进程正在从系统中消失

 

3.进程挂起模型

进程挂起:进程没有占用内存空间,进程映像在磁盘上。

阻塞挂起:进程在外存并等待某事件出现。

就绪挂起:进程在外存,但只要进入内存即可运行。

阻塞到阻塞挂起:没有进程就绪或就绪进程要求更多内存资源时发生转换

就绪到就绪挂起:有高优先级阻塞(系统认为很快就绪)进程和低优先级就绪进程时,系统会挂起低优先级就绪进程

运行到就绪挂起:对抢先式分时系统,有高优先级阻塞挂起进程因事件出现而进入就绪挂起时,系统可能把运行进程转到就绪挂起

阻塞挂起到就绪挂起:有阻塞挂起进程因事件出现,系统将阻塞挂起进程转化为就绪挂起进程

就绪挂起到就绪:没有就绪进程或挂起就绪进程优先级高于就绪进程

阻塞挂起到阻塞:当一个进程释放足够内存时,把高优先级阻塞挂起进程转换为阻塞进程

 

状态队列

由操作系统维护一组队列,用来表示系统所有进程当前状态,不同状态分别用不同对列表式,每个进程PCB根据它的状态加入到相应的队列中,当一个进程的状态发生变化时,该进程PCB从一个状态队列中脱离出来,加入另一个队列

 

3.线程

为什么用线程

进程之间如何通信,共享数据?维护进城开销较大。

需要提出一种新的实体:实体之间可以并发执行,实体之间共享相同的地址空间

 

什么是线程

线程是进程中的一条执行流程

进程是从资源整合角度考虑:将资源平台(环境),地址空间,打开的文件整合在一起

线程是从运行的角度考虑:代码在平台上的执行流程

线程=进程-共享资源

管理线程靠和PCB类似的TCB

优点:一个进程中可以有多个线程,各个线程直接可以并发执行,各个线程之间可以共享地址空间和文件资源

缺点:一个线程崩溃会导致其所属的进程的所有线程崩溃(安全性)

 

进程与线程比较

进程是资源分配单位,线程是CPU调度单位

进程拥有一个完整资源平台,线程只独享必不可少的资源,如寄存器和栈。

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

线程能减少并发执行的时间和空间开销(创建时间短(需要创建资源管理资源),终止时间短(需要释放资源管理资源),同一进程内线程切换时间短(地址空间相同,无需切换页表),由于共享内存资源,可以直接进行不通过内核的通信)

 

线程的实现

用户线程(操作系统看不到,由应用态的应用程序库管理):在用户空间实现。

优点:可用于不支持线程技术的多进程操作系统;TCB由线程库函数维护;无需用户态,核心态之间切换,速度快;允许每个进程有自定义线程调度算法

缺点:一个线程阻塞则整个进程等待;当一个线程开始运行后,除非主动交出CPU使用权,否则进程中所有其他线程都无法运行;因为是按时间片分给进程,当线程多了每个线程得到的时间就少,执行慢

 

内核线程(操作系统管理):在内核中实现

特点:靠内核管理TCB list,系统开销大;一个线程发起系统调用阻塞,不会影响其他线程;时间片分给线程,每个线程获得时间长

 

轻量级进程:在内核中实现,支持用户线程

用户线程与内核线程对应关系可以试多对一,一对多,多对多

用户线程实现:内核支持的用户线程,一个进程可以有多个轻量化进程,每个量级进程有一个单独的内核线程支持

 

4.进程控制

Windows进程创建API:CreateProcess(filename)

Unix进程创建系统调用:fork/exec

fork():

把一个进程复制成两个进程

fork()复制父进程的所有变量和内存,复制父进程的所有CPU寄存器

子进程的fork()返回0,父进程的fork()返回子进程标识符

fork()返回值可方便后续使用,子进程可使用getpid获取PID

多次fork()会导致1变2,2变4

fork()执行开销:对子进程分配内存,复制父进程内存和CPU寄存器到子进程内,开销大

 

exec():

用新程序重写当前进程,PID不改变

exec()调用允许一个进程加载一个不同的程序并且在main开始执行,它允许进程指定参数数量和字符串参数数组,调用成功则stack栈与heap堆会被重写

 

大多数情况下会在fork()后调用exec(),在fork()中复制内存是没有用的,子进程将可能关闭打开的文件和连接

vfork创建进程时不再创建同样的内存映像,有时被称为轻量级fork,子进程应该几乎立即调用exec(),现在使用copy on write(COW)技术,根据是否需要写操作决定是否复制。

 

wait():

系统调用被父进程用来等待子进程的结束

子进程退出后,需要操作系统返回给父进程信号,父进程帮助子进程完成内存资源回收

wait()使父进程休眠等待子进程结束,当子进程调用exit()时,解锁父进程,通过exit()返回值作为wait()调用结果,连同子进程的PID一起,如果没有子进程存活则wait()立刻返回;如果父进程僵尸等待,wait()立即返回其中一个值(并且解除僵尸状态)

僵尸状态:子进程exit()后,内核资源未被回收时为僵尸状态

 

exit():

将程序结果作为一个参数;关闭所有打开的文件与连接;释放内存;释放大部分支持进程的操作系统结构,检查父进程是否存活,如存活则保留结果直至父进程需要,此时为僵尸态,否则释放所有数据结构,进程死亡;清理所有僵尸进程

 

8.调度

背景

上下文切换

停止当前进程/线程,运行其他进程/线程

需要在切换前存储许多部分的进程/线程在PCB/TCB中上下文,必须能在之后恢复,进程不能显示曾被暂停过,必须快速;需要存储寄存器,CPU状态,有时会很费事

操作系统为活跃进程/线程准备了进程控制块(PCB/TCB)

操作系统将进程/线程控制块(PCB/TCB)放在一个队列里(就绪队列,等待IO队列,僵尸队列)

 

CPU调度

从就绪队列中选择一个进程/线程让CPU下一个运行,需要设计调度程序,考虑调度时机

内核运行调度程序条件:进程从运行状态切换到等待状态,或进程终结

非抢占式调度策略:调度程序必须等待时事件结束(进程等待时间长)

抢占式调度策略:调度程序在中断被响应后执行;当前进程从运行切换到就绪,或一个进程从等待切换到就绪;当前运行的进程可以被换出

抢占与非抢占分为用户级和内核级

 

调度准则

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

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

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

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

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

 

人们通常需要更快的服务:传输文件时的高带宽,玩游戏时的低延迟,这两个因素是独立的

希望响应时间短,及时处理用户输出并将输出提供给用户

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

希望增加吞吐量,减少开销,高效利用系统资源

希望减少等待时间,减少进程等待时间

 

低延迟调度增加了交互式表现,但是操作系统需要保证吞吐量不受影响;响应时间是操作系统计算延迟,吞吐量是操作系统的计算带宽

公平:保证进程之间公平,通常会增加平均响应时间

 

调度算法

FCFS先来先服务:

如果进程在执行中阻塞,队列中的下一个会得到CPU

优点:简单

缺点:当先执行的进程时间较长,会导致后续进程周转时间都会因等待长进程执行完毕而增加;

           CPU密集型进程可能导致IO设备闲置时,IO密集型进程也在等待

 

SPN(SJF) SRT短进程(短作业)短剩余时间优先:

按照预测的完成时间来将任务入队(可以是可抢占的或不可抢占的)

如果是可抢占的,则为最短剩余时间优先SRT

优点:最优平均等待时间

缺点:连续短任务会导致长任务饥饿;短任务可用时的任何长任务的CPU时间都会增加平均等待时间

        需要预先知道程序运行时间,怎么预估下一个CPU突发的持续时间,简单的解决办法:询问用户,如果用户欺骗就杀死进程,如果用户不知道怎么办?可以根据进程以往运行时间估测

 

HRRN最高响应比优先:

在SPR调度基础上增加了等待时间的考虑,不可抢占,防止无限期推迟

R = (w + s)/s,等待时间w,处理时间s,选择R值最高的进程

缺点:目前不可抢占,执行时间和等待时间无法精确确定

 

Round Robin轮循:

在叫做量子(时间切片)的离散单元中分配处理器,时间片结束时切换到下一个就绪的进程

开销主要花在上下文切换,时间量子过大则等待时间过长,极限情况退化为FCFS;时间量子过小则吞吐量会受上下文切换影响。

一般维持上下文切换开销在1%以内

 

Multilevel Feedback Queues多级反馈队列:

就绪队列被划分为独立的队列,每个队列有自己的调度策略,调度必须在队列间进行,一个进程可以在不同队列中移动

按照队列优先级顺序,依次执行每个队列第一个进程,按照时间量子来规划每个优先级使用的时间,时间量子随优先级增加而增加,如果任务在当前时间量子中未完成,则降级到下一个优先级

优点:CPU密集型任务优先级下降很快,IO密集型任务停留在高优先级

 

Fair Share Scheduling公平共享调度:

一些用户组比其他用户组更重要,保证不重要的组无法垄断资源,未使用的资源按照每个组所分配的资源的比例来分配,没有达到资源使用率目标的组获得更高的优先级

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

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

实现/模拟:建立一个允许算法运行实际数据的系统,最灵活/具有一般性

 

实时调度

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

性能指标:时间约束的及时性,速度和平均性能相对不重要

主要特性:时间约束的可预测性

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

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

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

属性:取得进展所需要的资源,定时参数

released time:一个进程处于就绪态时间

relative deadline:相对deadline,每个时间段完成特定任务

absolute deadline:绝对deadline,最终结束时间

execution time:执行时间

硬时限:必须在该时限内完成,保证确定性

软时限:理想情况下,时限应被最大满足,如果未被满足则降低要求,尽最大努力保证

 

静态优先级调度:在任务执行前确定优先级

动态优先级调度:任务优先级随着执行过程动态变化

 

速率单调调度算法(RM):

最佳静态优先级调度,通过周期安排优先级,周期越短优先级越高,执行周期最短的任务

最早期限调度算法(EDF):

最佳的动态优先级调度,deadline越早优先级越高,执行deadline最早的任务

 

多处理器调度

多处理器CPU调度更复杂,多个相同的单处理器组成一个多处理器,优点是能负载共享

对称多处理器:每个处理器运行自己的调度程序,需要在调度程序中同步

 

优先级反转

可以发生在任何基于优先级的可抢占的调度机制中,当系统中的环境强制高优先级任务等待低优先级任务时发生

由于高优先级任务与低优先级任务共享内存资源,而低优先级执行又被其他高优先级打断,导致无法释放资源

解决方法:根据内存共享依赖,由低优先级继承共享内存的高优先级进程的优先级

或者使用优先级天花板法:根据使用内存的最高优先级进程,给内存分出优先级。

 

9.同步互斥

背景

独立线程:不与其他线程共享资源或状态,具有确定性(输入状态决定结果),可重现(能够重现起始条件),调度顺序不重要

合作线程:在多个线程中共享状态,不确定,不可重现,意味着bug可能是间歇性发生的

合作好处:共享资源,加速(部分io操作和计算可重叠,通过多处理器将程序分成多个部分并行执行),模块化(将大程序分解成小程序,使系统易于扩展)

无论线程指令序列如何交替进行,程序都必须正常工作(不经过专门设计,调试难度很高)

不确定性要求并行程序的正确性

概念

竞态条件(Race Condition)

结果依赖于并发执行或者事件的顺序/时间(不确定性,不可重现性)

原子操作(Atom Operation)

一次不存在任何中断或失败的执行(该执行成功结束或者未被执行,不应该发现任何部分执行的状态)

实际上操作往往不是原子的

临界区(Critical section)

代码访问的资源是共享资源并且另一个进程处于相应代码区域时便不会被执行的代码区域

互斥(Mutual exclusion)

临界区的进程只能有一个,一个进程访问时不允许其他进程访问临界区内代码

死锁(Dead lock)

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

饥饿(Starvation)

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

前进原则(Progress)

如果一个线程想要进入临界区,最终会成功

有限等待

一个线程处于入口区,那么在i的请求被接受前,其他线程进入临界区的时间是有限制的

无忙等待(可选)

如果进程在等待进入临界区,那么在进入之前将被挂起

 

禁用硬件中断

没有中断则没有上下文切换,没有并发

问题:一旦中断被禁用,线程则无法停止,整个系统都会停下,可能导致其他线程处于饥饿状态;多CPU情况下只能屏蔽自己中断,其他CPU仍可能发生中断;临界区过长会无法响应中断所需的时间

 

基于软件的解决方法

算法:do{

flag[i] = TRUE;//i想进入临界区

turn = j;//轮到j进入临界区

while(flag[j] && turn = j);

CRITIACL SECRION

flag[i] = FALSE;

REMAINER SECTION

}while(true)

满足互斥,前进,有限等待的性质

 

Bakery算法

N个进程的临界区

进入临界区之前,进程接收一个数字,得到的数字最小的进入临界区,如果Pi,Pj收到相同的数字,如果i<j,则Pi进入临界区,否则相反,编号方案总是按照枚举方式增加顺序生成数字

 

复杂,需要两个进程间共享数据项;需要忙等待,浪费CPU时间;没有硬件保证的情况下无真正的软件解决方案

 

更高级的抽象

硬件提供了一些原语,像中断禁用,原子操作指令等,大多数现代体系结构都这样

操作系统提供更高级的编程抽象来简化并行编程,例如锁,信号量,从硬件原语中构建

锁是一个抽象的数据结构,Lock::Acquire()锁被释放前一直等待,然后得到锁

Lock::Release()释放锁,唤醒任何等待的进程

大多数现代的体系结构都提供特殊的原子操作指令,通过特殊的内存访问电路,针对但处理器和多处理器

 

Test-and-Set测试和置位:首先从内存中读取值;测试该值是否为1(然后返回真或假),内存值设置为1

Exchange交换:交换内存中两个值

两个方法提供一个即可完成临界区进入退出

 

while(Test-and-Set(value)){程序体},如果有锁,则一直循环,如果没有锁,就上锁并等待执行完成,完成后value为0跳出循环

为了不忙等,可以进入睡眠,前提是临界区较大,否则忙等效率更高。

 

初始lock=0;循环开始时key=1;当k=1时,互换lock与key的值,此时为1,0,上锁状态进入临界区,完成后lock置0,其他进程来时key与lock都为1,无论怎么交换都无法进入临界区。

 

优点:适用于单处理器或共享主存的多处理器中任意数量的进程;简单且容易证明;可以用于支持多临界区

缺点:忙等消耗CPU资源;当进程离开临界区并且多个进程在等待的时候可能导致饥饿;死锁(高优先级忙等导致低优先级无法释放内存)

 

10.信号量

信号量

抽象数据类型sem,两个原子操作

P():sem-1;如果sem<0,等待,否则继续

V():sem+1;如果sem≤0,唤醒一个等待的P

 

信号量使用

信号量是整数,且是被保护的变量(初始化完成后,唯一改变一个信号量的值的方法是通过P()和V(),操作必须是原子)

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

假设信号量是公平的(通过FIFO管理阻塞进程)

 

二进制信号量:0或1;一般/计数信号量:任何非负值

通过sem的值确定操作能被执行的次数,通过P()进行次数减一,V()进行次数加一,为零则等待。

 

信号量实现

使用硬件原语(禁用中断,原子指令)

信号量双用途:1.互斥和条件同步;2.等待条件是独立的互斥

读/开发代码比较困难:程序员必须非常精通信号量

容易出错:使用的信号量已经被另一个线程占用或忘记释放信号量

不能处理死锁问题

 

管程

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

什么是管程:一个锁(临界区);0或者多个条件变量(等待/通知信号量用于管理并发访问共享数据)组合以上二者的模块

一般方法:收集在对象/模块中的相关共享数据,定义方法来访问共享数据

Lock操作与Condition Variable条件变量:

当条件得不到满足使用Wait操作使得自己睡眠,释放锁

当条件得到满足使用Signal操作唤醒等待者继续执行

 

实现时需要维持每个条件队列,首先等待数为0,创建等待队列。

Wait方法首先增加等待计数,加入等待队列,释放锁,选择下一个线程执行,然后获得锁

Signal方法首先判断等待计数是否大于0,大于0则移出一个等待队列中线程,唤醒,等待计数减一

 

Hansen方法:唤醒进程后,被唤醒的进程立刻执行,发出唤醒信号的进程暂停执行

Hoare方法:唤醒进程后,发出唤醒信号的进程继续执行直到Release

 

经典同步问题

一、读者写者问题

读者不修改数据。写者修改和读取数据。

允许同时有多个读者,但只能有一个写者。

当没有写者时才能访问数据。

当没有读者和写者时写者才能访问数据

在任何时候只能有一个线程可以操作共享变量

 

实现核心:在状态转换时进行保护,从读写状态切换,以及读者有无切换。

 

二、哲学家就餐问题

通过对思考,饥饿,进食三个状态,来进行互斥保护,通过左右邻居以及自己的进食状态来表示叉子使用状态。

 

11.死锁

死锁问题

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

 

系统模型

角色分为两类,需求方和需求的资源。需求方一般是进程。资源可以是看得见摸得着的,也可以是内存或者I/O,甚至共享变量等。

可重复使用资源特征:一个时间只能一个进程使用且不能被删除,资源被进程使用完后需要释放给其他进程使用,CPU、I/O通道、主副存储器、设备和数据结构、如文件,数据库和信号量都属于可重复使用资源,进程拥有资源同时请求其他资源则可能发生死锁

 

资源分配图

一组顶点V(P={P1,P2...}集合包括系统中所有进程)(R={R1,R2,...}集合包括系统中所有资源类型)和边E(资源R分配给进程P则由R指向P,进程P请求资源R则由P指向R)的集合

圆表示进程,方内小框表示不同实例资源类型

如果图中无环则无死锁,如果有环但每个资源类有多个实例则可能死锁,如果只有一个实例则一定死锁。

 

死锁特征

四个条件同时成立则可能发生死锁

互斥:一个时间只能有一个进程使用资源

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

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

循环等待:存在等待进程集合,持有资源的进程互相等待其他进程释放资源,形成了环

 

死锁处理方法

死锁控制强度:操作系统永远不会进入死锁状态→运行系统进入死锁状态,然后恢复→忽略死锁问题,假装未发生死锁(大多数操作系统,如UNIX)

死锁预防

限制申请方式

互斥:如果不互斥会导致线程执行的不确定性

持有并等待:全部资源获取或不获取任何资源。进程执行不同时期需要资源不同,系统利用率低,会产生饥饿现象。

无抢占:需要抢占需要杀死享有互斥资源的进程

循环等待:要求进程按照资源请求顺序对资源进行排序,也会出现资源利用不够的问题

 

死锁避免

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

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

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

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

 

当一个进程请求可用资源,系统必须判断立即分配是否能使系统处于安全状态。安全状态指对于所有进程,存在安全序列。

安全序列:<P1,P2,...,PN>按照该序列顺序执行,则所有进程都能正常结束。

Pi执行时,可以使用P1到Pi-1的所有资源

不安全状态可能发生死锁

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

Claim edge :

  • 进程需要请求资源用虚线表示【Claim edgePi -> Rj】
  • 进程请求资源时,虚线转为实线 【Claim edge >> request edge】
  • 当进程被资源释放,实线转为虚线 【assignment edge >> claim edge】

 

银行家算法(Banker’s Algorithm)

多个实例,每个进程必须最大限度利用资源,当进程请求资源时必须等待,当进程获得所有资源后需要在一段有限时间内释放资源

通过以上条件,银行家算法通过尝试寻找每个进程获得的最大资源并结束进程请求的一个理想执行时序,来决定一个状态是否安全。不满足该需求的执行时序的状态都不安全。

n为进程数量;m为资源类型数量

Max(总需求量):nxm矩阵。如果Max[i,j]=k,表示进程Pi最多请求Rj的k个实例

Available(剩余空闲量):m维向量。Available[j]=k,表示Rj还剩k个实例可用

Allocation(已分配量):nxm矩阵。如果Allocation[i,j]=k,表示进程Pi已经分配了Rj的k个实例

Need(未来需要量):nxm矩阵。如果Need[i,j]=k,表示进程Pi还需要Rj的k个实例

Work和Finish分别是长度为m和n的向量。

 

算法过程:

1.初始化Work=Available资源空闲量;Finish[i]=false,线程未结束,为true则结束(申请到所有所需资源)。

2.寻找i符合要求:1.Finish[i]=false,Need[i]<=Work,没找到则进入4步

3.Work =Work+ Allocation[i];Finish[i]=true;转到2

4.if Finish全为true则处于安全状态,否则不安全

 

每次有新的request,更新Available,Allocation,Need至假设将资源分配给进程,看分配后是否安全,发出请求的进程需要等待评估。

如果Request < Need,转到步骤2。否则,提出错误条件,因为进程已经超过了其最大要求。

如果 Request < Available,转到步骤3。否则,Pi必须等待,因为没有那么多可用的资源

 

死锁检测

将图中资源节点去掉,只留下进程节点,若A进程请求B进程拥有的资源则在A连线至B,然后判断是否有环。

 

Available(剩余空闲量):m维向量。Available[j]=k,表示Rj还剩k个实例可用

Allocation(已分配量):nxm矩阵。如果Allocation[i,j]=k,表示进程Pi已经分配了Rj的k个实例

Request(请求量):nxm矩阵。如果Request[i,j]=k,表示进程Pi请求Rj的k个实例

 

算法过程:

1.初始化Work=Available资源空闲量;Allocation>0则Finish[i]=false,线程未结束,否则为true则结束(申请到所有所需资源)

2.寻找Finish[i]=false,Request[i]<=Work,未找到则转到4

3.Work =Work+ Allocation[i];Finish[i]=true;转到2

4.如果Finish全为true则无死锁,否则处于死锁状态

定期执行算法查找是否有环,算法时间复杂度为O(mn^2)

需要提前知道每个进程需要的最大资源个数(困难)

一般用于开发阶段,如果资源图有多个循环,无法分辨哪些进程造成死锁

 

死锁恢复

杀死所有死锁进程(过于严格),在一个时间内终止一个进程直到死锁消除

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

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

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

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

 

进程间通信IPC

进程执行过程中需要在进程间创建通信链路

直接通信

进程必须正确命名对方(send(P.message),receive(Q.message))

自动建立链路,一条链路对应一对通信进程,每对进程之间只有一个链接存在,链接可以是单向的,通常是双向的

间接通信

定向从消息队列接收消息,每个消息队列都有一个唯一的ID,只有它们共享了一个消息队列,进程之间才能通信。

链路可以与许多进程关联,每对进程可以共享多个通信链路,连接可以是单向或双向的。

send与receive发送至特定存储空间。

阻塞与非阻塞

阻塞是同步的,非阻塞是异步的

通信链路缓冲

将临时无法处理的数据存储起来提高效率

0容量:发送方必须等待接收方

有限容量:如果队列满则发送方必须等待

无限容量:发送方无需等待

 

信号Signal

发出通知信息,软件中断通知事件处理,处理完后回到打断的程序继续执行

接收到信号时可能:

Catch:指定信号处理函数被调用

Ignore:依靠操作系统的默认操作

Mask:闭塞信号因此不会发送

优点:快

缺点:不能传输要交换的任何数据

实现过程:首先应用程序针对单独信号处理,需要在程序执行开始时注册针对每个信号的handle,发给操作系统;当产生某个针对该进程的信号时,操作系统调用应用程序的信号处理函数执行。

操作系统收到信号时,运行在内核态,需要返回至用户态,将返回点改为调用信号处理函数入口。需要修改返回用户空间的堆栈,使本来需要返回到系统调用的后一条语句执行变为信号处理函数入口,同时将信号处理函数后需要执行的地址作为后一个栈指针的地址

 

管道

将多个进程首尾相连,一个进程的输出直接输入另一个进程(前提共有一个父进程)

管道Buffer有限,满了就会阻塞

通过符号|,A|B则A结果输入B,进程不关心从哪里输入数据。

 

消息队列

管道通过父进程帮助子进程建立通道,管道中数据为字节流,没有结构化表示形式

而消息队列可以使不相干进程通过消息队列传递数据,可以是结构化数据

消息队列也有Buffersize限制

 

共享内存

是一种直接通信方式

每个进程都有私有地址空间,在每个地址空间内明确设置了共享内存段

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

缺点:必须同步数据访问

一个进程写入另一个进程立即可见,没有系统调度干预,没有数据复制,不由操作系统提供同步(程序员提供)

 

12.文件系统

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

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

 

文件系统需要分配文件磁盘空间(管理文件块,管理空闲空间,分配算法),管理文件集合(定位文件及其内容,命名文件通过名字找到文件接口,最常见分层文件系统,文件系统类型),给文件提供保护(分层保护数据安全),可靠性和持久性(保持文件持久,即使发生崩溃,媒体错误攻击也不会导致文件被破坏)

 

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

需要元数据数据来管理打开文件:文件指针(指向最近一次读写位置),文件打开计数(记录文件打开次数),文件磁盘位置(缓存数据访问信息),访问权限(每个程序访问模式信息)

在用户看来文件是一个持久的数据结构,在系统看来文件是字节的集合,在操作系统看来是一块读写的buffer

用户访问文件方法:顺序访问(从前到后按照字节顺序依次读取)、随机访问(从中间读写)、基于内容访问(通过特征)

 

文件以目录形式保存,目录是一类特殊的文件,每个目录包含一张表,目录与文件是树形结构

目录操作:搜索文件,创建文件,删除文件,枚举目录,重命名文件,在文件系统中遍历一个路径

操作系统只允许内核模式修改目录

文件系统需要先挂载才能被访问,未挂载文件可以挂载在不同点上

 

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

软连接:以快捷方式指向其他文件

通过存储真实文件的逻辑名称来实现

删除有别名的文件时,别名将成为悬空指针

使用循环检测算法防止文件链接出现死循环

 

文件系统种类:磁盘文件系统,数据库文件系统,日志文件系统, 网络/分布式文件系统,特殊/虚拟文件系统

 

虚拟文件系统

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

功能:提供相同的文件和文件系统接口,管理所有文件和文件系统关联的数据结构,高效查询例程并遍历文件系统,与特定文件系统模块交互

卷控制块superblock:包含了操作系统中块的信息(每个文件系统一个)

文件控制块iNode:存储了单个文件的具体属性和数据(每个文件一个)

目录结点dentry:与其他文件有不同的处理(每个目录页一个)

 

数据块缓存

数据块按需读入内存,使用后被缓存

类似页面交换算法,尽量减少对硬盘的读写次数

 

文件分配

连续分配:文件头指出文件的起始位置和块长度

优势:读取方便,适合高效的顺序和随机访问

缺点:文件扩展时需要把后续文件移位或者将自身移位,会产生空间碎片

适合只读操作的文件

 

链式分配:文件以链表方式存储,文件头包含了第一块和最后一块的指针

优点:方便创建和改写,无碎片

缺点:无法随机访问,可靠性低(一旦丢失一个则后续都丢失)

 

索引分配:为每个文件创建一个名为索引数据快的非数据数据块,文件头包含了索引数据块

优点:方便创建和改写,无碎片,支持直接访问

缺点:当文件小时索引开销大,文件大时索引块内存不够用(链式索引块,多级索引块)

 

空闲空间列表

位图表示空闲数据块列表,11111000100101,0表示空闲

使用简单但是可能会是很大的向量,n为数据块总数,r为空闲块数目,需要扫描n/r次数去寻找0

需要保护:在硬盘上先置1,再分配,防止丢失数据。

 

多磁盘管理RAID

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

分区:磁盘的一种适合操作系统指定格式的划分

卷:一个拥有一个文件系统实例的可访问的存储空间

通过多个并行磁盘增加吞吐量和可靠性,可用性

RAID:冗余磁盘阵列

RAID0:通过多个硬盘并行存储,加快访问速度

RAID1:向两个硬盘写入同样的数据,防止单个硬盘损坏,增加可靠性,但是翻倍了写入时间

RAID4:使用多个磁盘并行存储,并增加Parity Disk,对错误码进行奇偶校验反推数据内容,但是每次读写需要修改Parity Disk,增大了开销

RAID5:将奇偶校验块分布在每个盘里,校验开销均匀且访问并行

RAID6:两个冗余块,可以允许两个磁盘错误

奇偶校验通过块进行

 

磁盘调度

机械操作读取速度慢。

读取或写入时需要先定位到期望的磁道,并从期望的扇区开始

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

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

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

Ta=Ts+1/2r+b/rN=Ts+Tr+Tt

Ta为访问时间,Ts为寻道时间,Tr为旋转延迟,T为传输时间,b为传输的比特数,N为磁盘比特数,r为磁盘转数(旋转一圈需要的时间)

寻道时间是性能区别的原因,对单个磁盘会有一个I/O请求数目,如果请求是随机的,那么会表现的很差。

 

按顺序处理请求,公平对待所有进程,当进程多时接近随机调度性能

最短服务优先,选择从磁臂当前位置需要移动最少的I/O请求,总是选择最短寻道时间(会导致饥饿)

磁臂在一个方向上移动,满足所有未完成的请求,直到磁臂到达该方向上最后的磁道后调换方向,有时称为elevator algorithm

磁臂仅在一个方向上扫描,当最后一个磁道扫描完成时,返回磁盘另一端再次扫描

C-Scan的改进版本,到达该方向最后一个请求处立即反转

N-Step Scan,将磁盘请求分成多个队列,每个队列执行SCAN方法

F Scan方法,仅分两个队列

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 39
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值