SQL嵌入式

操作系统知识点整理

2020-05-08  本文已影响0人  Hengtao24

操作系统基本概念

操作系统是计算机科学研究基石之一。

功能

内核特征

操作系统启动过程

BIOS:基本I/O处理系统

ROM是只读存储器,存在里面的东西不能删除和修改,只能读取、复制等;
闪存是在断电以后不丢失数据的存储器,不能以字节的方式擦除信息,只能以区域/块的方式擦除(如U盘)

CS为代码段寄存器,IP为指令指针寄存器,任意时刻,CPU将CS:IP指向的内容当作指令执行
在8086系统中,访问存储器的地址码由段地址(CS)和段内偏移地址(IP)两部分组成段寄存器用来存放各分段的逻辑基值。

BootLoader:加载OS

总结上述流程如下图所示:


操作系统启动示意图

中断、异常、系统调用

操作系统的交互

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

特点

相关术语

中断分类

  1. 外中断:来自CPU外部和内存外部的中断(如I/O中断和外部信号中断,其实就是我们一般上说的,狭义上的中断)
  2. 内中断:来自CPU内部和内存内部产生的中断,也称为陷入trap)或者异常,(比如常见的算术溢出,除数为0,地址非法,缺页等等)

所有的硬中断的优先级都高于软中断,各中断的优先级在系统设计初期给出,在运行过程中是不变的。

中断优先级

中断处理过程

  1. 将内部、外部事件设置中断标记
  2. 生成中断事件的ID
  1. 保存当前处理状态 [保护现场]
  2. 中断服务程序处理(中断向量表) [中断服务]
  3. 清除中断标记 [恢复现场]
  4. 恢复之前保存的状态 [中断返回]

中断向量与中断向量表

Linux 对256 个中断向量的分配如下:

  • 0~31 的向量对应于异常(不能被屏蔽)和非屏蔽中断
  • 32~47 的向量分配给屏蔽中断(即由I/O 设备引起的中断)
  • 48~255 的向量用来标识软中断Linux 只用了其中的一个(即128 或0x80向量)用来实现系统调用。当用户态下的进程执行一条int 0x80汇编指令时,CPU 就切换到内核态,并开始执行 system_call() 内核函数。

参考中断向量表 中断描述符

异常:非法指令或其他坏的处理状态(如内存出错)

特点

异常分类

异常处理过程

  1. 杀死异常程序
  2. 重新执行异常指令

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

特点

