OS 操作系统
Chapter3 操作系统结构
复杂度管理方法 M.A.L.H
Modularity: 模块化,分而治之
Abstraction: 抽象,接口与实现分离,遵循宽进严出原则。例如虚拟内存、文件系统
对于大型系统,只有模块化和抽象,可能导致划分模块太多,交互关系复杂,因此还需要引入分层和层次结构控制复杂度。
Layering: 分层,每个层级是一套完整机制。通常一个模块只能与本层和上下层交互,不能跨层。例如OSI、TCP/IP
Hierarchy: 层次结构,大的子系统由多个小的子系统组织成。即同级模块的分层
宽进严出原则:容忍各种输入(包括恶意输入),严格控制模块的对外输出
微内核
宏内核架构:单点bug使整个系统崩溃。
微内核:解耦单个功能/模块(如文件系统、设备驱动)作为独立服务隔离运行,使内核成为一个最小功能集。
微内核架构服务隔离,单点出问题系统不会崩溃
内核态部分,称为
微内核优势:
- 弹性硬件拓展能力
- 硬件异构实现
- 功能安全
- 信息安全
- 时延确定
现代操作系统特征:1)虚拟内存;2)用户态、内核态隔离。
抽象的缺点
- 过度抽象会损失性能(原则:abstract but don’t hide power)
- 通用的抽象对数据库、Web服务器来说不是最优抽象。(软件比操作系统更了解如何使用抽象来使用硬件资源)
外核 Exokernel
外核将硬件资源抽象为LibOS,按需最小化调用
外核的缺点:LibOS针对应用定制,不通用、复用差(增大内存开销)
开源的缺点:开放设备驱动源码会透露实现细节
策略与机制
重要的操作系统设计原则:分离 policy 与 mechanism
policy:做什么。就像房子。例如什么用户、什么权限
mechanism:怎么做。就像盖房子的砖块。例如输入处理、桌面加载
Chapter4 内存管理
虚拟内存
两种内存管理
- 一个程序占用全部内存。程序切换时,将内存的数据写入硬盘(写入慢)
- 程序共享内存。无需频繁写入硬盘(问题:隔离性、地址不连续)
方法2更好,于是催生了虚拟内存这个中间层来解决隔离性和地址不连续的问题。
有了虚拟内存技术,程序连续地存储在硬盘里,通过页表映射的方式高效使用内存。
内存分段与分页
分段:早期内存划分成大小不一样的段。缺点是容易产生内存碎片,不能充分利用内存资源。
分页:按规格划分内存和硬盘,任意虚拟页可以被映射到物理页。
多级页表
如果每个程序只维护1张页表,那么每张页表的大小将会是3千万GB!
单级页表每一项都要存在:因为整个页表必须连续,没有用到的页也预留着。
TLB Translation Buffer
TLB缓存了虚拟页与物理页的映射关系。
其中,每项TLB有一个标签(ASID/PCID)来区别不同的程序,切换程序时只需要刷新那个程序的页表,无需全部刷新。
页表切换时,操作系统需要主动刷新TLB。
换页与缺页
打开PS,占用2G内存;打开游戏,占用3G内存。一共占用5G内存,却可以在总共4G的电脑上运行。
换页:物理内存容量不够,将物理页写入磁盘,并回收资源。
缺页异常:内存找不到页,从磁盘找到对应页,写入内存。
预取:预测即将被访问的页,提前换入内存,减少不命中。
按需页分配:程序申请内存,只标记不换入,等到缺页异常再换入,提高利用率。
工作集模型
将程序使用的内存页集合共同保持在物理页中,避免颠簸,优先换出非工作集的物理页。
原理:时钟算法。通过定时比较当前内存页与上次检测结果的访问位,判断出哪些内存是当前工作集。
写时拷贝
对于两个程序共用的物理页,只在内存保留1份数据,只读地映射到页表。当其中一个程序对共享部分修改时,再拷贝一份可读写的数据(由异常处理完成操作)来完成修改。
内存去重
操作系统定期扫描内存中的相同内容,只保留其中一份。缺点是修改时需要写时拷贝,加大了时延。
针对内存去重的攻击:伪造数据,如果被去重了(表现为时延变长)就说明内存中存在相同的数据。
大页
TLB中翻译每个内存页要占用1个TLB缓存项。使用大页来解决缓存不够用的问题,提高命中率。大页的大小可以是2MB到1GB(普通页仅为4KB)。
物理内存分配与管理
内存碎片:无法被利用的内存。
外部碎片:有足够的内存却不是连续的,无法被分配使用。解决方法是按规格切分并分配内存。
内部碎片:切分并分配内存后(解决外部碎片),每块内存都没有被充分使用。
伙伴系统
物理内存分块,每块由个(1,2,4,8)物理页构成。2块相同大小的块为伙伴,可以合并成大一号的块;大块也可以分裂出2个小一号的伙伴块。
伙伴系统:一个块被释放后,自动寻找伙伴块向上合并成大块,直到没有伙伴块为止。
SLAB分配器
伙伴系统最小分配4KB,然而许多服务只需要几十、几百B,SLAB用于分配这些小内存。
SLAB后来被改进成SLUB;还有一个最小开销、用于嵌入式的SLOB,这三者共称“SLAB分配器”。
SLUB:2^n Byte(3\leq n \textless 12) 的伙伴系统。有2个指针,partial指向一块空闲内存slab,current指向slab中的空闲块。
软件着色(软件方案)
对放到不同位置的物理页标记不同颜色。对于连续虚拟内存,优先分别到不同颜色的位置,避免不命中。
Chapter5 进程与线程
进程:每个进程对应一个运行中的程序。
有了进程的抽象,应用程序再运行时仿佛独占了整个CPU。
上下文切换:通过保存/回复进程的运行状态(上下文),使进程可以暂停、切换、恢复,实现CPU资源共享。
操作系统为每个进程提供独立虚拟地址空间(页表)。
线程:更轻量的执行单元,解决进程间数据不易共享、通信开销问题。
纤程:允许上下文在用户态而非内核态切换,再次减小开销。
进程
名称 | 状态 |
---|---|
new | 进程被创建,未初始化,不能调度 |
ready | 进程初始化完成,未被调度器选择 |
running | 进程被调度器选择,在CPU上运行 |
blocked | 进程需要等待外部事件,暂时无法被调度 |
terminated | 进程完成执行,不会再被调度 |
进程内存空间布局
内核 | 最顶端 |
---|---|
用户栈 | 自顶向下拓展 |
代码库 | 共享,只读 |
用户堆 | 自底向上拓展 |
数据、代码段 | 静态 |
cat /proc/PID/maps
可以查看某个进程的空间布局
进程控制块与上下文切换
进程通过一个数据结构来保存它的相关状态(PID、进程状态、VM状态、打开的文件…)。
这个数据结构称为PCB(Process Control Block)
上下文切换:将前一个进程的寄存器状态保存到PCB,将下一个进程之前保存的状态写入寄存器。
fork
fork创建一个与父进程一样子进程(PID与VM不同)。
Chapter6 操作系统调度
调度策略
-
FCFS,First Come First Served
先到先服务。用时短的服务需要等待,用户体验不好。 -
SFJ, Shortest Job First
最短运行时间先服务。依赖到达时间,如果长时间任务先到,仍然是长时间任务占用系统。 -
STCF,Shortest Time-to Completion First
最短剩余时间先服务。容易饿死长时间服务。 -
RR,Round Robin
时间片循环服务。平均把时间放在所有任务上。 -
MLQ,Multi-Level Queue
多级队列,高优先级的队列执行完后,才执行低优先级队列。同级别的队列,随机选择。同样,低优先级会饿死。而且一个任务锁定内存时,高优先级实质上执行不了。 -
MLFQ,Multi-Level Feedback Queue
多级队列加上反馈。假设所有任务是短时间任务,优先执行,超过一定时间后降级,处理其他事务。定时刷新,重设任务为高优先级,防止饿死。
上面的所有调度方案,都是单用户下的调度;对多用户的调度和分配,我们需要引入份额。
-
彩票调度
生成随机数,例如生成32,则份额为10的不会执行,份额为20-40的会执行。
这个方法显然不是完全按概率调度的。 -
彩票货币
A任务份额10,B任务份额5。在实际执行过程中,B可能会锁死A需要的内存,这时就需要A转让份额,让B快点执行完。
通过在份额上,再增加一层抽象货币,即A分配1/3的份额给B时,转换成A给B100货币,更容易计算。 -
步幅调度
步幅调度设置虚拟时间T,严格按照1:3的分配执行两个任务。