操作系统复习
- 操作系统复习
- Von Neumann Architecture
- Events
- Processes
- Threads
- Virtual Memory
- Page replacement
- Scheduling
- Synchronization
- When are Resources Shared
- race example
- critical section 临界区
- Critical Section Requirements
- Critical Section properties
- alternation
- Peterson’s Algorithm
- lock
- Semaphores
- Monitor
- Messages and barriers
- UNIX Concurrency Mechanisms
- Linux Kernel Concurrency Mechanism
- Bounded Buffer Problem
- Dining Philosophers Problem
- The Sleeping Barber Problem
- Semaphore Implementation
- resource
- Deadlock
- Resource Allocation Graph
- Dealing with Deadlocks
- File System
- IO
- Block devices
- Character devices
- Device controller
- How to communicate with device controllers
- 专用内存总线 Dedicated Memory Bus
- Techniques for Performing I/O
- Hierarchical Design
- Device drivers
- Device-Independent I/O Software
- IO Buffer
- User-Space I/O Software
- UNIX IO
- The Elevator Scheduler
- Deadline Scheduler
- Anticipatory(预期) Scheduler
- Linux Page Cache
- Windows I/O
- RAID
- Multiple-Level RAID
- Distributed System
操作系统复习
Von Neumann Architecture
input、output、cpu(控制单元CU、算术逻辑单元ALU)、memory
Von Neumann Bottleneck
冯·诺依曼瓶颈是指处理器(CPU)和存储器之间的通信带宽限制,导致处理器速度无法完全发挥,成为计算机系统性能的主要限制因素
Events
- fault故障:同步异常、未预料的
- syscall系统调用:同步异常、故意的
- interrupt中断:异步中断、未预料的
- software interrupt:异步中断、故意的
Processes
- 五状态图、linux图
Process Control Block (PCB)
包含有关进程的所有信息,当进程未运行时,操作系统会保存进程的所有硬件执行状态(PC、SP、regs 等)
上下文切换时,更新PCB,使用下一个进程的PCB
Threads
Kernel vs. User Threads
内核线程调度好,开销大
Preemptive Scheduling
非抢占式线程必须自愿放弃调用yield,而抢占式调度使用定时器中断处理程序强制当前线程调用yield
Virtual Memory
见笔记
TLB快表
虚拟页号映射到物理页号的cache
-
在上下文切换时,管理机制有两种:
硬件管理(x86)/软件管理
Page replacement
NRU
Not Recently Used 最近未使用
工作原理
- 基于两个位标记:
- R 位(Referenced):页面是否被访问过。
- M 位(Modified):页面是否被修改过。
- 页面分类(4 类优先级):
- Class 0:
R=0, M=0
,未被访问或修改的页面(最优先置换)。 - Class 1:
R=0, M=1
,未被访问但已被修改的页面。 - Class 2:
R=1, M=0
,被访问过但未被修改的页面。 - Class 3:
R=1, M=1
,被访问且被修改的页面(最后置换)。
- Class 0:
- 算法流程:
- 定期由操作系统清零所有页面的
R
位(通过时钟中断实现)。 - 在需要置换时,按优先级从 Class 0 到 Class 3 寻找页面。
- 定期由操作系统清零所有页面的
FIFO
Belady’s Anomaly:增加内存页框的数量反而会导致页面置换次数增加
123412512345
SCR Second-Chance
- 页面访问位:
- 每个页面都有一个 R 位。
- 当页面被访问时,硬件将其 R 位置为
1
。
- 置换时的操作:
- 检查当前页框的 R 位:
- 如果 R = 1:将其清零,并将该页面放到队列末尾(相当于“第二次机会”)。
- 如果 R = 0:直接将其替换为新页面。
- 继续扫描,直到找到需要置换的页面。
- 检查当前页框的 R 位:
- 循环队列:
- SCR 通常使用一个循环队列实现,指针(Clock Hand)在页框间循环移动。
clock
- 页面访问位:
- 每个页面都有一个访问位(R 位),当页面被访问时,硬件会将其访问位设置为
1
。
- 每个页面都有一个访问位(R 位),当页面被访问时,硬件会将其访问位设置为
- 时钟指针:
- 页面以环形队列的形式排布,时钟指针从队列的一个位置开始扫描。
- 每次置换时,指针按顺序扫描每个页面,并检查该页面的访问位。
- 置换过程:
- 如果页面的访问位 R = 1,表示该页面最近被访问过,指针将继续移动并清除该页面的访问位(R = 0),然后转到下一个页面。
- 如果页面的访问位 R = 0,表示该页面较长时间未被访问,指针将替换该页面,加载新页面,并将指针指向下一个页面。
- 循环:
- 整个队列会形成一个循环,当指针扫描完一个周期后,重新回到队列的开始位置,继续检查和替换页面。
Least Recently Used (LRU)
- 页面访问记录:
- LRU 算法假设操作系统能够记录每个页面的访问顺序,或者能够确定哪些页面最近被使用过,哪些页面则很久没有使用。
- 置换策略:
- 当内存中没有足够的空间加载新的页面时,LRU 会选择 最久未被访问的页面(即最长时间没有被访问过的页面)进行置换。
- 实现方法:
- 使用链表:维护一个链表,最近访问的页面放在链表头,最久未访问的页面放在链表尾。每次访问一个页面时,将该页面移动到链表头部;当发生页面置换时,直接移除链表尾部的页面。
- 使用栈:通过栈模拟访问顺序,栈顶是最近访问的页面,栈底是最久未访问的页面。
Simulating LRU in Software
-
NFU 使用一个counter跟踪每个页面被引用的频率:对于每个页面,每次时钟中断时,计数器都会添加上 R 位(0 或 1)
问题:它永远不会忘记任何事情
-
Aging: A slight modification to NRU
在添加 R 位之前,每个计数器都会右移 1 位
R 位添加到最左边而不是最右边的位
Thrashing
Thrashing(抖动) 是操作系统中与 虚拟内存管理 相关的一个问题,指的是由于频繁的页面置换导致系统性能急剧下降的现象。当系统的物理内存不足以存放当前运行的进程所需要的页面时,操作系统会不断地从磁盘中加载页面到内存中,而这些页面很快又会被换出,从而导致大量的页面置换。这样,CPU 花费大量时间进行页面交换,而不是执行实际的计算任务,从而导致系统的 吞吐量急剧下降。
working set
-
工作集窗口大小:k
-
工作集:t_k到t之前间隔k次reference,在此之间查询的所有页p
-
工作集大小不是工作集窗口 k
-
工作集大小随程序局部性而变化,希望工作集是进程在内存中需要的页面集,以防止严重缺页
-
增长非线性:locality
-
如果空闲页面数 > 某个挂起进程的工作集,
则激活进程并映射其所有工作集(预分页)
如果某个进程的工作集大小增加且没有空闲页面,
则挂起进程并释放其所有页面 -
算法流程:
R=1,将时间置为当前时间
R=0,不在working set,换出这页
R=0,在working set,记录age最大的换出
WSClock
每个entry包含上次使用时间和引用位R,R=1置为0,R=0找一个entry不在工作集内并且clean,找到就替换,否则如果不在工作集内并且dirty,schedule写到硬盘继续找;最终没找到说明所有页面都在工作集中,那么此时只能选择一个干净的页面来换出,如果不存在干净页面就找一个页面写磁盘,然后换出。
Scheduling
MLFQ
- 初始化:
- 所有新进程通常被放入最高优先级队列。
- 时间片分配:
- 每个队列有不同的时间片长度,高优先级队列的时间片较短,低优先级队列的时间片较长。
- 时间片长度递增可以减少低优先级任务的切换开销。
- 优先级下降:
- 如果一个进程用完了当前队列的时间片且未完成运行,它会被移到更低优先级的队列。
- 优先级提升:
- 长时间未被调度的进程可能会被提升到更高优先级队列,以避免 饥饿(Starvation)。
- 调度规则:
- 优先运行高优先级队列中的进程。
- 如果多个进程在同一个队列中,使用 FCFS(先来先服务) 或 RR(轮转调度)。
多处理器
- master-slave / peer 优缺
- Thread调度:
- Load Sharing 全局的thread队列,负载均匀分布在各个处理器上,无需集中调度程序,但是需要互斥锁和缓存运行状态
- Gang Scheduling 将一个进程中的所有线程同时分配到多个处理器上运行。它特别适用于需要频繁线程间通信的任务。同一任务的所有线程在同一时间被分配到处理器上,避免了线程间通信的延迟。
- Dedicated Processor Assignment 一个处理器处理一个进程所有的threads
Rate Monotonic Scheduling (RMS)
RMS用于周期型任务,周期越短优先级越高,利用率上线趋近ln(2)
EDF
选择当前具有最早截止时间的任务进行调度。
截止时间越近,优先级越高。
LLF
松弛时间越小,优先级越高。
松弛时间计算公式:
\(L_i = D_i - t - C_i\)
其中:
- D_i:任务的截止时间。
- t:当前时间。
- C_i:任务的剩余执行时间。
Synchronization
When are Resources Shared
- Local variables are not shared
- Global variables and static objects are shared
- Dynamic objects and other heap objects (malloc) are shared
race example
foo(){x++;} bar(){x--;}
和多核/单核没有关系,x必须是全局变量
critical section 临界区
如:
ENTER CRITICAL SECTION
Access shared variables; // Critical Section;
LEAVE CRITICAL SECTION
Critical Section Requirements
- Mutual exclusion: 一次只能有一个进程进入
- Progress: 进入临界区的一定会离开,在临界区外的不能阻止进入
- Bounded waiting: 没有饥饿,等待的一定最终会进入
- Performance: 等待和进出的开销较小
Critical Section properties
-
Safety property: nothing bad happens
上面的1
-
Liveness property : something good happens
2/3
-
Performance property
-
Rule of thumb: 优先safety,但也不要忘了liveness
alternation
如:
turn = 1;
func1(){
while(turn == 2);
#critical section
turn=2;
#outside of critical section
}
func2(){
while(turn == 1);
#critical section
turn=1;
#outside of critical section
}
不work,因为只执行一个会无限循环
Peterson’s Algorithm
while (true) {
ready1 = true;
turn = 2;
while (turn == 2 && ready2) ;
#critical section
ready1 = false;
#outside of critical section
}
while (true) {
ready2 = true;
turn = 1;
while (turn == 1 && ready1) ;
#critical section
ready2 = false;
#outside of critical section
}
lock
spinlock
如:
void acquire (lock) {
while (lock->held);
lock->held = 1;
}
-
进程处于忙等,一直while
-
不work,可能两个进程同时退出while,需要原子操作或者关闭中断
-
test-and-set:原子操作,返回旧值并设置为1
void acquire (lock) { while (test-and-set(&lock->held)); }
-
wasteful: 一直占用cpu,解决方法:call thread_yield to give up the CPU/ sleep and wakeup
Disabling Interrupts
void acquire (lock) {
disable interrupts;
}
void release (lock) {
enable interrupts;
}
-
一个处理器,两个进程无法同时关中断
-
多处理器中断独立,不work
-
在临界区内不关中断:
void acquire (lock) { Disable interrupts; while (lock->held) { put current thread on lock Q; Enable interrupts; block current thread; Disable interrupts; } lock->held = 1; Enable interrupts; } void release (lock) { Disable interrupts; if (Q) remove waiting thread; unblock waiting thread; lock->held = 0; Enable interrupts; }
lock的缺点
- Spinlocks – inefficient
- Disabling interrupts – can miss or delay important events, and do not work on multicores.
Semaphores
读者写者模型
可以有多个读者
// number of readers
int readcount = 0;
// mutual exclusion to readcount
Semaphore mutex = 1;
// exclusive writer or reader
Semaphore w_or_r = 1;
writer {
P(w_or_r); // lock out readers
Write;
V(w_or_r); // up for grabs
}
reader {
P(mutex); // lock readcount
readcount ++; // one more reader
if (readcount == 1)
P(w_or_r); // synch w/ writers
V(mutex); // unlock readcount
Read;
P(mutex); // lock readcount
readcount --; // one less reader
if (readcount == 0)
V(w_or_r); // up for grabs
V(mutex); // unlock readcount}
}
问题:starvation,可以写者优先
semaphore的缺点
- 本质上是共享的全局变量,可以在程序中的任何位置访问
- 信号量与信号量控制的数据之间没有联系
- 同时用于临界区(互斥)和协调(调度)
- 无法保证正确使用
Monitor
- monitor封装了共享数据、用这些数据的程序、进程间的同步机制
- monitor里最多只有一个进程
- 和临界区的区别:如果monitor里的进程block了,另一个进程可以进
Condition Variable
-
wait(condition, lock)
释放锁并且等待,直到接收到signal,重新获取锁
说明有等待队列
-
signal(condition) Wake up a thread
-
broadcast(condition) Wake all the thread
-
不是布尔表达式
-
和semaphore的区别:wait会block和释放锁,而P操作只会block;signal信号可能丢失,没有history,而V操作允许未来的进入
两种monitor
-
Hoare monitors:
signal()会立即切换到wait的进程,需要保证一定正确
if (empty) wait(condition);
-
Mesa monitors:
signal()把一个wait的进程放入monitor的ready queue,更小的上下文切换,更好实现广播
while (empty) wait(condition);
读者写者实现:
Monitor RW { int nr = 0, nw = 0; Condition canRead, canWrite; void StartRead () { while (nw != 0) do wait(canRead); nr++; } void EndRead () { nr--; if (nr==0) signal(canWrite) } void StartWrite { while (nr != 0 || nw != 0) do wait(canWrite); nw++; } void EndWrite () { nw--; signal(canWrite); broadcast(canRead); } } // end monitor
Messages and barriers
- message issues:
- Message lost,ack lost
- Order
- Naming
- Authentication
- Barriers:等待所有进程都到barrier处才都继续执行
UNIX Concurrency Mechanisms
-
Pipes
进程间通过 producer-consumer model 通信的特殊的buffer,first-in-first-out queue,分命名和不命名两种
-
Messages
msgsnd和msgrcv的syscall
-
Shared memory
共享的虚拟内存,需由使用的进程提供同步机制
-
Semaphores
semWait/semSignal,有一个信号量上阻塞的进程队列
-
Signals
通过更新进程中的字段,如SIGINT
Linux Kernel Concurrency Mechanism
在UNIX的基础上还有:
-
Atomic Operations
在integer和bitmap上的原子操作,有如add_and_test的操作
-
Spinlock
A spinlock is an integer,循环检查是否为0 (spin)
-
Semaphores
Linux的有
- Binary semaphores
- Counting semaphores
- Reader-writer semaphores
-
Memory Barriers
smp_mb()
: 用于内存屏障,确保前后的所有读写操作顺序。smp_rmb()
: 用于读屏障,确保屏障前的所有读操作先完成。smp_wmb()
: 用于写屏障,确保屏障前的所有写操作先完成。
Bounded Buffer Problem
生产者消费者,buffer内的数量需要在0-n之间
producer {
while (1) {
Produce new resource;
P(empty); // wait for empty buffer
P(mutex); // lock buffer list
Add resource to an empty buffer;
V(mutex); // unlock buffer list
V(full); // note a full buffer
}
}
consumer {
while (1) {
P(full); // wait for a full buffer
P(mutex); // lock buffer list
Remove resource from a full buffer;
V(mutex); // unlock buffer list
V(empty); // note an empty buffer
Consume resource;
}
}
- mutex and full/empty PV交换位置,会死锁
Dining Philosophers Problem
每个人如果先取左边再取右边,会死锁,需要同时取
The Sleeping Barber Problem
如果没有顾客,理发师去睡觉;顾客满座了会走,否则等待;sleep用down(&customers)实现
Semaphore Implementation
P(Semaphore S)
{
PB(mutex);
S.count--;
if(S.count<0) {
VB(mutex);
PB(delay);
}
else
VB(mutex);
}
V(Semaphore S)
{
PB(mutex);
S.count++;
if(S.count<=0) {
VB(delay);
}
VB(mutex);
}
这样实现的问题是先连续多个VB(delay)后,只有一个PB(delay)得到响应,于是改成在PB(delay)后再释放V的mutex
P(Semaphore S)
{
PB(mutex);
S.count--;
if(S.count<0) {
VB(mutex);
PB(delay);
}
VB(mutex);
}
V(Semaphore S)
{
PB(mutex);
S.count++;
if(S.count<=0) {
VB(delay);
}else VB(mutex);
}
问题:效率低,阻塞V进程
Barz’s Solution:在P进程中链式地VB(delay),V只用VB(delay)第一个,count最低为0
P(Semaphore S)
{
PB(delay);
PB(mutex);
S.count--;
if(S.count>0) {
VB(delay);
}
VB(mutex);
}
V(Semaphore S)
{
PB(mutex);
S.count++;
if(S.count==1) {
VB(delay);
}
VB(mutex);
}
还可以在P再开一个barrier互斥锁,让count最多为-1
Kearn’s solution: count正负都可以,count<=0时V操作记录wakecount,用于链式唤醒
P(Semaphore S)
{
PB(mutex);
S.count--;
if(S.count<0) {
VB(mutex);
PB(delay);
wakecount--;
if(wakecount>0)
VB(delay);
}
VB(mutex);
}
V(Semaphore S)
{
PB(mutex);
S.count++;
if(S.count<=0) {
wakecount++;
if(wakecount==1)
VB(delay);
}
VB(mutex);
}
resource
-
reusable: e.g., CPU, memory, disk space, I/O devices, files
acquire → use → release
-
Consumable: produced by a process, needed by a process; e.g., messages, buffers of information, interrupts
create → acquire → use (consumed)
Deadlock
定义:一个进程集合中的进程都在等待集合中的其他进程进行某事件
可能导致死锁的某些情况:
- Mutual exclusion 进程要求独占控制其所需资源
- Hold-and-wait condition 进程持有已分配给它们的资源,同时等待其他资源
- No preemption condition 不能从持有资源的进程中移除资源,直到使用完毕
- Circular wait condition 存在一个循环的进程链,其中每个进程持有一个或多个资源,这些资源由链中的下一个进程请求
Resource Allocation Graph
RAG:进程节点指向申请的资源节点,资源节点指向分配到的进程,有cycle则说明可能有死锁,因为一种资源可能有多个unit
WFG:一种资源只有一个unit,只考虑进程节点构成的图,有环则有死锁
Dealing with Deadlocks
-
Ignore it - 鸵鸟算法,重启系统
-
Deadlock Prevention
避免上面四种情况中断至少一种
-
Two-Phase Locking,第一阶段获得所有需要的锁,第二阶段全部释放,类似一开始就申请所有资源
-
Break Circular Wait Condition
Method 1: 一次只获得一个资源,申请下一个时释放
Method 2:申请资源按升序排序
-
Summary
Mutual Exclusion: 用缓存避免互斥的情况
Hold and wait: 一开始就申请所有资源
No preemption: 虚拟化之后可以抢占(如硬件)
Circular wait: 给资源编号,按顺序申请
-
-
Deadlock Avoidance
Banker’s algorithm:
-
初始化:
系统维护四个关键数据结构:Available、Max、Allocation、Need。 -
请求资源:
当某进程 P 提出资源请求 Request 时:检查 Request <= Need:确保请求不会超过进程的声明需求。
检查 Request <= Available:确保请求不会超过系统当前的可用资源。 如果不满足这两个条件,则拒绝请求。 -
试探性分配:
如果满足条件,系统试探性地分配资源:Available = Available - Request
Allocation[P] = Allocation[P] + Request
Need[P] = Need[P] - Request -
安全性检查:
使用安全性算法验证系统状态是否安全:初始化一个工作向量 Work,等于当前的 Available。
标记所有进程的完成状态为 False。
查找一个满足 Need[i] <= Work 且未完成的进程。
如果找到,模拟进程完成,释放其资源到 Work 并标记为完成。
如果所有进程均能完成,状态为安全。 -
回滚或完成:
如果状态安全,确认分配。
如果状态不安全,回滚分配并拒绝请求。
-
-
Detection and Recovery
Detection: 标记边进行搜索
Recovery:
- 中止所有进程
- 一次中止一个进程,然后再运行一次探测
- 抢占资源,需要选择进程和资源,回滚被抢占的进程,还需要避免饥饿
-
livelock活锁
发生阻塞时所有进程都在释放锁,然后又都申请锁,重复进行无用的操作
解决方法:引入随机量
-
Non-Deadlock Bugs
Atomicity-Violation:串行地内存访问一个地址,但是在中间被另一个线程改动了,解决方法:加锁
Order-Violation:两个内存访问的执行顺序错误,解决方法:条件变量的wait和signal
File System
功能:
- abstraction(files)
- directories
- sharing
- protection
Files
Files: logical units of information created by processes
- Contents, size, owner, last read/write time, protection, etc.
- A file can also have a type
- 可以是一段字节序列,或者一段记录序列,或者记录的树型结构
Directories
对于用户来说,它们提供了一种组织文件的结构化方式,对于文件系统,它们提供了一个方便的命名接口,允许实现将逻辑文件组织与磁盘上的物理文件位置分开
一个directory是<name, location>的一个list, usually unordered
Protection
Capabilities: 对每个主体(alice/bob/...)维护object的权限
ACL Access Control Lists:对每个object(file)维护每个主体的权限
ACL以对象为中心,易于授予、撤销,但是当大量共享时ACL会很大,因此unix使用用户组权限。
文件系统如何使用磁盘存储文件
- 文件系统定义块大小 block size,如4kb
- 需要一个master block决定根目录的位置,始终位于众所周知的磁盘位置,通常在磁盘上复制以确保可靠性
- 需要一个free map确定哪些块是空的,而哪些块是已分配的,通常是bitmap,缓存到内存以提高性能
- 剩下的块是空闲的
File System Layout
太小的文件也会使用一整个block,空的部分属于内部碎片
Contiguous Layout
- 目录存储第一个块的地址,其他的块地址用offset可以算出来
- written之后就难以grow a file
- 删除之后会有gaps,产生外部碎片
Linked Layout
- 每个block link到后一个block
- 随机访问代价变大
indexed Layout
- 索引块存储指向数据块的指针
- 目录指向索引块
- 大文件需要多个索引块
Unix inodes
- inode包含文件元数据File size、 Protection bits、Reference count、Timestamps等
- 然后包含15个块的指针,12个直接块,然后是一级/二级/三级索引指针
- 文件大小最大为48k+4m+4g+4t
Master Block
- 存储在事先定义的固定位置
- 存储指向“/” inode 的指针
Free map blocks
- store a bitmap, one bit per block
总结:Master block 、Bitmap blocks 、Inode blocks 、Data blocks 一共四种block
路径翻译
用master block获得根目录的inode,然后每个目录的inode可以查找目录下文件的块号,最后加载目标文件的inode,获得第一个数据块的地址
软链接 Soft Links
ln –s file alias
Syscall:symlink()
当遇到软链接时,重新进行路径翻译(使用链接到的地址)
实现:将别名作为字符串存储在文件中,并将它的 inode 标记为一个软链接
硬链接Hard Links
ln file alias
Syscall:link()
创建硬链接会添加另一个目录条目,将新名称映射到与旧名称相同的 inode,向 inode 添加一个新指针或链接,inode 中的引用计数也会增加
软链接与硬链接的区别
- 与软链接不同,硬链接使用同一个inode,而软链接使用新的inode
- 软连接可以跨文件系统,硬链接不行
- 硬链接性能更好
- 硬链接不允许链接目录(会造成无限深度访问,父目录不唯一,一个文件多个目录),软链接可以
文件操作
- 创建文件:分配inode,初始化,在目录entry里创建映射,进程写时再分配数据块
- 另一种分配方法:Extent Allocation,extent是一块连续的地址,这样一段地址只需要储存开头和结尾的两个数字,减少文件碎片和元数据开销;示例 FS:ext4、JFS、btrfs、NTFS
- 重命名文件:
- 使用新名称,复制内容,然后删除旧文件
- 或者创建一个新的目录条目,只改变名称,仅修改目录,文件和 inode 保持不变
- 系统调用:rename()
- 删除文件
- 删除目录条目,系统调用unlink()
- 减少 inode 中的引用计数,如果为 0 释放数据块和 inode 块
- 如果文件仍在任何进程中打开,则目录条目会被删除,但文件块不会被删除,直到最后一个进程关闭这个文件
挂载Mouting
比如挂载home文件系统,挂载到根目录下/home,访问/home/username/file会在“根”文件系统中启动路径名转换,在跨挂载点时在“home”文件系统中继续;
大多数情况不可见,但无法跨文件系统进行硬链接
写回缓存
操作系统通常会进行写回缓存,维护未提交块的队列,定期将队列刷新到磁盘(30 秒阈值),以减少 IO 次数
- 如果块在 30 秒内多次更改,则只需要一次 I/O;如果块在 30 秒之前被删除,则不需要 I/O
- 不可靠但实用,崩溃时过去 30 秒内的所有写入都会丢失
- 现代操作系统默认以提升性能
- 系统调用(Unix:fsync)使应用程序能够强制将数据写入磁盘
预读取
FS 预测进程将请求下一个块,这样IO时间可能和计算上一个块的时间重合,提升效率;当进程请求块时,它将位于缓存中
Data Redundancy
A和B部分内容重合,提升可靠性,伤害容量且需要一致性(某些值的组合是非法的)
fsck
文件系统检查和修复
问题:如何修复文件系统映像并不总是显而易见的(不知道“正确”状态)、速度慢
Journaling General Strategy
写入时不删除旧data X,而是先写X的备份(以及redundant f(X)的备份),再把备份写入原本文件,这样任何时候都是good time to crash
FAT (File Allocation Table)
-
核心结构是文件分配表,它位于存储设备的开头,用于记录每个文件的存储位置。
-
存储设备被划分为固定大小的簇(文件存储的基本单位)。
每个簇包含一个或多个扇区(sector)。
FAT 使用簇号在表中定位文件数据。
-
优点:简单高效
-
缺点:碎片化、安全性
NTFS
-
所有文件和目录的元数据都存储在一个特殊文件中(称为 Master File Table,MFT)。
MFT 中包含文件名、文件权限、创建时间、数据位置等信息。MFT可以生长
-
支持日志记录 Journaling 功能,记录文件系统的更改操作。
在系统崩溃后,可以通过日志恢复文件系统的一致性。
-
优点:安全性(efs文件加密,权限),文件压缩、通过日志系统提供可靠性和一致性
-
缺点:开销较大、兼容性
IO
Block devices
块设备将信息存储在固定大小的块中,每个块都有自己的地址,如硬盘
Character devices
字符设备传递或接受字符流,而不考虑任何块结构,如打印机、鼠标、网络接口
Device controller
I/O 单元通常由机械部件和电子部件组成,电子部件称为设备控制器或适配器。
控制器的工作是将串行比特流转换为字节块,并在需要时执行错误更正
每个控制器都有几个寄存器用于与 CPU 通信;许多设备都有一个可以读取和写入的数据缓冲区
How to communicate with device controllers
Memory-Mapped I/O:将所有控制寄存器映射到内存空间,将 I/O 读取视为内存读取
优点:I/O 设备驱动程序可以完全用 C 编写,可以在内存中引用设备寄存器,缺点:需要有选择地禁用缓存
Separate I/O and memory space:需要访问单独的IO接口
专用内存总线 Dedicated Memory Bus
- 单总线
- 高速内存总线:需要区分访存和IO
Techniques for Performing I/O
-
Programmed I/O (PIO) 处理器发出 I/O 命令,可能需要忙等I/O操作完成,不需要中断
-
Interrupt-driven I/O 等待 I/O 时可以释放 CPU,I/O 操作完成时将生成中断,前两种都是用处理器进行I/O
-
DMA:处理器将 I/O 操作委托给 DMA 模块,DMA 模块直接传输数据到或读取内存,完成时 DMA 模块向处理器发送中断信号
流程:CPU委托给DMA、DMA向设备申请、设备向内存传输、设备返回给DMA ack信号、DMA产生中断通知CPU
Integrated DMA & I/O:每个IO设备集成DMA,提升效率
Dedicated I/O Bus:专用IO总线,减少DMA模块数量(否则每个IO设备都有一个DMA)、方便扩展新的IO设备
-
I/O module as a separate processor、I/O module as a computer
Hierarchical Design
IO软件通常分四层,由低到高:
- Interrupt handlers:唤醒驱动
- Device drivers:设置设备的寄存器,检查状态,执行IO
- Device-independent os software:缓存、提供接口、分配空间等其他
- User-level I/O software:用户发出IO请求、Spooling
每层都有明确定义的功能,以及与相邻层明确定义的接口;每层都依赖于下一层来执行更原始的功能
Device drivers
通常由设备制造商编写并随设备一起交付,通常在操作系统内核中运行
Device-Independent I/O Software
为设备驱动程序提供统一接口、报告错误、缓存、分配和释放专用设备、提供块大小
IO Buffer
-
Block-oriented Device:输入传输到缓冲区,提前读取
-
Stream-oriented Single Buffer:如终端、鼠标
-
Double Buffer:
当一个缓冲区(Buffer A)正在被数据生产者填充时,另一个缓冲区(Buffer B)可以被数据消费者处理。
一旦 Buffer A 填满,生产者切换到 Buffer B;同时消费者开始处理 Buffer A 的数据。
双缓冲实现了数据生产者和消费者的并行处理,减少了等待时间。
如果进程执行快速突发的 I/O,则双缓冲可能不够
-
Circular Buffer:多个buffer
-
buffer可平滑 I/O 需求的峰值、可以提高操作系统的效率和各个进程的性能
User-Space I/O Software
系统调用、输入和输出的格式化(printf)
- Spooling:用户进程在进行IO请求时将请求放在缓冲区中,由 daemon 进程决定何时进行请求,可以提高效率和实现设备共享
UNIX IO
Unbuffered I/O
- 由DMA执行,最快
- 进程被锁定在主内存中,无法交换出去
- 设备与进程绑定,其他进程无法使用
Buffer Cache
本质上是磁盘缓存
Character Queue
只能读取一次,鼠标等设备
The Elevator Scheduler
用于改进磁盘臂disk arm调度
- 维护磁盘读写请求的优先队列,按块号排序
- 驱动器可朝一个方向移动以满足每个请求
Deadline Scheduler
有到期时间,到期后从FIFO队列调度,否则从排好序的队列调度
Anticipatory(预期) Scheduler
如果一个进程频繁执行小块的顺序读操作,上面的算法可能会造成性能下降
预期调度器会延迟几毫秒,假设同一个进程可能会发出后续的相关 I/O 请求。
- 如果在等待时间内,来自同一进程的请求到达,则优先处理该请求。
- 否则调度器继续服务其他进程的请求。
Linux Page Cache
使用统一虚拟页缓存和I/O缓存的好处:方便dirty page的写回
Windows I/O
Windows I/O 管理器负责操作系统的所有 I/O ,提供所有类型的驱动程序都可以调用的统一接口,与缓存管理器、各种驱动紧密合作
提供同步和不同步的IO
RAID
独立磁盘冗余阵列,冗余磁盘容量用于存储奇偶校验信息,从而提供磁盘故障的可恢复性
RAID 0
- 无冗余,将数据分块,并交替存储在多个磁盘上。
- 性能高:多个磁盘并行读写,速度提升明显。
- 单盘故障导致数据丢失,仅适合高速存储需求。
至少需要2个磁盘
RAID 1
镜像存储,提供冗余,数据安全性高,但磁盘利用率低(仅 50%)。
至少需要2个磁盘
RAID 2
将数据按位分割,并存储在多个磁盘上,同时使用专用磁盘存储纠错码(基于海明码)。复杂性高,额外需要多个校验磁盘。
RAID 3: 字节级条带化 + 专用校验
将数据按字节分割,分布在多个磁盘上,同时使用一个专用磁盘存储校验信息(通过异或计算生成)。(n+1)
RAID 4: 块级条带化 + 专用校验
与 RAID 3 类似,但将数据按块分割,使用一个专用磁盘存储校验信息。
RAID 5: 分布式校验
将数据和校验信息分布在所有磁盘上,没有专用校验磁盘。每个磁盘既存储数据块,也存储校验块。(round-robin)
至少需要3个磁盘
RAID 6: 双重分布式校验
类似 RAID 5,但存储两组校验信息,能容忍最多两个磁盘同时故障。
至少需要4个磁盘
Multiple-Level RAID
10和01需要4个,50需要6个
10是stripe of mirror,先mirror再stripe
Distributed System
多个节点可以同时运行任务,提高系统的吞吐量和效率。即使部分节点发生故障,系统仍然可以继续运行。
RPC
RPC(远程过程调用)是一种分布式计算的通信机制,允许程序调用位于远程节点上的函数,就像调用本地函数一样。它屏蔽了网络通信的细节。
基本流程:
- 客户端发起调用:
- 客户端程序调用一个“本地代理函数”(称为 stub)。
- 序列化:
- 参数被序列化(即编组,marshalling),并通过网络传输到远程服务器。
- 服务器处理:
- 服务器解码参数,执行相应的远程函数,并返回结果。
- 结果传递:
- 服务器将结果序列化后传回客户端,客户端反序列化后得到结果。
RPC 的优点:
- 隐藏了底层网络通信的复杂性。
- 简化了分布式程序的开发。
RPC 的缺点:
- 性能较低:相比于本地函数调用,RPC 调用涉及网络延迟。
- 出现失败的可能性更高:网络故障、服务器崩溃等问题。
2. Stub Compiler (桩编译器)
RPC 的实现依赖于 stub(桩)。Stub 是用于客户端和服务器之间通信的代理程序。
Stub 的作用:
- 客户端 stub
- 模拟远程过程,负责参数的编组(序列化)和发送。
- 服务器 stub
- 接收并解码参数,调用实际的远程函数。
Stub Compiler 的功能:
- 自动生成客户端和服务器的 stub。
- 屏蔽开发者与网络通信相关的低层细节。
- 确保参数和返回值的正确编组和解组。
Network File System (NFS)
NFS(网络文件系统)是分布式文件系统的一种,由 Sun Microsystems 开发,允许用户通过网络访问远程主机上的文件,就像访问本地文件一样。
Statelessness (无状态性)
NFS 的设计中采用了无状态模型,这意味着服务器不维护客户端的状态信息。
无状态性的特点:
- 每次请求独立
- 每次客户端请求都携带完成请求所需的所有信息,例如文件句柄、偏移量等。
- 不依赖历史信息
- 服务器不会记录客户端上一次的请求信息,因此即使服务器重启,客户端仍然可以继续操作。
优点:
- 简化了服务器设计:无需维护客户端状态。
- 提高了可靠性:服务器重启后可以立即恢复服务。
缺点:
- 操作复杂性增加:无状态性要求客户端每次都发送完整信息,增加了网络负担。
- 缺乏事务支持:无状态模型难以保证复杂操作的原子性。