系统调用处理过程

  1. Win32 API
  2. POSIX API(通用可移植系统调用接口,UNIX Linux)
  3. Java API(非系统调用接口

内核态:CPU可以访问内存的所有数据,包括外围设备,例如硬盘、网卡,CPU也可以将自己从一个程序切换到另一个程序。
用户态只能受限的访问内存,且不允许访问外围设备,占用CPU的能力被剥夺,CPU资源可以被其他程序获取。
参考内核态与用户态

  1. 函数调用在同一个堆栈内完成
  2. 系统调用存在用户态栈和内核态栈的切换
中断 异常 系统调用

跨越操作系统边界的开销

  1. 建立中断/异常/系统调用号与对应服务例程映射关系的初始化开销
  2. 建立内核堆栈
  3. 参数传递:把一些参数从用户空间传给内核,再进行验证参数(安全性):检查所有的参数是否合法有效
  4. 内核态映射到用户态的地址空间更新页面映射权限
  5. 内核态独立地址空间

内存管理

内存是一个计算机最重要的部分之一,程序只有被加载到内存中才可以运行,CPU所需要的指令和数据也都是来自于内存。

计算机体系结构/内存分层结构

计算机体系结构
  1. CPU寄存器
  2. cache(L1缓存 L2缓存...)
  3. 主存
  4. 硬盘(虚拟内存)

主存与硬盘之间存在交换/分页

内存金字塔

操作系统内存管理目标

内存抽象

没有内存抽象的年代

早期的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存,内存的管理也非常简单,除去操作系统所用的内存之外,其余全部给用户程序使用。

但是这种无内存抽象存在一些问题:

比如当执行如下指令时:mov reg1,1000

这条指令会将物理地址1000中的内容赋值给寄存器。这种内存操作方式使得操作系统中存在多进程变得完全不可能,必须执行完一条指令后才能接着执行下一条。如果是多进程的话,由于直接操作物理内存地址,当一个进程给内存地址1000赋值后,另一个进程也同样给内存地址赋值,那么第二个进程对内存的赋值会覆盖第一个进程所赋的值,这回造成两条进程同时崩溃。

因此无内存抽象存在以下问题:

  1. 用户程序可以访问任意内存,容易破坏操作系统,造成崩溃
  2. 同时运行多个程序特别困难

参考操作系统内存管理

内存抽象:地址空间

如何做到进程的地址受保护
操作系统对物理内存做了一层抽象,也就是地址空间(AddressSpace),一个进程的地址空间包含了该进程所有相关内存,如下图所示:

AddressSpace

从低地址到高地址分别内存区分别为:代码段,数据段(初始化),数据段(未初始化)(BSS),堆,栈,命令行参数和环境变量。(堆向高内存地址生长,栈向低内存地址生长

  • 代码段:存放全局常量(const)、字符串常量程序代码
  • 数据段(初始化):存放初始化的全局变量初始化的静态变量(全局的和局部的)
  • 数据段(未初始化)(BSS):存放未初始化的全局变量未初始化的静态变量(全局的和局部的)
  • 堆:存放动态分配的区域(malloc、new等)
  • 栈:存放局部变量(初始化以及未初始化的,但不包含静态变量)、局部常量(const)
  • 命令行参数和环境变量:存放命令行参数和环境变量

参考进程实体 内存划分

真实地址生成过程

地址空间分为以下两种:

从进程的角度看待内存其范围是0-X,但是其对应的真实物理内存地址应该是base——base+X数值base称为进程的基址,存放在基址寄存器中。每个内存地址送到内存之前,都会自动加上基址寄存器的内容。

CPU 上用来做内存地址转换的叫做内存管理单元(MMU),其完成逻辑地址到物理地址的映射

逻辑地址生成过程

逻辑地址生成的过程就是将一行行代码转变成内存中具体的逻辑地址,使得CPU运行程序时能"按图索骥"。
高级语言编写出来的程序逻辑地址生成一般步骤为:编译->汇编->链接->载入

  1. 编译:对源代码进行编译,成为汇编源代码,此时仍然用符号来指代函数
  2. 汇编:汇编成二进制代码用具体地址来代替符号了,但是有一些函数还没有找到(此函数在别的程序文件中)
  3. 链接加入函数库,找到库函数的地址,把多个小程序组装成一个可执行文件
  4. 载入程序加载时进行,视程序实际位置改变符号地址

链接分为两种方式:

  • 静态链接:在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(EXE文件)
  • 动态链接:在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝

延伸知识:静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,lib文件(静态链接的函数库)中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL文件(动态链接的函数库),该DLL不必被包含在最终的EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。

逻辑地址生成过程

地址安全检查

CPU 生成的地址通常称为逻辑地址,而内存单元看到的地址(即加载到内存地址寄存器的地址)通常称为物理地址。
参考逻辑地址空间和物理地址空间

总结一下,物理地址生成过程如下图所示:

物理地址生成过程

连续内存分配

连续分配是指为一个用户程序分配连续的内存空间。连续分配有单一连续存储管理分区式储管理两种方式。

单一连续内存分配

内存被分为两个区域:系统区和用户区。应用程序装入到用户区,可使用用户区全部空间。其特点是,最简单适用于单用户、单任务的操作系统

分区式内存分配

内存碎片

固定分区

动态分区

  1. 没有内部碎片
  2. 进程大小无限制
  3. 多程序的程度是动态的,可以同时将更多的进程加载到内存中
  1. 存在外部碎片
  2. 复杂的内存分配,动态分区中,分配和释放非常复杂,分配和取消分配的操作非常频繁,因为每次分配给新进程时分区大小都会发生变化,操作系统必须跟踪所有的分区
  1. 首次适配找到第一个可用空闲块(空闲块空间大于应用程序所需大小)[优势:简单,易产生更大空闲块][缺点:易产生外碎片]

地址排序的空闲块列表
分配需要寻找一个合适的分区
重分需要检查相邻空闲分区是否可以合并

  1. 最优适配:按分区在内存的先后次序从头查找,找到其大小与要求相差最小的空闲分区进行分配 [优势:避免分割大空间][缺点:外部碎片,重分配慢,易产生微小碎片]

尺寸排序的空闲块列表
分配需要寻找一个合适的分区
重分需要检查相邻空闲分区是否可以合并

  1. 最差适配:按分区在内存的先后次序从头查找,找到最大的空闲分区进行分配 [优势:中等尺寸分配效果最好][缺点:重分配慢,大分区可能无法被分配]

地址排序的空闲块列表
分配很快
重分需要检查相邻空闲分区是否可以合并

压缩式与交换式碎片整理

  1. 重置程序合并孔洞,压缩后,所有的空闲分区都是连续的,所有已被使用的分区也都集中在一起
  2. 要求所有程序是动态可重置
  3. 开销大,系统的效率会降低
  1. 情形描述:运行程序需要更多的内存
  2. 抢占等待的程序的内存,回收它们的内存,将其数据存放到硬盘上

非连续内存分配

在前面的几种存储管理方法中,为进程分配的空间是连续的,使用的地址都是物理地址。如果允许将一个进程分散到许多不连续的空间,就可以避免内存紧缩,减少碎片

  1. 分配给一个程序的物理内存是连续的
  2. 内存利用率低
  3. 有外、内碎片问题
  1. 一个程序的物理内存是非连续的
  2. 更好的内存利用和管理
  3. 允许共享代码和数据(共享库等...)
  4. 支持动态加载和动态链接
  1. 软件:开销大
  2. 硬件分段/分页

分段式存储管理

image.png
  1. 段表示访问方式和存储数据等属性相同的一段地址空间
  2. 上图可以看出段对应一个连续的内存“块”
  3. 若干个段组成进程逻辑地址空间
  1. 逻辑地址由二元组(s,addr)表示
  2. s:段号;addr:段内偏移
  3. 实现方案:段寄存器+地址寄存器方案;单地址实现方案
  4. 硬件实现机制:操作系统需要设置段表
段访问硬件实现机制
  • 首先从逻辑地址中得到段号和偏移量
  • 在段表中查找段号,得到段基址和段长度范围
  • MMU来判断偏移量是否合法(偏移量是否大于段长度)
  • 得到物理地址,在物理内存中查找相应内容

分页式存储管理

划分物理内存至固定大小的帧(Frame),大小是2的幂;划分逻辑地址空间至相同大小的页(Page),大小是2的幂

帧(物理地址)

f:帧号(F位,共有2^F个帧)
o:帧内偏移(S位,每帧有2^S个字节)
物理地址 = [2^Sxf + o]

物理地址计算例子

页(逻辑地址)

页内偏移的大小 = 帧内偏移的大小; 页号大小 ≠ 帧号大小

p:页号(P位,共有2^P个帧)
o:页内偏移(S位,每页有2^S个字节)
逻辑地址 = [2^Sxp + o]

页寻址机制

页寻址机制.jpg
  • 首先从逻辑地址中得到页号和页内偏移
  • 在页表中由页号查找帧号,页表保存了逻辑地址->物理地址的映射关系
  • 帧号和帧内偏移(页内偏移)得到物理地址,在物理内存中查找相应内容

页表

  1. 修改位(dirty bit):对应的页面中的内容是否被修改了
  2. 存在位(resident bit):逻辑页面是否存在与之对应的物理帧有些逻辑页没有对应的物理帧
  3. 引用位(clock/reference bit):在过去一段时间内是否访问过页中的某一个存储单元
页表结构 地址转换实例
  1. 时间性能问题访问一个内存单元需要两次内存访问(一次用于获取页表项,一次用于访问数据)
  2. 空间性能问题:页表可能非常大
  3. 解决方法(缓存,间接访问方式)
快表(TLB)

关联存储器有一组key,可以并行地查找所有表项,得到匹配项

  • 如果TLB命中,物理页号可以很快被获取
  • 如果TLB未命中,对应的表项被更新到TLB中
快表
多级页表

x86架构中,CR3寄存器用于存储PTBR(页表基址)

多级页表
反向页表
  1. 大地址空间问题,对于大地址空间(64-bits)系统,多级页表变得繁琐;比如:5级页表
  2. 不让页表与逻辑地址空间的大小相对应,让页表与物理地址空间的大小相对应,逻辑(虚拟)地址空间增长速度快于物理地址空间
基于页寄存器方案
  1. 页寄存器思路:不让页表与逻辑地址空间的大小相对应,让页表与物理地址空间的大小相对应,以帧号为索引去寻找页号,这样反向页表大小最大就为物理地址的容量
  2. 页寄存器(Page Registers)每个帧与一个页寄存器(Page Register)关联,寄存器内容包括:
  • 使用位(Residence bit):此帧是否被进程占用
  • 占用页号(Occupier):对应的页号p
  • 保护位(Protection bits):约定这一页的访问方式,可读 or 可写
  1. 页寄存器方案资源消耗举例
页寄存器方案举例
  1. 页寄存器方案优点
  • 页表大小相对于物理内存而言很小
  • 页表大小与逻辑地址空间大小无关
  1. 页寄存器方案缺点
  • 页表信息对调后,需要根据帧号可找页号
  • 在页寄存器中搜索逻辑地址中的页号
基于关联内存方案
  1. 如果帧数较少, 页寄存器可以被放置在关联内存中
  2. 在关联内存中查找逻辑页号
  • 成功: 帧号被提取
  • 失败: 页错误异常(pagefault)
  1. 限制因素
  • 大量的关联内存非常昂贵
  • 到难以在单个时钟周期内完成
  • 耗电
基于hash方案
  1. 在反向页表中通过哈希算法查找对应页表项中的帧号
  2. 查找过程
  • 从逻辑地址中得到页号
  • 根据页号和PID计算出Hash值
  • 在反向页表中查找对应的页表项,核对页号是否一致,从中找出相应的物理帧号

虚拟内存

简单介绍

虚拟内存
  • 如果是程序太大, 超过了内存的容量, 可以采用手动的覆盖(overlay)技术, 只把需要的指令和数据保存在内存当中:
  • 如果是程序太多, 超过了内存的容量, 可以采用自动的交换(swapping)技术, 把暂时不能执行的程序送到外存中:
  • 如果想要在有限容量的内存中, 以更小的页粒度为单位装入更多更大的程序, 可以采用自动的虚拟存储技术

覆盖技术

  1. 必要部分(常用功能)的代码和数据常驻内存
  2. 可选部分(不常用功能)在其他程序模块中实现,平时存放在外存中,在需要用到时才装入内存;
  3. 不存在调用关系的模块不必同时装入到内存,从而可以相互覆盖,即这些模块共用一个分区
覆盖技术举例
  1. 由程序员来把一个大的程序划分为若干个小的能模块;并确定各个模块之间的覆盖关系,增加了程序复杂度,费时费力;
  2. 覆盖模块从外存装入内存, 实际上是以时间延长换取空间

交换技术

交换技术
  1. 交换时机的确定(何时发生交换?只有当内存空间不够时交换);
  2. 交换区的大小(必须足够大以存放所有用户进程的所有内存映像的拷贝;必须能对这些内存映像直接存取);
  3. 程序换入时的重定位(换出后再换入的内存位置一定要在原来的位置上吗?最好采用动态地址映射的方式)。
  1. 覆盖只能发生在那些相互之间没有调用关系的程序模块之间,因此程序员必须给出程序内的各个模块之间的逻辑覆盖结构。
  2. 交换技术是以在内存中的程序大小为单位来进行的, 它不需要程序员给出各个模块之间的逻辑覆盖结构;
  3. 交换发生在内存中程序与管理程序或操作系统之间而覆盖则发生在运行程序的内部

虚存技术

image
  1. 时间局部性:一条指令的一次执行和下次执行, 一个数据的一次访问和下次访问都集中在一个较短时期内
  2. 空间局部性:当前指令和邻近的几条指令, 当前访问的数据和邻近的几个数据都集中在一个较小区域内
  1. 在装入程序时,不必将其全部装入内存中,只需装入部分执行的页或段
  2. 程序执行过程中,如果需执行的指令或访问的数据尚未在内存中(称为缺页或缺段),则由处理器通知操作系统将相应页面或段调入到内存中,再继续执行程序
  3. 操作系统将内存中暂时不使用的页面或段调出保存在外存中,从而腾出更多空闲空间。
  1. 大的用户空间虚拟地址可以远远大于物理地址,32位系统虚拟地址理论上可以访问4GB
  2. 部分交换:与交换技术相比,虚拟存储的调入调出是对部分虚拟地址空间进行的
  3. 不连续性物理内存分配的不连续性,虚拟地址空间使用的不连续性

虚拟页式内存管理

虚拟页式内存管理
  1. 当一个用户程序要调入内存运行时,不是将该程序的所有页面都装入内存,而是只装入部分的页面,就可启动程序运行;
  2. 在运行的过程中,如果发现要运行的程序或要访问数据不在内存,则向系统发出缺页中断请求,系统在处理这个中断时,将外存中相应的页面调入内存,使得该程序能继续运行。
页表表项
  1. 页号:虚拟地址空间中的页号
  2. 访问位如果该页面被访问过( 包括读操作或写操作),则设置此位。用于页面換算法。
  3. 修改位表示此页在内存中是否被修改过。当系统回收该物理页面时,根据此位来决定是否把它的内容写回外存0 表示页面在内存时数据未被修改,1 表示被修改过。
  4. 保护位表示允许对该页做何种类型的访问,如只读、可读写、可执行等.
  5. 驻留位表示该页是在内存还是在外存。1 表示在内存中,0 表示不在内存中,为 0 时会发生“缺页”中断信号,请求系统处理
  6. 帧号:物理地址空间的帧号
缺页中断

后备存储

后备存储

页面置换算法

最优页面置换算法(OPT)

先进先出算法(FIFO)

最近最少使用算法(LRU)

链表实现:系统维护一个页面链表, 最近刚刚使用过的页面作为首结点, 最久未使用的页面作为尾结点。每一次访问内存时, 找到相应的页面. 把它从链表中摘下来, 再移动到链表之首。每次缺页中断发生时, 淘汰链表末尾的页面。

堆栈实现:设置一个活动页面栈. 当访问某页肘, 将此页号压入栈顶, 然后, 考察栈内是否有与此页面相同页号, 若有则抽出。当需要淘汰一个页面时,总是选择栈底的页面, 它就是最久未使用的。

时钟算法(CLOCK)

  1. 需要用到页表项当中的访问位,当一个页面被装入内存时,把该位初始化为0。然后如果这个页面被访问(读/写),把该位置为1
  2. 把各个页面组织成环形链表(类似钟表面),把指针指向最老页面(最先进来)
  3. 当发生缺页中断时,考察指针是否指向最老页面,若它的访问位为0,则立即淘汰;若访问位为1,则把该位置0,然后指针指向下一个,直到找到淘汰页面,然后把指针移动到它的下一格。

第二次机会算法(SCR)

第二次机会算法

最不常用算法(LFU)

进程及线程

进程基本概念

进程定义

进程示意图

进程组成

总之,进程包含正在运行的一个程序的所有状态信息

进程特点

进程管理

进程控制结构(PCB)

  1. 进程的创建:为该进程生成一个PCB;
  2. 进程的终止:回收它的PCB;
  3. 进程的组织管理:通过对PCB的组织管理来实现。
pcb进程标识和状态信息
  1. 链表同一状态的进程其PCB成一链表,多个状态对应多个不同的链表

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

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

各状态的进程形成不同的索引表:就绪索引表、阻塞索引表

pcb组织方式

进程生命周期

进程等待(阻塞)情况:

  1. 请求并等待系统服务,无法马上完成
  2. 启动某种操作,无法马上完成
  3. 需要的数据没有到达
进程状态变化 进程挂起
  1. 阻塞挂起状态进程在外存并等待某事件出现
  2. 就绪挂起状态进程在外存,但只要进入内存便可运行
  3. 挂起状态变化
挂起状态变化

OS进程调度

OS进程调度
  1. 操作系统来维护一组队列,用来表示系统当中所有进程的当前状态;
  2. 不同的状态分别用不同的队列来表示(就绪队列、各种类型的阻塞队列);
  3. 每个进程的PCB都根据它的状态加入到相应的队列当中当一个进程的状态发生变化时肘,它的PCB 从一个状态队中脱离出来,加入到另外一个队列
状态队列

线程基本概念

线程定义

  1. 从资源组合角度进程是把一组相关的资源组合起来,构成了一个资源平台,包括地址空间、打开的文件等各种资源
  2. 从运行的角度线程是代码在这个资源平台上的一条执行流程
  3. 线程 = 进程 - 共享资源
进程中的线程
  1. 一个进程中可以同时存在多个线程
  2. 各个线程之间可以并发地执行
  3. 各个线程之间可以共享地址空间和文件等资源
  1. 一个线程崩溃, 会导致其所属进程的所有线程崩溃

线程与进程区别

  1. 进程是资源分配单位线程是CPU调度单位
  2. 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈
  3. 线程同样具有就绪、阻塞和执行三种基本状态,同样具有状态之间的转换关系
  4. 线程能减少并发执行的间和空间开销
  • 线程的创建时间比进程短;
  • 线程的终止时间比进程短;
  • 同一进程内的线程切换时间比进程短;
  • 由于同一进程的各线程间只享内存和文件资源,可直接进行不通过内核的通信。
进程线程区别

线程实现

用户线程

  1. 在用户空间实现,它不依赖于操作系统的内核,由一组用户级的线程库函数来完成线程的管理,包括进程的创建、终止、同步和调度等
  2. 由于用户线程的维护由相应进程来完成(通过线程库函数),不需要操作系统内核了解用户线程的存在,可用于不支持线程技术的多进程操作系统
  3. 每个进程都需要它自己私有的线程控制块(TCB)列表,用来跟踪记录它的各个线程的状态信息PC、栈指针、寄存器),TCB 由线程库函数来维护
  4. 用户线程的切换也是由线程库函来完成,无需用户态/核心态切换,所以速度特别快
  5. 允许每个进程拥有自定义的线程调度算法
用户线程缺点

内核线程

内核线程示意图
  1. 在操作系统的内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建、终止和管理
  2. 内核来维护进程和线程的上下文信息(PCB 和TCB)
  3. 线程的创建、终止和切换都是通过系统调用/内核函数的方式来进行,由内核来完成,因此系统开销较大
  4. 在一个进程当中,如果某个内核线程发起系统调用而被阻塞,并不会影响其他内核线程的运行
  5. 时间片分配给线程多线程的进程获得更多CPU时间

轻量级线程

内核支持的用户线程一个进程可以有多个轻量级进程,每个轻量级进程由一个单独的内核线程来支持

轻量级进程

上下文切换

  1. 在切换之前,存储许多部分的进程上下文
  2. 能够在之后恢复他们
  3. 必须快速,因为上下文切换十分频繁
  1. 寄存器(PC、SP...),CPU状态,...
上下文切换

进程控制

  1. 调用fork创建一个继承的子进程
  2. 复制父进程的所有变量和内存
  3. 复制父程的所有CPU寄存器有一个寄存器例外
fork exec示意图
  1. 一个子进程向父进程返回一个值,所以父进程须接受这个值并处理
  2. 它使父进程去睡眠来等待子进程的结果
  3. 当一个子进程调用exit()的时候,操作系统解锁父进程,并且通过exit()传递得到的返回值作为wait()调用的一个结果,如果这里没有子进程存活,wait()立引返回
  4. 当然,如果这里有父进程的僵尸等待,wait()立即返回其中一个值(并解除僵尸状态
  5. 父进程可以帮助子进程清除所有资源
  1. 将这程序“ 结果” 作为一个参数
  2. 关闭所有打开的文件,连接等等
  3. 释放内存
  4. 释放大部分支持进程的操作系统结构
  5. 检查父进程是否存活:
  • 如果是活,它保留的结果直到父进程需要它;进程没有真正死亡,但是它进入了僵尸状态
  • 如果是死,它释放所有数据结构,这个进程死亡
  1. 清理所有等待的僵尸进程

CPU调度

调度背景

  1. 一个进程从运行状态切换到等待状态
  2. 一个进程被终结
  1. 非抢占式调度程序必须等待事件结束
  2. 抢占式
  • 调度程序在中断被响应后执行
  • 当前进程从运行切换到就绪,或一个进程从等待切换到就绪
  • 当前运行的进程可以被换出

调度原则

基本概念 CPU调度目标

调度算法

先来先服务(FCFS)

  1. 根据就绪队列的到达时间来排序,此时就绪队列是一个FIFO队列先到先服务,算法简单
  2. 缺点
  • CPU进程区间变化很大时,平均等待时间会变化很大
  • 可能导致I/O和CPU之间的重叠处理CPU密集型进程会导致I/O设备闲置时,I/O密集型进程也在等待

最短作业优先调度(SJF)

  1. 按照预测的完成时间来将任务入队
  2. 可以是可抢占的或不可抢占的
  3. 可能导致饥饿
  • 连续的短任务流会使长任务饥饿
  • 短任务可用时的任何长任务的CPU时间都会增加平均等待时间

最高响应比优先(HRRN)

  1. 在SPN调度的基础上改进
  2. 不可抢占
  3. 关注进程等待了多长时间
  4. 防止无限期推迟

响应比:R = (w + s)/s ,选择R值最高的进程

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

轮转法调度(RR)

  1. 选择固定时间片时间片到了则切换进程运行
  2. 时间片太大
  • 等待时间过长
  • 极限情况退化成FCFS
  1. 时间片太小
  • 反应迅速,但是切换上下文频繁
  • 吞吐量由于大量的上下文切换开销受到影响
  1. 目标
  • 选择一个合适的时间片
  • 经验规则: 维持上下文切换开销处于1%以内

多级反馈队列调度

  1. 实现思路
  • 就绪队列被划分成独立的队列:如前台(交互),后台(批处理)
  • 每个队列拥有自己的调度策略:如前台一RR,后台——FCFS
  • 调度必须在队列间进行:固定优先级或时间片轮转
  1. 具体方案举例
  • 一个进程可以在不同的队列中移动
  • 例如:有n 级优先级(优先级调度在所有级别中,RR在每个级别中),时间片大小随优先级级别增加而增加,如果任务在当前的时间片中没有完成, 则降到下一个优先级
多级调度

公平共享调度(FFS)

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

实时调度

实时系统

  1. 时间约束的及时性
  2. 速度和平均性能相对不重要
  1. 时间约束的可预测性

多处理器调度

  1. 多处理器的CPU调度更加复杂
  • 多个相同的单处理器组成一个另处理器
  • 优点:负载共享
  1. 多对称处理器(SMP)
  • 每个处理器运行自己的调度程序
  • 需要在调度程序中同步
多处理器

优先级反转

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

进程/线程同步

背景知识

  1. 资源共享
  2. 加速:CPU计算和I/O操作可以重叠
  3. 模块化:将大程序拆分为小程序,使系统易于扩展
  1. 该执行成功结束
  2. 或者根不没有执行
  3. 并且不应该发现任何部分执行的状态
  4. i++,java new一个对象都不是原子操作

临界区

实现方案

  1. 没有中断,没有上下文切换,因此没有并发
  • 硬件将中断处理延迟到中断被启用之后
  • 大多数现代计算机结构体系都提供指令来完成
  1. 进入临界区禁止中断
  2. 离开临界区开启中断
  3. 缺点
  • 一旦中断停用,线程无法停止,整个系统停止,其他线程可能处于饥饿状态
  • 如果临界区任意长,无法限制响应中断所需时间
  • 要小心使用

基于软件解决方案

  • 使用两个共享数据项:
基于软件互斥方法
  • Dekker算法(1965):第一个针对双线程例子的正确解决万案
  • Bakery算法(Lamport 1979):针对n线程的临界区问题解决方案
  1. 复杂:需要两个进程间的共享数据顷
  2. 需要忙等待:浪费CPU 时间
  3. 没有硬件保证的情况下无真正的软件解决方案原子操作都需要硬件支持

更高级的抽象

  1. 硬件提供了一些原语中断禁用原子操作指令),大多数现代体系结构都这样
  2. 操作系统提供更高级的编程抽象来简化并行编程锁,信号量),从硬件原语中构建

  1. 定义:锁是一个抽象的数据结构
  • 一个二进制状态(锁定/ 解锁),两种方法
  • Lock::Acquire()锁被释放前一直等待,然后得到锁
  • Lock::Release()释放锁,唤醒任何等待的进程
  1. 应用编写临界区
  1. 原子操作指令
