OS
清华大学操作系统网课笔记
课程地址
Chapter1
操作系统将CPU抽象成进程,将磁盘抽象成文件,将内存抽象成地址空间
并发和并行的区别:并发是一段时间内有多个程序可以运行,并行是一个时间点上有多个程序可以执行
Chapter2
内核态和用户态:
用户态是应用程序执行过程中CPU所处于的执行的特权级状态,特权级比较低
内核态是操作系同运行过程中CPU所处于的状态,这个状态中可以执行特权指令
系统调用:应用程序请求操作系统提供服务
当应用程序调用系统调用时会完成从用户态到内核态的装换,从而让控制权从应用程序转到操作系统
系统调用和函数调用的区别:
函数调用是在一个栈空间,而系统调用是使用不同的堆栈,应用程序与操作系统使用不同的堆栈并且完成从用户态到内核态的转换,这个开销比函数调用大(跨越操作系统边界的开销),但安全
Chapter3
操作系统管理物理内存完成的功能
- 抽象 逻辑地址空间
- 保护 独立地址空间
- 共享 访问相同内存
- 虚拟化 更多的地址空间
物理地址空间——硬件支持的地址空间
逻辑地址空间——一个运行的程序所拥有的地址空间
编译器将基于符号的地址变成逻辑地址,操作系统完成逻辑地址到物理地址的映射
操作系统简单的内存管理方法
- 首次适配
- 最优适配
- 最差适配
无论用哪种都会产生内存碎片(内碎片和外碎片)
减少碎片的方法
- 压缩式碎片整理
- 交换式碎片整理
Chapter4 非连续内存分配
为什么需要非连续内存分配
连续内存分配的缺点:
- 分配给一个程序的物理内存是连续的
- 内存利用率较低
- 有外碎片、内碎片问题
非连续内存分配的优点:
- 一个程序的物理地址空间是非连续的
- 更好的内存利用和管理
- 允许共享代码与数据
- 支持动态加载和动态链接
缺点:
- 如何建立虚拟地址和物理地址之间的转化(软件方案\硬件方案)
两种硬件方案:
- 分段
- 分页
分段
将连续的逻辑地址空间分段成多个物理地址空间
分段寻址硬件实现方案:
从逻辑地址开始,逻辑地址由段号和偏移组成,通过段号找到段所在物理内存的起始地址,具体的,段号指定了段表中的一个位置,段表中存储着逻辑地址段号和物理地址段号之间的对应关系,以及段的长度限制,随后偏移和段表中的起始地址加起来得到物理地址,其中,段表是由操作系统建立的
分页
分页是现在大部分CPU中采取的非连续内存管理方式
分段和分页的最大区别:在分段中,段的尺寸是可变的,分页中页的大小是固定的
- 划分物理内存至固定大小的帧(物理页frame):大小是2的幂
- 划分逻辑地址空间至相同大小的页(逻辑页page):大小是2的幂(方便硬件实现)
帧(frame)
物理内存被分割为大小相等的帧,一个内存物理地址是一个二元组(f,o)
f——帧号(F位,共有2^F个帧)
o——帧内偏移(S位,每帧有2^S字节)
物理地址=2^S*f+o
页(page)
一个程序的逻辑地址空间被划分为大小相等的页
- 页内偏移的大小=帧内偏移的大小
- 页号大小<>帧号大小
一个逻辑地址也是一个二元组(p,o)
p——页号
o——页内偏移
逻辑地址计算方法和物理地址一样
页寻址机制
页表保存了逻辑地址——物理地址之间的映射关系,页表以页号为索引,存放的是帧号,页表也是由操作系统建立的
页寻址机制
- 页映射到帧
- 页是连续的虚拟内存
- 帧是非连续的物理内存
- 不是所有的页都有对应的帧
页表
页表就是一个大数组,索引是页号,存储的是帧号,页表中有一些Flag,可表明页帧是否存在,不存在则抛出内存访问异常
分页机制的性能问题
- 访问一个内存单元需要两次内存访问:一次用于获取页表项,一次用于访问数据
- 页表可能非常大,64位机器如果每页1024字节,那么一个页表的大小为2^54,普通计算机无法存储
- 每个应用程序有自己的页表
解决上述问题
Translation Look-aside Buffer(TLB)
TLB是CPU中MMU(内存管理单元)里的Cache,用来缓存近期访问的页帧装换表项,从而提升访问速度,对于TLB找不到的情况,称为TLB的缺失,在写程序时,尽量保证访问的局部性,从而避免TLB的缺失(miss)
时间换空间
二级页表(减少内存消耗)
多级列表
反向页表
index为帧号,值为页号,此时页表的大小只与物理地址空间大小相关,与逻辑地址空间大小无关,更节省空间
- 基于页寄存器
- 基于关联内存
- 基于哈希
Chapter5 虚拟内存
解决内存容量不够的问题:overlay、swap、虚拟内存
覆盖技术
目标:在比较小的可用内存中运行较大的程序
原理:把程序按照其自身逻辑结构,划分为若干个功能上相对独立的程序模块,那些不会同时执行的模块共享同一块内存区域,按时间先后来执行
缺点:增加编程难度、时间换空间
交换技术
目标:多道程序在内存中时,让正在运行的程序或需要运行的程序获得更多的内存资源
方法:可将暂时不能运行的程序送到外存,从而获得空闲内存空间
虚存技术
虚拟内存管理技术
覆盖技术:增加了程序员的负担
交换技术:增加了处理器的开销
目标:
- 像覆盖技术那样,不是吧程序的所有内容都放在内存中,因而能够运行比当前空闲内存空间更大的的程序,但做的更好,由操作系统自动来完成,无须程序员的干涉
- 像交换技术那样,能够实现进程在内存与外存之间的交换,因而获得更多的空闲内存空间,但做的更好,只对进程的部分内容在内存和外存之间进行交换
程序的局部性原理(principle of locality):指程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域,这可以表现为:
- 时间局部性:一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时期内;
- 空间局部性:当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都几种在一个较小区域内;
虚存技术的基本概念:(可在页式或段式内存管理的基础上实现)
- 在装入程序时,不必将其全部装入到内存,而只需将当前需要执行的部分页面或段装入到内存,就可让程序开始执行
- 在程序执行过程中,如果需执行的指令或访问的数据尚未在内存(称为缺页或缺断),则由处理器通知操作系统将相应的页面或段调入得到内存,然后继续执行程序
- 另一方面,操作系统将内存中暂时不使用的页面或段调出保存在外存上,从而腾出更多空闲空间放将要装入的程序以及将要调入的页面或段
基本特征:
- 更大的用户空间
- 部分交换
- 不连续性
虚拟技术–虚拟页式内存管理
- 大部分虚拟存储系统都采用虚拟页式存储管理技术,即在页式存储管理的基础上,增加请求调页(从外存中调入所需页)和页面置换(换出不需要页,换入需要页)功能
页表
缺页中断
后备存储
- 一个虚拟地址空间的页面可以被映射到一个文件中的某个位置
- 代码段:映射到可执行二进制文件
- 动态加载的共享库程序段:映射到动态调用的库文件
- 其他段:可能被映射到交换文件(swap file)
虚拟内存性能:有效存储器访问时间(effective memory access time)EAT
EAT = 访问时间×页表命中几率+page fault处理时间×page fault几率
程序的局部性特点使得p小,从而效率更高
Chapter 6 页面置换算法
- 功能:当缺页中断放生,需要调入新的页面而内存已满时,选择内存当中哪个物理页面被置换
- 目标:尽可能减少页面换进换出次数
- 页面锁定(frame locking):用语描述必须常驻内存的操作系统关键部分或时间关键的应用进程。实现的方法是:在页表中添加锁定标志位(lock bit)
记录一个进程对页访问的一个轨迹,模拟一个页面置换行为并记录产生缺页数的数量
局部页面置换算法(针对单个程序)
- 最优页面置换算法:缺页中断发生时,对于保存在内存的每一个逻辑页面,计算在它的下一次访问之前,还需等待多长时间,从中选择时间最长的那个,作为被置换的页面,这个算法作为最佳的评价置换算法的标准来计算缺页中断发生的次数,希望设计出的算法接近本算法的结果
- 先进先出算法:选择在内存中驻留时间最长的页面并淘汰之,性能较差,调出的页面有可能是经常要访问的页面,并且有Belady现象,FIFO算法很少单独使用。
- 最近最久未使用算法(Least Recently Used, LRU):缺页中断发生时,选择最久未使用的那个页面,并淘汰之,用双线链表或栈实现
- 时钟页面置换算法(LRU的近似,对FIFO的一种改进),效果接近LRU
- 需要用到页表项中的访问位(硬件功能,可被使用)
- 把各个页面组织成环形链表(类似钟表面),把指针指向最老的页面(最先进来)
- 当发生一个缺页中断时,考察指针所指向的最老页面,若它的访问位为0,立即淘汰;若访问位为1,则把该位置为0,然后指针往下移动一格
- 二次机会法,或Enhanced Clock algorithm,时钟页面置换法有一个巨大代价来替换“脏”页(被修改过的页),二次机会法修改Clock算法使得脏页总是在第一次时钟头扫描中保留下来,用两个bit(访问位、脏位)
- 最不常用法(Least Frequently Used, LFU),选择访问次数最少的页面换出
**Belady现象:在采用FIFO算法时,有时会出现分配的物理页面数增加,缺页率反而提高的异常现象
全局页面置换算法
操作系统动态分配每个程序的页帧数
工作集:一个进程当前正在使用的逻辑页面集合,(某时间过去一段时间访问到的逻辑页面的集合)
常驻集:当前时刻,进程实际驻留在内存当中的页面集合
希望两个集合尽量重合,减少缺页中断
常驻集大小达到某个数目后,再给它分配更多的物理页面,缺页率页不会明显下降
此时我们希望操作系统将某个进程中多余的页帧分配给别的进程,从而实现整体的缺页中断次数尽可能少
工作集页置换算法
随着程序执行,只要页不属于工作集窗口,就丢掉,从而确保物理内存有足够的空间,给其他程序提供更多内存,可以确保整个系统的缺页次数降低
缺页率页面置换算法
通过缺页率来动态调整每个程序的常驻集大小(物理页数),保证每个程序的缺页率平衡。具体的,计算两次缺页中断的间隔,大于某个阈值时,去掉两次中断间没有在工作集中断页,小于某个阈值时,加上导致缺页中断的页
抖动问题:如果分配给一个进程的物理页面太少,不能包含整个的工作集,即常驻集包含于工作集,那么进程将会造成很多缺页中断,需要频繁换入换出,从而运行速度变慢,这种状态称为“抖动”
原因:随着主流内存的进程数增加,分配给每个进程的物理页面不断减小
MIBF(mean time between page faults)平均缺页时间
PFST(page fault service time)页缺失服务时间
CPU的使用率是去除了换入换出的负担计算的
希望找到中间的平衡点,使得两次缺页间隔的平均时间和完成一个缺页服务的时间尽量相等,此时系统在运行很多程序的情况下,也有很高的CPU利用率
Chapter7 进程管理
进程描述
一个具有独立功能的程序在一个数据集合上的一次动态执行的过程
进程≠程序,进程是一个动态执行的过程,程序只是静态的代码
进程的特点:
- 动态
- 并发性(一段时间内有多个进程)
- 独立性:不同进程的工作不相互影响(页表使得不同程序访问不同的地址空间,使进程相互之间不受影响)
- 制约性:因共享数据/资源或进程间同步而产生制约
描述进程的数据结构,Process Control Block,PCB,操作系统为每个进程都维护了一个PCB,用来保存与该进程有关的各种状态信息
进程控制结构
进程控制块:操作系统管理控制进程运行所用的信息集合
使用进程控制块
- 进程的创建:为该进程生成一个PCB
- 进程的终止:回收它的PCB
- 进程的组织管理:通过对PCB的组织管理来实现
PCB的组成方式:链表
进程状态
- 进程创建
- 进程运行,内核选择一个就绪的进程执行
- 进程等待,进程只能自己阻塞自己
- 进程唤醒
- 进程结束
进程的三种基本状态:进程在生命结束前处于且仅处于三种基本状态之一
- 运行状态
- 就绪状态
- 等待状态(又称阻塞状态)
进程挂起
进程没有占内存空间,称为进程挂起,与进程阻塞不同,挂起(suspend)把一个进程从内存转到外存
- 阻塞挂起状态(Blocked-suspend):进程在外存并等待某事件出现
- 就绪挂起状态(Ready-suspend):进程在外存,但只要进入内存,即可运行
状态队列
- 由操作系统来维护一组队列,用来表示系统当中所有进程的当前状态
- 不同的状态分别用不同的队列来表示(就绪队列、各种类型的阻塞队列)
- 每个进程的PCB都根据它的状态加入到相应的队列当中,当一个进程的状态发生变化时,它的PCB从一个状态队列中脱离出来,加入到另外一个队列
线程(Thread)管理
为什么使用线程?
并发且共享地址空间(进程并不能共享地址空间)
线程就是进程当中的一条执行流程
线程=进程共享资源
线程的优点:
- 一个进程中可以同时存在多个线程
- 各个线程之间可以并发执行
- 各个线程之间可以共享地址空间和文件等资源
线程的缺点:
一个线程崩溃,会导致其所属进程的所有线程崩溃
线程的实现
主要有三种线程实现方式:
- 用户线程,其他应用程序管理的线程
- 内核线程,操作系统管理的线程
- 轻量级进程
用户线程机制,不依赖于操作系统的内核,由一组用户级的线程库函数来完成线程的管理
用户线程缺点:
- 如果一个线程发起系统调用而阻塞,则整个进程在等待
- 当一个线程开始运行后,除非它主动交出CPU使用权,否则它所在进程中的其他线程将无法运行
- 由于时间片分给进程,每个线程得到的时间片段较少,执行会较慢
内核线程是指在系统的内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建、终止和管理
轻量级进程(Solaris/linux)
内核支持的用户线程,一个进程可有一个或多个轻量级进程,每个量级进程由一个单独的内核线程来支持
上下文切换
停止运行当前进程并且调度其他进程
上下文:寄存器、CPU状态…
进程控制
- fork,创建一个继承的子进程,复制父进程中的所有变量和内存
- exec,系统调用exec加载新程序取代当前运行程序
对于父进程fork() 返回child PID, 对于子进程返回值为0
fork()的简单实现,完全复制父进程的地址空间,复制操作代价昂贵
- vfork()轻量级fork()
- copy on write(仅复制需要被写的地址空间)
wait()系统调用是被父进程用来等待子进程的结束,帮助释放子进程的PCB等资源
进程结束执行之后,它调用exit()
子进程执行完exit,父进程还未执行wait时,此时子进程处于zombie状态,如果此时父进程先死亡了,子进程就会一直处于zombie状态,一般每隔一段时间root进程会扫描是否存在僵尸状态的进程,如果有,它会代理完成资源回收操作
子进程的状态如下
Chapter 8 调度
上下文切换
- 切换CPU的当前任务,从一个进程/线程到另一个
- 保存当前进程/线程在PCB/TCP中的执行上下文(CPU状态)
- 读取下一个进程/线程的上下文
CPU调度
- 从就绪队列中挑选一个进程/线程作为CPU将要运行的下一个进程/线程
- 调度程序:挑选进程/线程的内核函数(通过一些调度策略)
执行模型:程序在CPU突发和I/O中交替
每个调度决定都是关于在下一个CPU突发时将哪个工作交给CPU
评价指标
-
CPU使用率:CPU处于忙状态所占时间的百分比
-
吞吐量:在单位时间内完成进程的数量
-
周转时间:
-
等待时间:进程在就绪队列中的总时间
-
响应时间:从一个请求被提交到产生第一次响应花费的总时间
-
减少响应时间
-
减少平均响应时间波动
-
增加吞吐量
-
减少等待时间
上述指标是矛盾的 -
低延迟调度增加了交互式表现
-
吞吐量是操作系统的计算带宽
-
响应时间是操作系统的计算延迟
调度算法
- First Come, First Served (FCFS) 先来先服务
- SPN 短任务优先,可以是可抢占或者不可抢占的(SRT最短剩余时间)
- HRRN 最高响应比优先
- Round Robin 让各个进程轮流占用CPU执行
- Multilevel Feedback Queues 任务等待时间越长优先级越来越高,任务执行时间越长优先级时间越来越低
- Fair Share Scheduling,多用户情况下,在用户级别的平等
实时调度:正确性依赖于其时间和功能两方面的一种操作系统
多处理器调度
优先级反转:低优先级任务影响高优先级任务的现象(例如:高优先级任务依赖的低优先级任务被中间优先级的任务抢占的现象)
优先级继承:低优先级任务继承高优先级任务的优先级
Chapter9 同步
独立的线程:不和其他线程贡献资源或状态,具有确定性和可重现性,调度顺序不需要
合作线程:在多个线程中共享状态,不确定性,不可重现
调度带来的问题
上面这种现象称为Race Condition(竞态条件)
系统缺陷:结果依赖于并发执行或者时间的顺序/时间:不确定性、不可重现
Atomic Operation(原子操作)
原子操作是指一次不存在任何中断或者失败的执行,可以避免上述问题
不同的调度策略会出现不同的情况
Critical section(临界区)
临界区是指进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域
Mutual exclusion(互斥)
当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源
Dead lock(死锁)
两个或以上的进程,在相互等待完成特定任务,而最终没法将自身任务进行下去
Starvation(饥饿)
一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行
互斥:同一时间临界区中最多存在一个线程
Progress:如果一个线程想要进入临界区,那么它最终会成功
有限等待:如果一个线程i处于入口区,那么在i的请求被接受之前,其他线程进入临界区的时间是有限制的
无忙等待:如果一个进程在等待进入临界区,那么在它可以进入之前会被挂起
禁用硬件中断:
没有中断,没有上下文切换,因此没有并发,进入临界区,禁用中断,离开临界区,开启中断
但是一旦中断被禁用,线程就无法被停止,无法限制响应中断所需的时间,要谨慎使用
基于软件的解决方案:
- Dekker算法
- Bakery算法
- 复杂
- 需要忙等(待)
- 需要硬件原子的LOAD和STORE方法
更高级的抽象(广泛使用):
基于硬件原子操作的高层抽象实现
硬件提供了一些原语:中断禁用、原子操作等
操作系统提供更高级的变成抽象来简化并行编程,如锁,信号量
锁是一个抽象的数据结构
通过test-and-set或exchange来实现
锁是更高等级的变成抽象
- 互斥可以使用锁来时间
- 通常需要一定等级的硬件支持
常用的三种实现方法
- 禁用中断(仅限于但处理器)
- 软件方法(复杂)
- 原子操作指令(单处理器或多处理器均可)
可选的实现内容:
- 有忙等待
- 无忙等待
Chapter 10 信号量和管程
需要多个进程进入临界区的情况
两种类型信号量:
- 二进制信号量
- 一般/计数信号量
信号量可以用在2个房秒
- 互斥
- 条件同步
例子:生产者和消费者,生产者写数据时,消费者不可以读取数据,但生产者或消费者可以是多个一起访问数据
信号量容易出错
- 使用的信号量已被另一个线程占用
- 忘记释放信号量
管程(Monitor)
目的:分离互斥和条件同步的关注
什么是管程:
-
一个锁:指定临界区
-
0个或者多个条件变量:等待/通知信号量用于管理并发访问共享数据
-
Lock
- Lock::Acquire() - 等待直到锁可用,然后抢占锁
- Lock::Release() - 释放锁,唤醒等待者如果有
-
Condition Variable
- 允许等待状态进入临界区
- Wait() 睡眠(条件得不到满足)
- Signal() 唤醒等待者(条件满足时)
详看视频中的例子
Chapter 11 死锁和进程间通信
死锁问题,进程间相互等待的问题
资源分配图:
资源指向进程:被进程使用
进程指向资源:需要资源
如果途中不包含循环,无死锁
如果图中包括循环,每个资源只有一个实例->死锁,多个实例->可能死锁
死锁的四个必要条件:
- 互斥
- 拥有并等待
- 无抢占
- 循环等待
死锁预防:
- 从互斥下手:共享资源不是必须的,必须占用非共享资源
- 从占用并等待下手:要么不hold要么同时hold所有资源
- 可以抢占
- 对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请(通常用在嵌入型操作系统中)
死锁避免:
死锁避免算法动态检查分配状态,以确保永远不会又一个环形等待状态
系统处于安全状态指:针对所有进程,存在安全序列
避免进入包含死锁的不安全状态
银行家算法:
银行家算法是一个死锁避免的著名算法,银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要
前提条件:
- 多个实例
- 每个实例都必须能最大限度利用资源
- 当一个进程请求一个资源,就不得不等待
- 当一个进程获得所有的资源,就必须在有限时间内释放它们
基于上述前提条件,银行家算法通过尝试寻找允许每个进程获得的最大资源并结束(把资源返还给系统)的进程请求的一个理想执行时序,来决定一个状态是否是安全的
死锁检测和死锁恢复
- 允许系统进入死锁状态
- 死锁检测算法
- 恢复机制
判断资源等待图是否存在环(资源分配图中去掉资源节点)开销比较大(很少实际使用),选择某个进程杀死
IPC(Inter-Process communication)进程间通信
直接通信:通信链路
简介通信:定向从消息队列接收消息
阻塞(blocking):同步(发送完成前不继续进行)
非阻塞(Non-blocking):异步
几个进程间通信机制:
- 信号(Signal):软件level的中断,对进程的通知,仅仅传输一个bit
- 管道:数据交换(A的输出变成B的输入)ls | more
- 消息队列,也是数据传递机制,和管道区别在于没有父子进程关系,传递结构化数据(不仅仅是字符)
- 共享内存
Chapter 12 文件系统
文件系统:一种用于持久性存储的系统抽象
文件:文件系统中一个单元的相关数据在操作系统中的抽象
文件头:在存储元数据中保存了每个文件的属性
文件描述符:操作系统为每个进程维护一个打开文件表,一个打开文件描述符是这个表中的索引
需要元数据来管理打开文件:
- 文件指针:指向最近一次读写位置
- 文件打开计数
- 文件磁盘位置
- 访问权限
用户试图:持久的数据结构
系统访问接口:字节的集合
文件系统中所有操作都是在整个磁盘块空间上进行的
文件以目录的方式组织起来
文件系统的种类
- 磁盘文件系统,FAT NTFS ext2/3/4
- 数据库文件系统,WinFS
- 日志文件系统
- 网络/分布式文件系统,通过网络共享,延迟大
文件系统的实现
虚拟文件系统,屏蔽文件系统差异性,为上层提供统一的接口
数据缓存:基于分页的数据缓存方式,类似页的换入换出机制
文件分配(文件所占空间的分配):
- 连续分配,文件头指定起始块和长度,数据块以数组存在:文件读取表现良好,但存在碎片,文件增长问题
- 链式分配,数据块以链表存在:增大缩小很容易,但不能进行真正的随机访问
- 索引分配,索引数据块,保存索引到文件头中,创建、增大、缩小容易,没有碎片,但文件很小时,存储索引的开销(比例),很大的文件,可能要多个索引块(链式索引块,多级索引块 (使用于大小文件,类似多级页表))
空闲空间列表:
跟踪在存储中所有未分配的数据块
多磁盘管理RAID
通常磁盘通过分区来最大限度减小寻道时间
分区:硬件磁盘的一种适合操作系统指定格式的划分
卷:一个拥有一个文件系统实例的可访问的存储空间
-
使用多个并行磁盘来增加
- 吞吐量(通过并行)
- 可靠性和可用性(通过冗余)
-
RAID 冗余磁盘阵列
并行
冗余
磁盘调度
组织IO顺序减少磁盘访问开销
寻道时间是性能上区别的原因
- FIFO
- 最短寻道时间(饥饿)
- 限制了仅在一个方向上扫描
- N-step-SCAN 将磁盘请求队列分成若干个长度为N的子队列,磁盘调度算法按照FCFS(先来先服务)算法以此处理这些子队列