Modern Operating System (I)
前几章内容暂省略
- 内存管理
- 进程与线程
- 操作系统调度
- 进程间通信
4 内存管理
- 转址旁路缓存
TLB (Translation Lookaside Buffer)
- TLB命中:
TLB hit
- TLB未命中:
TLB miss
- TLB命中:
AArch64 (based on ARMv8.0)
架构:4级页表- 最小页:有
4KB
,16KB
,64KB
,可通过TCR_EL1
进行配置
- 最小页:有
- 一种TLB刷新机制:为
TLB
缓存项打”标签”来减少程序切换时TLB刷新的开销ASID (Address Space IDentifier)
:基于aarch64
PCID (Process Context IDentifier)
:基于x86-64
- 换页
page swapping
- 换出
swap out
- 换入
swap in
- 换出
- 缺页异常
page fault
- 预取
prefetching
机制:发生swap in
时,预测还有哪些页即将被访问,提前将其换入物理内存,减少page fault
- 按需页分配
demand paging
- 未分配状态
- 已分配但未映射至物理内存状态(初次访问时出发
page fault
) - 区分两种状态:
- Linux中,程序的虚拟内存空间由多个虚拟内存区域构成,即
VMA (Virtual Memory Area)
:含beginning VA
,ending VA
,访问权限等信息。 - P -> page fault
- P属于VMA:已分配未映射
- P不属于VMA:未分配
- Linux中,程序的虚拟内存空间由多个虚拟内存区域构成,即
- 页替换策略
MIN/OPT (Minimum / Optimal 最优策略)
FIFO (First-In First-Out)
Second Chance
LRU (Least Recently Used)
:基于程序过去频繁访问的页后续也可能被访问MRU (Most Recently Used)
:基于程序不会反复访问相同地址情况clock algorithm
:时钟算法策略
- 工作集模型
working set model
- 基于:优先将非工作集中的页换出
- 工作集时钟算法:
- 维护2个状态:上次使用时间和访问位
- 定期检查算时间间隔
- 共享内存
shared memory
- 写时拷贝
copy-on-write
- 内存去重
memory deduplication
- 定期扫描相同内容的物理页,找到映射对应的虚拟页,保留一个物理页,其余通过
copy-on-write
映射到该物理页,释放其他物理页。 - 如Linux的
KSM (Kernel Same-page Merging)
机制 - 性能下降产生延迟,资源利用率提高
- 定期扫描相同内容的物理页,找到映射对应的虚拟页,保留一个物理页,其余通过
- 写时拷贝
- 内存压缩
- 如Linux的
zswap
机制,zswap
区域(仍属于内存区域):为内存换页过程提供缓冲区 - 更加高效的磁盘批量
I/O
- 如Linux的
- 大页
huge page
- Linux提供透明大页
transparent huge page
机制:自动将一个应用程序中连续的4KB内存页合并成2MB的内存页 - 提高效率(减少TLB项,提高TLB命中率),可能浪费物理内存资源
- Linux提供透明大页
- 内存碎片
fragmentation
- 内部碎片
internal fragmentation
- 外部碎片
external fragmentation
- 内部碎片
- 伙伴系统
buddy system
- 减少外部碎片
SLAB
分配器- 减少内部碎片
- 包括
SLAB
,SLUB
,SLOB
- linux-2.6.23之后内核默认采用
SLUB
分配器(依赖于伙伴系统)
- 常用空闲链表
- 隐式空闲链表
implicit free list
- 显式空闲链表
explicit free list
- 分离空闲链表
segregated free list
- 隐式空闲链表
5 进程与线程
process
进程控制块
PCB (Process Control Block)
僵尸进程
zombie
进程管理:
进程组
process group
:进程的集合 —> 进程组标识符tgid (In Linux PCB)
会话
session
:进程组的集合根据执行状态划分为:
- 前台进程组
foreground thread group
- 后台进程组
background thread group
- 前台进程组
process group
和session
主要用于shell
环境的中的进程管理- 进程id ->
PID
- 进程组id ->
GID
- 会话id ->
SID
- 进程id ->
控制中断进程
controlling terminal
thread
用户态线程
user-level thread
内核态线程
kernel-level thread
多线程模型
multithreading model
线程控制块
Thread Control Block
NPTL
Native POSIX Thread Library
:自Linux 2.6
引入的线程库,沿用至今
fiber
完全在用户态下完成
- 抢占式多任务处理
preemptive multitasking
- 合作式多任务处理
cooperative multitasking
(Windows) TLS(Fiber Local Storage)
:纤程本地存储,用来为每个纤程保存同一变量的私有拷贝- 协程
coroutine
:为了便于与操作系统提供的纤程支持相区分,程序语言提供的纤程支持称为协程
6 操作系统调度
优先级
priority
时间片
time slice
截止时间
deadline
任务调度/CPU调度
task scheduling
线程
thread
是调度器的调度对象,又叫做任务task
- 运行队列
run queue
Linux
使用的调度器会用红黑树实现运行队列
- 运行队列
确定调度指标
what
——> 做出调度决策how
调度指标
分类
- 性能相关指标
- 批处理任务
- 吞吐量:单位时间内处理的任务数量
- 周转时间:任务从被发起直到执行结束所需的时间
- 交互式任务
- 响应时间:任务从被发起直至第一次向用户返回输出以响应用户所需的时间
- 调度开销:做出决策的时间应该尽可能短
- 批处理任务
- 非性能指标:公平性、资源利用率
- 任务场景需求:实时性、能耗
- 性能相关指标
权衡
- 调度开销和调度效果
- 优先级和公平性
- 性能与能耗
调度机制
进程调度
process scheduling
机制长期调度
long-term scheduling/job scheduling
限制系统中真正被用来进行短期调度管理的进程数量,避免短期调度的开销过大
根据进程使用的资源类型,分类:计算密集型
CPU-bound
和I/O
密集型I/O-bound
短期调度
short-term scheduling/job scheduling
实际做出调度决策的机制
负责进程在 预备,运行,阻塞 状态间的转换
中期调度
medium-term scheduling/job scheduling
长短期调度 —> based on CPU and I/O resource
中期调度 —> based on memory
中期调度也是换页机制的一部分,当系统中内存使用量过大,就会触发换页机制,挂起处于预备状态的/阻碍状态的进程,置为挂起预备状态/挂起阻塞状态并放入挂起运行队列/挂起阻塞队列
引入无法被短期调度执行的两个状态
- 挂起预备状态
suspended ready
- 挂起阻塞状态
suspended blocking
- 挂起预备状态
批处理队列
batch queue
阻塞队列
blocking queue
调度决策
7 进程间通信
进程间通信
Inter-Process Communication (IPC)
消息
message
:一种常见通信数据的抽象,包含一个头部header
和一个数据段payload
header
: 魔数magic number
,状态state
,消息长度message length
,验证码verification code
等payload
: 纯数据(如字符串string
),系统资源(如文件描述符file descriptor
)
消息传递基本接口:
Send(message)
Recv(message)
RPC(req_message, resp_message)
—> 远程过程调用:Remote Procedure Call
Reply(resp_message)
内存重映射
memory remapping
IPC Classification
- 单向(管道,信号等)和双向(RPC)
- 同步(阻塞进程直到该IPC操作完成)和异步(非阻塞的,进程发起一次操作即可返回,不需要等待其完成)
同步:被调用者
callee
处理请求时,调用者caller
会处于block
的状态,当callee
执行完任务后,控制流会切回caller
- 一般是双向
IPC
(或RPC
)
- 一般是双向
异步:多个并行的控制流,异步
IPC
通常通过【轮询内存状态】或【注册回调函数】(如果kernel
支持的话)来获取返回结果。
超时机制
- 拒绝服务
denial-of-service
- 拒绝服务
通信连接管理
- 直接通信:通信的
process
一方需要显示地标识另一方 - 间接通信:通过一个中间信箱完成通信,每一个信箱有一个唯一的标识符,共享信箱交换
message
,进程间连接的建立发生在共享一个信箱时候。(如管道)
- 直接通信:通信的
权限检查机制
- 微内核,如
seL4
等微内核系统的Capability
机制 - 宏内核,如
Linux
, 将【IPC安全检查机制】和【文件检查】结合在一起
- 微内核,如
System V
进程间通信:即【宏内核】下的三种进程间通信机制:System V
消息队列System V
信号量System V
共享内存
System V
通信的权限管理:在Linux
等宏内核中是基于文件File
的权限检查机制访问模式
mode
权限检查
Linux
对IPC
进行权限管理的IPC_PERM
结构,类似文件的【访问模式】,权限检查机制确保安全性1
2
3
4
5
6
7
8
9
10
struct ipc_perm {
key_t key; // 进程可以根据key来索引到一个IPC对象
uid_t uid; // owner uid
gid_t gid; // owner gid
uid_t cuid; // creator uid
gid_t cgid; // creator gid
mode_t mode; // 访问模式
}继承
- 除了命名服务外的另外一种常见的【分发权限】的方式
Linux
中,匿名管道 —> 用于父子进程之间的通信,内核通过在fork
时复制copy
文件描述符表File Descriptor Table
来建立父子进程之间的连接
命名服务
naming server
【权限分发】通过一个用户态的服务——命名服务实现
过程:
- 各个服务(如文件系统服务,数据库系统服务等)向
naming server
注册自己的服务名。 - 客户端进程向
naming server
上查询当前服务,并选择所需要的服务去尝试获取权限。 - 是否分发权限,是由
naming server
和对应的服务端进程(如文件系统进程,网络系统进程,数据库进程)根据特定的策略(如无条件,需要私钥签名的证书)来判断的。
- 各个服务(如文件系统服务,数据库系统服务等)向
优点:
- 各个服务不再是
kernel
中的ID
等抽象的表示,而是对应用更友好的名字name
- 各个服务不再是
naming server
一般在用户态
宏内核IPC
- 管道
pipe
System V
:常指宏内核下三种具体的IPC
机制
- 消息队列
- 信号量
- 共享内存
Linux
信号机制- 套接字
socket
机制
IPC机制 | 数据抽象 | 参与者 | 方向 | 内核实现 |
---|---|---|---|---|
管道pipe |
字节流 | 两个进程 | 单向 | 通常是FIFO 缓冲区管理数据,分为匿名管道和命名管道 |
消息队列MQ |
消息 | 多进程 | 单向双向 | 队列的组织方式,通过【文件权限】管理对队列的访问 |
信号量semaphore |
计数器 | 多进程 | 单向双向 | 内核kernel 维护共享计数器,通过文件权限管理对计数器的访问 |
共享内存chm |
内存区间 | 多进程 | 单向双向 | 内核维护共享内存的内存区间,通过文件的权限来管理对共享内存的访问 |
信号signal |
事件编号 | 多进程 | 单向 | 为线程/进程维护【信号等待队列】,通过用户 / 组等的权限管理信号的操作 |
套接字socket |
数据报文 | 两个进程 | 单向双向 | 有基于IP + port 和基于file path 的寻址方式,利用网络栈来管理通信 |
管道
匿名管道:通过
pipe
的系统调用创建父子进程
命名管道:通过
mkfifo
命令创建 —> 指定一个全局文件名远距离进程(任意两个进程)
System V
消息队列
发送和接收
message
的接口是kernel
提供的kernel
从system memory
中分配一个队列数据结构,作为消息message
的内核对象MQ
基本操作:1
In Linux, the following operations are defined as system call.
msgget
:允许进程获取已有消息队列的连接,或者创建一个新的消息队列msgsnd
:发消息msgrcv
:收消息msgctl
:可以控制和管理一个消息队列,如修改消息队列的权限信息,或删除消息队列
消息发送和消息接收一般是【非阻塞的】
Linux kernel
实现:- 【系统管理员】通常可以配置【单个消息】
single message
的最大空间、【单个消息队列】single MQ
的最大空间、全系统的消息队列个数 - 通常建议使用【共享内存机制】传递长消息,而非使用
MQ
- 消息在用户态和内核态之间传递时,会有拷贝开销:
copy_from_user
copy_to_user
- 【系统管理员】通常可以配置【单个消息】
System V
信号量
- 通过对信号量的
PV
操作限制可以保证执行顺序 - 关键:封装一个计数器
System V
共享内存
在
Linux
的系统设计中,将共享内存的机制封装在了一个特殊的文件系统中(即共享内存文件系统shm 文件系统
),实际中使用tmpfs
实现
1 | * kernel 为全局所有的共享内存维护一个全局队列结构,由结构体 [shmid_kernel] 组成 |
信号IPC
信号
signal
:具备【单向的事件通知】能力(信号量semaphore
有通知能力,但需要进程主动查询计数器的状态或陷入阻塞状态等待通知)信号传递信息很短,只有一个编号,且由
kernel
传递Linux
早起使用信号有31(1~31号)个,后续POSIX
标准引入了编号32~64的其他信号- 传统信号称为【常规信号】
- 进程多次收到,
kernel
只会记录一次
- 进程多次收到,
POSIX
引入称为【实时信号】- 进程多次收到,
kernel
不会丢弃相同信号时间
- 进程多次收到,
- 传统信号称为【常规信号】
Linux
:kernel
会为每个进程和线程准备一个【信号事件等待队列】- 一个进程内的多个线程
- 共享该进程的【信号事件队列】
- 拥有自己私有的【等待队列】
- 一个进程内的多个线程
Linux
提供了一个专门的系统调用sigprocmask
:允许用户程序设置对特定信号的阻塞block
状态- 信号阻塞,
Linux
不会再触发该信号对应的handler function
,直到该信号被解除block
,信号阻塞不阻碍信号被添加到等待队列上
- 信号阻塞,
signal
既用于IPC
也用于进程管理内核对信号的处理
时机:通常是
kernel
执行完exception
,interrupt
,syscall
返回用户态的时刻方式:
- 忽略
- 用户处理函数:调用【用户注册】的【信号处理函数】
- 内核默认处理函数:调用【默认】的【内核处理函数】
Linux信号内核与用户态之间的切换
步骤
2
Linux完成从内核到用户态的切换后(转向handler
),就会把【相关栈状态】和【上下文】清空,所以为了避免执行完handler
返回时无法恢复到当初系统调用时候用户态的上下文等,在切换到用户态执行handler
时,Linux
会将【syscall
的返回值】和【此前的用户态的上下文(PC,register等)】保存在【用户栈】上。因此步骤4
就会从用户栈上提取上下文等信息以恢复到当初系统调用时候的环境。flowchart TD subgraph usr B(调用syscall1) C(handler) --> D(sigreturn) end subgraph kern A1(syscall1) --> A2(ret_to_user) A3(sigreturn) --> A4(ret_to_user) end B -->|1.用户态进程系统调用进入内核态| A1 A2 -->|2.内核态处理完系统调用时,\n发现有signal,于是切换到\n用户态处理函数位置,让其处理信号事件| C D --> |3.信号处理完后,信号处理函数\n会通过系统调用sigreturn返回内核.\n该系统调用的主要作用即辅助内核\n恢复到被信号打断之前的上下文context|A3 A4 --> |4.继续完成在系统调用被打断时的任务,\n即syscall返回时回到原用户态|B
可重入
reentrant
函数:允许多个任务(☞对信号事件的响应和处理)并发使用,而不必担心【共享数据】的错误- 实现可重入函数的前提
- 不使用
static data
或者static data
只读 - 尽量只是用
local data
- 在必须使用全局共享数据的时候,需要保护对全局数据的访问(防止死锁)
- 避免在函数中修改自己的代码
- 不调用【不可重入函数】,很多常见的库函数的实现(如
malloc
)都是不可重入的
- 不使用
- 实现可重入函数的前提
套接字
客户端通过【地址】找到要调用的服务端进程
IP + port
:通信双方通常使用【本地回环地址127.0.0.1
】,然后各自绑定在不同的端口上,OS网络协议栈可以识别回环地址,将通信转发到目标端口对应的进程UNIX domain socket
:本地文件系统的一个路径
协议
- 传输控制协议
TCP : Transmission Control Protocol
- 可靠性好
- (数据丢失情况下)数据重传
- (数据乱序到达情况下)数据顺序维护
- 用户数据报协议
UDP : User Datagram Protocol
- 简单
- 传输性能较好
- 传输控制协议
Linux
套接字1
2
3
4socket(int domain, int type, int protocol);
* domain: 寻址方式, [IP + port] or [local path];
* type: 通信方式, [SOCK_STREAM](数据流,对应[TCP]) or [SOCK_DGRAM](数据报,对应[UDP])
* protocol: 定义协议相关配置, [TCP] or [UDP];