原子操作指令 TestAndSet

忙等和无忙等待对比:

忙等和无忙等待对比 Exchange

优点:

  • 适用于单处理器或者共享主存的多处理器中任意数量的进程
  • 筒单并且容易证明
  • 可以用于支持多临界区

缺点:

  • 忙等待消耗处理器时间
  • 当进程离开临界区并且多个进程在等待的时候能导致饥哦

信号量

  1. 定义一个抽象数据类型在早期的操作系统是主要的同步原语
  • 一个整型(sem),两个原子操作
  • P()sem减1,如果sem<0,等待,否则继续
  • V()sem加1,如果sem<=0,唤醒一个等待的P
  1. 一些基本概念
  1. 用途可以用在互斥,同步
信号量实现互斥 信号量实现同步
  1. 信号量实现
信号量的实现
  1. 信号量实现生产者消费者模型
生产者消费者模型
  • 在任何一个时间只能有一个线程操作缓冲区(互斥)
  • 当缓冲区为空,消费者必须等待生产者(调度/同步约束)
  • 当缓存区满,生产者必须等待消费者(调度/同步约束)
信号量实现生产者消费者模型
  1. 缺点

管程

  1. 目的分离互斥和条件同步的关注,封装了同步操作,对进程隐蔽了同步细节,简化了同步功能的调用
  2. 定义:包含若干共享变量及其说明和所有访问这些共享变量的函数所组成的软件模块
