目录
1 概述
《Operating System —— Three Easy Pieces》,相对简单的操作系统入门书,讲述3个方面:
1)虚拟化:虚拟CPU,单个CPU使用像在运行多个CPU;虚拟存储器,各个程序像有私有的内存空间。
2)并发:并发需要处理的问题。
3)持久化:文件存储。
操作系统的设计目标:
1)抽象:方便使用,不需关注太多细节。
2)高性能
3)提供保护
2 虚拟化
2.1 虚拟CPU
目标:同时运行多个程序,造成多个CPU可用的假象。
方法:时分共享,一个进程运行一定的时间,然后切换给另一个进程。
2.1.1 进程
(1)抽象
进程:正在执行中的程序
创建进程,操作系统要为进程分配内存(分段、分页)、堆(malloc、free)、栈(局部变量、函数参数、返回地址等)等。
进程3种基本状态:
其他状态:
初始状态:表示进程在创建时处于的状态
最终状态:已退出但尚未清理
操作系统调度进程,会进行上下文切换,为追踪进程,通过进程列表描述。例如struct proc。
(2)API
fork:复制当前进程,
返回值:
父进程——子进程PID
子进程——0
wait:使用fork后,父进程调用wait,等待子进程结束
exec:新进程替换当前进程。
fork、exec设计原因:例如,shell可在fork之后,exec之前运行,进行某些操作。
wc p3.c > newfile.txt
shell调用exec前,关闭标准输入,打开newfile.ext,之后将wc结果输入到该文件
2.1.2 Limited Direct Execution(受限直接执行)
虚拟化考虑问题:
1)性能
2)控制权:有效运行进程,保留操作系统对CPU的控制权
受限直接执行思想:直接创建进程,运行程序
存在问题:
1)问题:没有限制,用户可能随意操作。
用户模式—>trap指令—>内核模式,限制用户行为
2)问题2:trap如何知道OS内运行哪些代码
异常向量表
3)问题:进程切换
两种方式:
协作方式:等待系统调用
非协作方式:由操作系统控制,时钟中断
4)并发
禁止中断:可能导致丢失中断
加锁
2.1.3 进程调度
2.1.3.1 简介
(1)评价指标
周转时间=完成时间-到达时间
响应时间=首次运行时间-到达时间
(2)策略
策略 | 内容 | 优缺点 |
---|---|---|
FIFO | 先到先执行 | 护航效应,耗时长的任务占据大量时间,耗时短的得不到执行 |
SJF | 最短任务优先 | 护航效应 |
STCF | 最短完成时间优先,添加抢占 | 响应时间差 |
RR | 轮转,分配时间片 | 优化响应时间,周转时间差 |
IO问题:一个程序在执行IO操作时,可能耗时较长,此时CPU应调度其他任务。
2.1.3.2 多级反馈队列MLFQ
(1)目标:
1)优化周转时间
2)降低响应时间
(2)基本流程
有多个不同优先级的队列,优先执行较高优先级队列,例
(3)基本规则
1)如果A优先级>B优先级,运行A
2)如果A优先级=B优先级,轮转A、B
3)进入系统,首先放在最高优先级
4)一旦进程用完了某一层中的时间配额,降低优先级
5)经过一段时间S,系统所有进程重新加入最高优先级
2.1.3.3 比例份额
目标:确保每个工作获得一定比例的CPU时间
(1)彩票调度
彩票数代表了进程占据的比例。
(2)步长调度
通过一个大数除以票数,得到每个进程的步长
未被广泛应用:不能很好适应IO;票数分配难确定
2.1.3.4 多处理器调度
(1)多处理器架构
缓存:基于时间局部性和空间局部性
问题:
1)缓存一致性——总线探针,缓存一致性协议
2)同步——加锁
3)缓存亲和度:进程在某个CPU上运行,下次再同一CPU上运行,因缓存中的数据可执行更快。
(2)单队列调度
将原有策略用于多个CPU
问题:
1)缺乏可扩展性:加锁确保原子性
2)缓存亲和性
(3)多队列调度
包含多个调度队列,每个CPU调度之间相互独立。例如
问题:负载不均
解决:不断迁移
2.1.3.5 其他
linux调度:O(1)调度、完全公平调度CFS、BF调度
2.2 虚拟存储器
2.2.1 抽象:地址空间
地址空间
虚拟存储器,为每个进程构建一个私有的、可能很大的地址空间的抽象。目标:
1)透明:程序像拥有自己的地址空间
2)效率:时间上处理较快,空间上不需要太多额外的内存支持虚拟化
3)保护
2.2.2 机制:地址转换
基址加界限机制:地址转换,每次对内存的访问,将虚拟地址转换成物理地址。CPU需要有基址寄存器和界限寄存器。
physical address = virtual address + base
问题:
1)进程创建时,要能找到空闲的内存
2)进程终止时,回收内存
3)上下文切换时,保存状态
4)提供异常处理,例如程序越界访问内存,引发异常。
5)内部碎片:从图中看到,有大量未使用的空间
2.2.3 分段
做法:每个逻辑段一对基址界限寄存器
问题:外部碎片
解决:空闲列表
2.3.4 分页
(1)基本方法
按照固定长度将内存分片,如
物理帧号PFN,虚拟页号VPN,页表项PTE。转换
问题:
1)页表存储在内存中,查找较慢——TLB
2)页表太大——多级页表
(2)TLB
将频繁发生的地址转换放在硬件cache中。
问题:
1)上下文切换时,cache处理:添加地址空间标识符,标识不同进程的使用
2)替换策略:最近最少使用LRU、随机策略
(3)多级页表
将页表分成页大小,如果整页的页表项无效,完全不分配该页的页表。
2.3.5 页面交换
交换空间:在磁盘上开辟空间,用于从内存移出的页面
策略 | 内容 | 评价 |
---|---|---|
最优替换策略 | 替换掉最远的将来才会访问的项目 | 难以实现 |
FIFO | 替换掉最先进入的项目 | 命中率低 |
随机 | 随机替换掉一个项目 | 命中率随机 |
最近最少使用LRU | 替换掉最长时间没有被使用的项目 | 命中效果较好,实现成本高 |
近似LRU | 硬件设置使用位,系统定期清除,替换没有标记的页 | 实现成本低 |
3 并发
3.1 简介
(1)线程
进程 | 线程 | |
---|---|---|
地址空间 | 相互独立 | 同一进程的线程,共享地址空间 |
状态 | 进程控制块PCB | 线程控制块TCB |
多线程栈
(2)多线程问题
共享数据——加锁
执行顺序——条件变量
术语:
临界区:访问共享资源的代码
竞态条件:多个执行线程同时进入临界区
3.2 锁
(1)基本用法
lock_t mutex;
...
lock(&mutex);
临界区
unlock(&mutex);
(2)如何评价锁效果
1)完成基本任务
2)公平性:保证每个线程有公平的机会抢到锁
3)性能:只有一个锁时的开支;多个锁竞争时的开支
(3)实现方法
1)控制中断
void lock() {
disableInterrupts();
}
void unlock() {
enableInterrupts();
}
问题:调用线程要有特权操作;不支持多处理器;中断丢失;
2)硬件支持
硬件支持原子执行,例如test-and-set、compare-and-swap、fetch-and-add
int TestAndSet(int *old_ptr, int new) {
int old = *old_ptr;
*old_ptr = new;
return old;
}
锁实现
typedef struct __lock_t {
int flag;
} lock_t;
void init(lock_t *lock) {
lock->flag = 0;
}
void lock(lock_t *lock) {
while (TestAndSet(&lock->flag, 1) == 1)
; // spin-wait (do nothing)
}
void unlock(lock_t *lock) {
lock->flag = 0;
}
评价:线程自旋浪费时间,只有fetch-and-add保证所有线程都能抢到锁
3)解决自旋问题
a 自旋时,放弃CPU
上下文切换成本还是高
b 使用队列:休眠代替自旋
3.3 条件变量和信号量
区别:
1)条件变量支持广播方式唤醒等待者,而信号机制不支持
2)条件变量是无状态的,如果唤醒早于等待,则唤醒会丢失;信号机制是有状态的,可以记录唤醒的次数
3)条件变量一般配合互斥量一起使用,例
int done = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;
void thr_exit() {
Pthread_mutex_lock(&m);
done = 1;
Pthread_cond_signal(&c);
Pthread_mutex_unlock(&m);
}
void *child(void *arg) {
printf("child\n");
thr_exit();
return NULL;
}
void thr_join() {
Pthread_mutex_lock(&m);
while (done == 0)
Pthread_cond_wait(&c, &m);
Pthread_mutex_unlock(&m);
}
int main(int argc, char *argv[]) {
printf("parent: begin\n");
pthread_t p;
Pthread_create(&p, NULL, child, NULL);
thr_join();
printf("parent: end\n");
return 0;
}
3.4 常见并发问题
(1)非死锁缺陷
违反原子性
违法顺序:访问顺序被打乱
(2)死锁缺陷
条件:
1)互斥:使用互斥锁,抢到资源的线程会阻止其他线程
2)拥有并等待:线程有部分资源,但又在等待其他资源
3)非抢占:已经获得的资源不能被抢占
4)循环等待:存在一个环路,环路上每个线程拥有下一线程等待的某种资源
预防:
1)顺序抢锁:所有线程都根据某种顺序获得锁
2)原子抢锁:当一个线程开始抢锁,它会不被打断地运行至抢锁完成
3)放弃已有锁:如果不能抢到所有需要的锁,那么就放弃已经抢到的锁,但是会产生活锁问题,即线程间一直互相测试并放弃锁
4)使用无等待数据结构。
4 持久化
4.1 磁盘驱动器
(1)模型
将磁盘看做由多个扇区组成,每个扇区大小512字节,图示
基本的IO流程:寻道–>等待转动到对应位置–>传输。在此过程中的延时:
单磁道延时:旋转延时
多磁道延时:寻道时间
(2)调度
1)最短寻道时间优先,问题:饥饿
2)电梯调度
3)最短定位时间优先:比较旋转和寻道时间
4.2 廉价冗余磁盘阵列RAID
基本思想:将多个容量较小、相对廉价的磁盘进行有机组合,从而以较低的成本获得与昂贵大容量磁盘相当的容量、性能、可靠性。
3个重要等级:RAID0(条带化)、RAID1(镜像)、RAID4/5(基于就校验的冗余)
…