管程
  1. 实现
  • Wait() operation释放锁,睡眠,重新获得锁返回后
  • Signal() operation唤醒等待者(或者所有等待者)
管程实现
  1. 管程实现生产者消费者模型
image

IPC

  1. 如果P和Q想通信,需要在他们之间建立通信链路共享内存,硬件总线,逻辑属性等),通过send/receive交换消息
  2. 存在直接通信,间接通信两种方式
进程间通信
  1. 直接通信

进程必须正确的命名对方

  • send(P, message):发送信息到进程P
  • receive(Q,message):从进程Q接受消息

通启链路的属性:

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

定向从消息队列接收消息:

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

通信链路的属性:

  • 只有进程共享一个相同的消息队列,才建立链路
  • 链接可以与许多进程相关联
  • 每对进程可以共享多个通信链路
  • 链接可以是单向或双向
  1. 消息传递分为阻塞和非阻塞两种
  • 阻塞:同步
  • 非阻塞:异步
  1. 通信链路缓冲
  • 0容量:发送方必须等待接收方
  • 有限容量(n):如果队列满,发送方必须等待
  • 无限容量:发送方不需要等待

信号(Signal)

  1. 定义软件中断通知事件处理(如SIGFPE, SIGKILL...)
  2. 接收到信号时会发生什么:
  • Catch: 指定信号处理函数被调用
  • Ignore: 依靠操作系统默认操作(如: Abort, memory dump, suspend or resume process)
  • Mask:闭塞信号因此不会传送(可能是暂时的)
  1. 不足
信号实现机制

管道(Pipe)

  1. 定义是一系列将标准输入输出链接起来的进程,其中每一个进程的输出被直接作为下一个进程的输入
  2. 原理:每创建一个管道,就有两个文件描述符,一个是负责读管道的,一个是负责写管道的。所以,使用管道通信时,可以看作是两个文件描述符加一段内核空间中的内存
  3. 管道只能协调有亲缘关系的进程间通信,所谓亲缘,比如父子进程、兄弟进程。当某进程创建一个管道后,它就拥有了这个管道的两个文件描述符,它的子进程会继承这两个文件描述符,所以子进程也能读写这个管道
  4. 示意图
管道示意图

消息队列

  1. 定义一个消息的链表,是一系列保存在内核中消息的列表,由消息队列标识符标识
  2. 优点
  • 传递消息多,克服了信号传递信息少缺点
  • 克服了管道只能承载无格式字节流以及缓冲区大小受限的缺点
消息队列

共享内存

  1. 定义映射一段能被其他进程所访问的内存这段共享内存由一个进程创建,但多个进程都可以访问
  2. 优点
  • 通信速度最快的 IPC 方式,针对其他进程间通信方式运行效率低而专门设计的
  • 进程可以直接读写内存,而不需要任何数据的拷贝
  1. 缺点必须同步数据访问
  2. 实现方式:内存映射和共享内存机制两种方式
  • 内存映射==内存映射 memory map机制使进程之间通过映射同一个普通文件实现共享内存,通过mmap()系统调用实现。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read/write等文件操作函数==
  • 共享内存机制==把所有的共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面==
  1. 原理
共享内存示意图

死锁

  1. 定义一个进程集合中的多个进程因为竞争资源,而造成的互相等待现象
  2. 死锁原因
  1. 死锁的必要条件
  1. 死锁预防保证系统不进入死锁状态的一种策略基本思想是要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态

a. 一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测

b. 资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行

c. 降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了

a. 限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销

b. 为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间

  1. 死锁避免

安全序列{P1,P2,...,Pn}是这样组成的:若对于每一个进程Pi,它需要的附加资源可以被系统中当前可用资源加上所有进程Pj当前占有资源之和所满足(其中j<i),则{P1,P2,...,Pn}为一个安全序列,这时系统处于安全状态,不会进入死锁状态。

  1. 死锁的检测与恢复:

a. 最常用的方法就是进行系统的重新启动,不过这种方法代价很大

b. 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁

文件系统

基本概念

  • 文件属性:名称、类型、位置、大小、保沪、创建者、创建时间、最近修改时间、....
  • 文件头:在存储元数据中保存了每个文件的信息保存文件属性,跟踪哪一块存储块属于逻辑上文件结构的哪个偏移。
  1. 分配文件磁盘空间管理文件块,管理空闲空间,分配算法
  2. 管理文件集合定位文件及其内容,命名,最常见:分层文件系统)
  3. 提供的便利及特征保护作用:分层来保证安全可靠性,持久性
  1. 磁盘文件系统:文件存储在数据存储设备上,如FAT、NTFS、ext2/3
  2. 数据库文件系统:文件根据其特征是可被寻址的,如WinFS
  3. 日志文件系统:记录文件系统的修改/事件
  4. 分布式文件系统:如NFS、SMB、AFS

文件描述符

  1. 文件指针:指向最近的一次读写位置,每个打开了这个文件的进程都这个指計
  2. 文件打开计数:记录文件打开次数一当最后一个进程关闭了文件时,允许将其从打开文件表中移除
  3. 文件磁盘位置:缓存数据访问信息
  4. 访问权限:每个程序访问模式信息
  • 块是逻辑转换单元,unix中块大小是4KB
  • 即使每次只访问1字节数据,也会缓存目标数据4096字节

文件目录

  1. 文件名的线性列表,包含了指向数据块的指针
  • 编程简单
  • 执行耗时
  1. Hash数据结构的线性表
  • 减少目录搜索时间
  • 碰撞,两个文件名的hash值相同
  • 固定大小
解析目录 挂载点

虚拟文件系统

  1. 文件系统分层结构
文件系统分层结构
  1. 目的:对所有不同文件系统的抽象
  2. 功能
  1. 文件系统数据
  • 卷控制块(每个文件系统一个)文件系统详细信息,块、块人小、空余块、计数/指针等
  • 文件控制块(每个文件一个)文件详细信息,许可、拥有者、大小、数据库位置
  • 目录节点(每个目录一个):将目录项数据结构及树型布局编码成树型数据结构,指向文件控制块、父节点、项目列表等
文件系统数据结构

数据块缓存

磁盘缓存
  1. 普通缓冲区缓存
  2. 页缓存统一数据块和内存项

一个页可以映射到一个本地文件中
数据块被映射成页
文件的读/写转换成对内存的操作
可能导致缺页

页缓存

打开文件数据结构

  1. 每个被打开的文件一个
  2. 文件状态信息
  3. 目录顶、当前文件指针、文件操作设置等
  1. 一个进程一个
  2. 一个系统级的
  3. 每个卷控制块也会保存一个列表
  4. 所以如果有文件被打开将不能被卸载
打开文件表

文件分配(数据块)

  1. 需要对小文件提供强力的支持
  2. 块空间不能太大
  1. 必须支持大文件(64-bit 文件偏移)
  2. 大文件访问需要相当高效
  1. 连续分配

文件头指定起始块和长度,优势:文件读取表现好,高效的顺序和随机访问,缺点:碎片,文件增长问题(对文件扩容麻烦)

连续分配
  1. 链式分配

文件以数据块链表方式存储,文件头包含了第一块和最后一块的指针,优点:创建,增大,缩小很容易,没有碎片;缺点:不可能真正的随机访问,可靠性差

链式分配
  1. 索引分配

为每个文件创建索引数据块,文件头包含了数据索引块,优点:创建,增大,缩小很容易,没有碎片,可随机访问;缺点:文件小时存储索引冗余文件大时索引数据块有容量限制(采用分级索引解决大文件问题,链式索引/多级索引)

索引分配
  1. 多级索引
多级索引 多级索引

文件头包含13个指针

  • 10个指针指向数据块;
  • 第11个指针指向间接接据块;
  • 第12个指针指向二级间接接据块;
  • 第13个指针指向三级间接接据块。
  • 提高了文件大小限制阈值
  • 动态分配数据块,文件扩展很容易
  • 小文件开销小
  • 只为大文件分配间接数据块,大文件在访问间接数据块是需要大量的查询
  1. 高效:存储利用(外部碎片)
  2. 表现:访问速度

空闲空间列表

  1. 使用简单,但位图会是个大向量,160GB硬盘需要5M位图数据量
  2. 需要保证其一致性
链式/分组列表

多磁盘管理-RAID

  1. 一个分区是一个柱面集合
  2. 每个分区都是逻辑上独立的磁盘
磁盘示意图
  1. 它是一种用于连接多个辅助存储设备以提高性能,数据冗余或两者兼备的技术
  2. RAID技术有7个级别的RAID方案。这些模式为:RAID 0,RAID 1,....,RAID 6
  1. 在操作系统内核存储/卷管理
  2. RAID硬件控制器(I/O)
多磁盘

磁盘调度

  1. 寻道时间:是将磁盘臂定位到满足读/写请求的指定磁道所用的时间寻道时间是性能上的区别本质
  2. 旋转延迟期望的扇区将自己倒换到可以访问R / W磁头的位置
  3. 传输时间: 传输数据所需的时间
I/O访问时间
  1. FCFS调度算法(先来先服务)
  2. SSTF算法(最短寻找时间优先)
  3. SCAN调度

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

  1. C-SCAN调度

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

  1. C-LOOK调度

C-SCAN的改进版,达到该方向最后一个请求处立即停止

  1. N-Step-Scan算法
  • SSTF、SCAN 及C-SCAN这几种调度算法中,都可能出现磁臂停留在某处不动的情况,例如进程反复请求对某一磁道的I/O操作。我们把这一现象称为“磁臂粘着”。
  • N-Step-Scan算法是将磁盘请求队列分成若干个长度为N的子队列,磁盘调度将按FCFS算法依次处理这些子队列。而处理每一个队列又是SCAN算法,对一个队处理完后,再处理其他队列。
  1. FSCASN算法:
  • 实质上是N 步SCAN 算法的筒化,只将磁盘请求队列分成两个子队列。
上一篇 下一篇

猜你喜欢

热点阅读