1. 引论
1.1 什么是操作系统?
操作系统是一种运行在内核态的软件,核心作用是作为应用程序与硬件之间的媒介:一方面向应用程序提供硬件的抽象接口,另一方面统一管理处理器(CPU)、内存、磁盘等硬件资源。
从系统层级来看,结构自上而下为:应用程序(如viddler)→ 操作系统(含虚拟内存、进程、I/O、文件管理模块)→ 硬件(含存储器、磁盘、处理器、总线、I/O设备)。
1.2 操作系统主要功能
操作系统的核心功能围绕硬件资源管理展开,具体包括五大核心模块及两类保障模块:
- 处理器(CPU)管理:核心是进程管理,负责CPU资源的分配与调度,确保多个进程高效、有序地使用CPU;
- 内存管理:通过虚拟内存机制实现内存的分配与回收,为每个进程提供独立的地址空间,避免进程间内存冲突;
- 外存管理:将磁盘等外存设备以“文件”的形式抽象化,负责文件的存储、读取、权限控制及空间分配;
- I/O管理:对键盘、鼠标、网卡、磁盘等输入/输出设备进行统一管理,屏蔽不同硬件的差异,提供标准化的I/O接口;
- 健壮性与安全性管理:保障操作系统自身稳定运行,同时防止非法操作(如越权访问内存)和外部入侵(如恶意程序攻击)。

2. 操作系统结构
2.1 什么是内核?
内核是操作系统的核心程序,具备计算机系统的最高权限,能够直接控制CPU、内存、硬盘等所有硬件资源,提供操作系统最基础的能力(如进程调度、内存地址映射、I/O设备控制),是操作系统实现资源管理的核心载体。
2.2 用户态与内核态
为实现权限隔离、避免应用程序误操作硬件,操作系统将内存划分为两个独立区域,对应两种程序执行状态,具体对比如下:
| 执行状态 | 对应内存区域 | 权限范围 | 运行程序类型 |
|---|---|---|---|
| 用户态 | 用户空间 | 仅能访问局部内存,权限受限 | 应用程序(如浏览器、办公软件) |
| 内核态 | 内核空间 | 可访问所有内存及硬件资源,权限无限制 | 内核程序(如进程调度、内存管理模块) |
用户空间的代码仅能操作自身所属的局部内存,无法直接访问内核空间或硬件;若需使用硬件资源,必须通过内核态程序间接实现。
2.3 用户态和内核态的切换
应用程序无法直接进入内核态,需通过系统调用触发状态切换,具体流程如下:
- 应用程序(运行于用户态)执行系统调用(如读取文件的
read调用),触发软件中断; - CPU接收到中断信号后,立即中断当前正在执行的用户程序,跳转到内核的中断处理程序,此时程序进入内核态;
- 内核程序在 kernel 空间执行对应操作(如从磁盘读取数据到内核缓冲区);
- 内核处理完成后,主动触发中断信号,将CPU的执行权限交回给用户程序,程序回到用户态并继续执行后续代码。

3. 进程和线程
3.1 并行与并发的区别
- 并发:在一段时间内,多个任务会被交替处理,但某一时刻仅一个任务在执行。
例:单核CPU通过“时间片轮转”调度进程A和B——A运行1个时间片(如10ms)后切换到B,B运行1个时间片后再切回A,因切换速度极快,宏观上表现为“同时运行”。 - 并行:在同一时刻,多个任务被同时执行,需多核CPU支持。
例:双核CPU将进程A分配到核心1,进程B分配到核心2,微观上两条指令同时执行,是物理层面的“同时运行”。

3.2 进程上下文切换
对于单核单线程CPU,某一时刻仅能执行一条CPU指令。上下文切换是操作系统将CPU资源从一个进程分配给另一个进程的核心机制,具体流程如下:
- 操作系统首先保存当前进程的“上下文状态”,包括内存空间指针、已执行指令的位置、寄存器值等;
- 从内存中读取下一个待执行进程的上下文状态,并加载到CPU寄存器中;
- 切换CPU的执行权限,开始运行新进程。
用户感知的“计算机能同时运行多个程序”,本质是操作系统通过高频次的上下文切换(如每秒数十次)模拟实现的。

3.3 进程的状态
进程从创建到终止,会经历多个状态及转换,可分为“核心状态”和“扩展状态”两类:
3.3.1 核心状态(基础流转)
核心状态包括运行态、就绪态、阻塞态,是进程最主要的状态,转换规则如下:
- 运行态(Running):进程当前占用CPU,正在执行指令;
- 就绪态(Ready):进程已具备运行条件,但因其他进程占用CPU而暂时等待;
- 阻塞态(Blocked):进程因等待某一事件(如I/O操作完成、接收信号)而暂停运行,即使分配CPU也无法执行。
状态转换逻辑:
- 运行态 → 就绪态:调度程序选择其他进程(如当前进程时间片用完、高优先级进程进入就绪态);
- 就绪态 → 运行态:调度程序选中该进程,分配CPU资源;
- 运行态 → 阻塞态:进程主动请求某事件(如调用
read等待I/O); - 阻塞态 → 就绪态:进程等待的事件完成(如I/O操作结束)。

3.3.2 扩展状态(生命周期两端)
- 创建态(New):进程正在被创建,如操作系统为其分配PID(进程ID)、初始化内存资源;
- 终止态(Exit):进程完成执行或因异常(如除零错误)终止,正在从系统中清除资源(如释放内存、删除进程表项)。

状态转换逻辑:创建态 → 就绪态(进程初始化完成);运行态 → 终止态(进程执行结束或异常退出)。
3.4 僵尸进程与孤儿进程
3.4.1 僵尸进程
- 定义:已完成执行(处于终止态),但进程表中仍保留其描述符的进程;
- 产生场景:仅存在于父子进程关系中——子进程退出时,其进程描述符需父进程通过
wait()或waitpid()函数读取后才会释放;若父进程未调用这两个函数,子进程的描述符会一直保留在进程表中,成为僵尸进程; - 影响:长期积累会占用进程表资源,导致系统无法创建新进程。
3.4.2 孤儿进程
- 定义:父进程提前退出,但其子进程仍在运行,这些子进程称为孤儿进程;
- 处理机制:孤儿进程会被
init进程(系统初始化进程,PID=1)自动收养,由init进程调用wait()函数回收其资源; - 影响:因资源会被
init进程正常回收,不会对系统造成危害。
3.5 进程调度算法
进程调度的核心是“确定某一时刻CPU应运行哪个进程”,常见调度算法如下:
| 算法名称 | 类型 | 核心逻辑 | 特点 |
|---|---|---|---|
| 先来先服务(FCFS) | 非抢占式 | 按进程请求CPU的先后顺序调度 | 有利于长作业,不利于短作业(短作业需等待长作业执行完毕);对I/O密集型进程不友好 |
| 短作业优先(SJF) | 非抢占式 | 按进程“估计运行时间”从短到长调度 | 缩短短作业等待时间,但长作业可能“饿死”(持续有短作业进入时无法调度) |
| 优先级调度 | 可抢占/非抢占 | 为每个进程分配优先级,按优先级从高到低调度;可随时间提高低优先级进程优先级 | 避免低优先级进程饿死,但需合理设置优先级(防止高优先级进程独占CPU) |
| 时间片轮转 | 抢占式 | 就绪进程按FCFS排队,每次分配1个时间片;时间片用完后移至队尾 | 时间片大小关键:过小则切换频繁,过大则实时性差 |
| 最短剩余时间优先(SRTF) | 抢占式 | 新进程到达时,比较其总运行时间与当前进程剩余时间;新进程更短则抢占CPU | 进一步优化短作业响应时间,但实现复杂度高 |
3.6 进程间通信(IPC)方式
进程间内存相互独立,需通过特定机制交换数据,常见IPC方式及特点如下:

3.6.1 管道
- 原理:内核中的一块缓存区,数据从一端写入、另一端读取;
- 分类:
- 匿名管道:单向通信,仅支持有亲缘关系的进程(如父子进程);
- 命名管道:双向通信,支持本机任意进程;
- 特点:实现简单,但效率低、容量有限,仅能传输无格式字节流。
3.6.2 信号
- 原理:发送特定软件中断信号(如
kill -9 PID发送SIGKILL信号),通知接收进程执行预设处理逻辑; - 特点:异步通信,承载信息量少(仅能传递信号编号);是唯一支持异步的IPC方式;
- 常见信号:
SIGINT(Ctrl+C触发,终止进程)、SIGKILL(强制终止,无法忽略)、SIGCHLD(子进程退出信号)。
3.6.3 消息队列
- 原理:内核中的消息链表,进程可按权限向队列添加/读取消息(含类型标识);
- 特点:克服信号信息量少、管道无格式的缺点,但需在用户态与内核态间拷贝数据,实时性一般。

3.6.4 共享内存
- 原理:多个进程将同一块物理内存映射到各自的虚拟地址空间,直接读写内存实现通信;
- 特点:最快的IPC方式(无需数据拷贝),但需配合信号量等机制实现同步(防止并发写冲突)。

3.6.5 信号量
- 原理:基于整数计数器的同步机制,通过
P操作(减1,无资源则阻塞)和V操作(加1,唤醒阻塞进程)控制资源访问; - 特点:仅用于同步(如互斥访问共享资源),不能传递数据;
P/V操作必须成对出现。

3.6.6 Socket
- 原理:基于网络协议(如TCP/UDP)的通信接口;
- 特点:支持不同机器间的进程通信,是跨主机IPC的核心方式;如网络编程中客户端与服务器的通信。
3.7 进程与线程的联系与区别
3.7.1 联系
- 线程是进程内的一条执行流程,一个进程可包含多个线程(即“多线程进程”);
- 同一进程的线程共享代码段、数据段、打开的文件等资源;
- 每个线程拥有独立的寄存器和栈,确保执行流的独立性。

3.7.2 区别
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 调度单位 | 资源分配单位(分配内存、文件等资源) | CPU调度单位(分配CPU时间片) |
| 资源持有 | 拥有完整的资源平台 | 仅独享寄存器、栈等必要资源 |
| 系统开销 | 创建/销毁、上下文切换开销大 | 创建/销毁、上下文切换开销小(共享虚拟内存) |
| 独立性 | 进程间相互独立,一个进程崩溃不影响其他进程 | 线程间共享进程资源,一个线程崩溃可能导致整个进程崩溃 |
3.8 线程上下文切换
线程切换的开销取决于是否属于同一进程:
- 不同进程的线程:切换流程与进程上下文切换一致,需保存/加载虚拟内存、资源表等,开销大;
- 同一进程的线程:因虚拟内存、文件等资源共享,仅需保存/加载线程独立的寄存器、栈,开销远小于进程切换。
3.9 线程的实现方式
线程主要有三种实现方式,对应不同的内核与用户空间协作模式:

3.9.1 内核态线程(1:1模型)
- 原理:每个用户线程对应一个内核线程,内核直接管理线程的调度与切换;
- 特点:线程阻塞时不影响其他线程,但内核线程创建开销大,数量受内核限制。
3.9.2 用户态线程(N:1模型)
- 原理:线程在用户空间实现,内核无感知(仅识别进程);由用户态库负责线程调度;
- 特点:创建/切换开销小,数量不受限,但一个线程阻塞会导致整个进程的线程阻塞。

3.9.3 混合线程(M:N模型)
- 原理:结合前两种方式——多个用户线程映射到少量内核线程;用户态管理非阻塞线程切换,内核态管理阻塞线程切换;
- 特点:平衡开销与灵活性,是现代操作系统(如Linux、Windows)的主流实现方式。

3.10 线程间同步
同步的目标是“确保多线程操作共享资源时,结果正确”,核心是避免并发冲突,常见机制如下:
3.10.1 临界区
- 定义:访问共享资源的代码片段,需保证“同一时刻仅一个线程执行”;
- 实现:通过“锁”控制——线程进入临界区前执行“加锁”,成功则进入;离开后执行“解锁”,释放资源。

3.10.2 锁的分类
- 忙等待锁(自旋锁):加锁失败时,线程循环尝试获取锁,不放弃CPU;适用于临界区执行时间短的场景;
- 无忙等待锁(阻塞锁):加锁失败时,线程进入阻塞态,放弃CPU;适用于临界区执行时间长的场景。

3.10.3 信号量(同步复用)
- 原理:通过
P操作(申请资源)和V操作(释放资源)实现同步;如初始化信号量为1(互斥锁),确保临界区仅一个线程进入。
3.11 死锁
3.11.1 定义
两个或多个线程互相持有对方所需的资源,且均不释放,导致永久阻塞的状态(如线程A持有资源X、等待资源Y,线程B持有资源Y、等待资源X)。

3.11.2 死锁产生的4个必要条件
死锁的产生需同时满足以下4个条件,缺一不可:
- 互斥条件:资源仅能被一个线程占用(如锁、打印机);
- 请求并持有条件:线程持有部分资源,同时请求其他线程的资源;
- 不可剥夺条件:线程占用的资源只能主动释放,不能被强制抢占;
- 环路等待条件:线程与资源形成环形等待链。
3.11.3 死锁的避免(破坏任一条件)
- 破坏“请求并持有”:线程一次申请所有所需资源;
- 破坏“不可剥夺”:线程申请新资源失败时,主动释放已持有资源;
- 破坏“环路等待”:按资源序号从小到大申请(如先申请锁A,再申请锁B),避免环形链;
- 注:“互斥条件”无法破坏(部分资源本质为互斥资源)。
3.12 活锁与饥饿锁
3.12.1 饥饿锁
- 定义:线程长期无法获取所需资源,导致无法推进;
- 例:低优先级线程永远抢不过高优先级线程,始终得不到CPU时间片。
3.12.2 活锁
- 定义:线程状态不断变化,但整体无法推进(类似“互相谦让”导致的僵局);
- 例:两人过窄桥,均想让对方先过,每次同时向同一侧避让,始终无法过桥。
4. 内存管理
4.1 什么是虚拟内存?
虚拟内存是操作系统提供的地址映射机制,核心作用是:
- 为每个进程提供独立的虚拟地址空间,避免进程间内存冲突;
- 突破物理内存限制(将部分不常用数据存储到磁盘,需用时调入内存);
关键概念:
- 虚拟地址:应用程序代码中使用的地址(如变量的指针地址);
- 物理地址:内存硬件实际存储数据的地址;
- 映射关系:操作系统通过页表/段表,将虚拟地址转换为物理地址,对应用程序透明。

4.2 内存分段
- 原理:将程序按逻辑功能划分为多个“段”(如代码段、数据段、栈段、堆段),每个段有独立的属性(如可读、可写、可执行);
- 虚拟地址结构:
段号 + 段内偏移量; - 映射方式:通过“段表”实现——段表存储每个段的“段基地址”(物理内存起始地址)和“段界限”(段大小),物理地址 = 段基地址 + 段内偏移量;
- 例:段表中“段3(栈段)”的基地址为7000,段内偏移量为500,则物理地址 = 7000 + 500 = 7500。
4.3 内存分页
- 原理:将虚拟内存和物理内存均划分为固定大小的“页”(如Linux中每页4KB),通过“页表”映射虚拟页与物理页;
- 虚拟地址结构:
虚拟页号 + 页内偏移量; - 访问流程(两次内存访问):
- 访问页表:通过虚拟页号查找对应的物理页号;
- 计算物理地址:物理地址 = 物理页号 × 页大小 + 页内偏移量,访问该地址获取数据;
- 特点:页大小固定,对应用程序透明,便于内存碎片管理。

4.4 多级页表
- 问题:单级页表在大地址空间(如32位系统)下会非常庞大(需存储大量页表项),浪费内存;
- 原理:将单级页表再次分页,形成多级结构(如二级页表),利用“局部性原理”——仅顶级页表常驻内存,其他级页表需用时才调入;
- 例:32位虚拟地址分为“10位一级页号 + 10位二级页号 + 12位页内偏移”:
- 一级页号索引顶级页表,获取二级页表的物理地址;
- 二级页号索引二级页表,获取物理页号;
- 结合页内偏移得到最终物理地址。

4.5 块表(TLB)
- 问题:分页访问需两次内存(页表 + 物理地址),效率低;
- 原理:利用“局部性原理”(程序短期内仅访问少量页),在CPU中集成高速缓存(TLB,转址旁路缓存),存储最常访问的页表项;
- 访问优化:
- CPU先查询TLB,若命中(找到页表项),直接获取物理地址(仅1次内存访问);
- 若未命中,再访问内存中的页表,同时将该页表项存入TLB。

4.6 分页与分段的区别
| 对比维度 | 分页 | 分段 |
|---|---|---|
| 单位性质 | 物理单位(为管理内存划分) | 逻辑单位(按程序功能划分) |
| 大小 | 固定(由系统决定,如4KB) | 不固定(由程序功能决定) |
| 地址空间 | 一维地址(如0x12345678) | 二维地址(段号 + 段内偏移) |
| 透明性 | 对用户透明 | 对用户可见(需指定段结构) |
| 共享与保护 | 共享、保护受限 | 便于共享(如共享代码段)和保护(按段设权限) |
4.7 交换空间
- 定义:磁盘上的一块专用空间,用于临时存储物理内存中不常用的“页”;
- 作用:物理内存不足时,操作系统将不常用的页“交换”到交换空间,释放内存给活跃进程;
- 虚拟内存容量:物理内存大小 + 交换空间大小。
4.8 页面置换算法
当缺页中断发生(虚拟页不在物理内存)且内存无空闲页时,需置换内存中的某一页,常见算法如下:
| 算法名称 | 核心逻辑 | 特点 |
|---|---|---|
| 最佳置换(OPT) | 置换“未来最长时间不访问”的页 | 理论最优,无法实现(无法预知未来访问);仅作基准 |
| 先进先出(FIFO) | 置换“最早进入内存”的页(用链表记录顺序,置换链表头) | 实现简单,可能触发“Belady异常”(内存页数增加,缺页率反而上升) |
| 最近最久未使用(LRU) | 置换“最近最长时间未访问”的页(假设历史访问可预测未来) | 近似最优,实现开销大(需维护访问顺序链表) |
| 时钟算法 | 环形链表存储页,表针指向最老页;缺页时检查访问位,0则置换,1则清位前移 | 实现简单,开销低于LRU,是LRU的近似算法 |
| 最不常用(LFU) | 为页设“访问计数器”,置换“访问次数最少”的页 | 可能置换“早期常用、近期不常用”的页,对突发访问不友好 |
- 先进先出置换算法(FIFO)

- 最近最久未使⽤的置换算法(LRU)

- 时钟页⾯置换算法

5. 文件管理
5.1 硬链接与软链接的区别
硬链接和软链接是文件系统中两种链接方式,核心区别如下:
| 对比维度 | 硬链接 | 软链接(符号链接) |
|---|---|---|
| inode | 与源文件共享同一个inode | 拥有独立的inode |
| 本质 | 目录中指向源文件inode的条目 | 独立文件,内容为源文件的路径字符串 |
| 跨文件系统 | 不支持(inode在不同文件系统不唯一) | 支持(仅依赖路径,与文件系统无关) |
| 源文件删除 | 仅删除一个条目,文件仍存在(inode引用数>0) | 链接文件存在,但无法访问源文件(路径失效) |
| 链接对象 | 仅支持文件,不支持目录 | 支持文件和目录 |


6. I/O技术
6.1 零拷贝技术
6.1.1 传统I/O的问题
传统文件传输(如从磁盘读文件并通过网卡发送)需经历:
- 4次上下文切换(用户态→内核态→用户态→内核态);
- 4次数据拷贝(磁盘→内核缓冲区→用户缓冲区→socket缓冲区→网卡);
频繁的切换与拷贝导致I/O效率低下。

6.1.2 零拷贝的实现方式
零拷贝的核心是减少“用户态-内核态切换”和“数据拷贝”,主流实现有两种:
1. mmap + write
- 原理:
mmap()将内核缓冲区直接“映射”到用户空间,用户程序可直接操作内核缓冲区,无需用户态与内核态的数据拷贝; - 流程:磁盘→内核缓冲区(DMA拷贝)→socket缓冲区(CPU拷贝)→网卡(DMA拷贝);
- 优化:减少1次用户态-内核态拷贝,上下文切换2次,数据拷贝3次。

2. sendfile
- 原理:
sendfile()是Linux内核提供的专用系统调用,直接在内核空间完成“内核缓冲区→socket缓冲区”的拷贝,无需用户态参与; - 流程:磁盘→内核缓冲区(DMA拷贝)→socket缓冲区(CPU拷贝)→网卡(DMA拷贝);
- 优化:减少2次上下文切换(仅1次系统调用),数据拷贝3次;
- 应用:Kafka、RocketMQ等中间件均采用sendfile提升I/O效率。

6.2 阻塞与非阻塞 I/O、同步与异步 I/O
四种I/O模型的核心区别在于“等待数据准备”和“数据拷贝”两个阶段是否阻塞,可通过“老三等UP主更新”的例子理解:
6.2.1 阻塞I/O
- 流程:调用
read后,线程阻塞,直到内核数据准备好并完成“内核→用户”拷贝,read才返回; - 等待阶段:“数据准备”和“数据拷贝”均阻塞;
- 例子:老三不做其他事,一直盯着UP主页面等更新。

6.2.2 非阻塞I/O
- 流程:调用
read后,数据未准备好则立即返回错误;应用程序需轮询内核,直到数据准备好,再等待拷贝完成; - 等待阶段:“数据准备”不阻塞,“数据拷贝”阻塞;
- 例子:老三发现UP主没更新,就去喝茶,过一会儿回来查看,直到更新。

6.2.3 基于非阻塞的I/O多路复用
- 流程:通过
select/poll/epoll监控多个I/O事件,内核数据准备好后以“事件通知”告知应用程序,再发起read; - 等待阶段:“数据准备”不阻塞(靠事件通知),“数据拷贝”阻塞;
- 本质:同步I/O(拷贝阶段仍阻塞);
- 例子:老三没等到更新就去干别的,B站推送“有UP主更新”后,再去查看目标UP主是否更新。

6.2.4 异步I/O
- 流程:调用
aio_read后立即返回;内核自动完成“数据准备”和“内核→用户拷贝”,完成后通知应用程序; - 等待阶段:“数据准备”和“数据拷贝”均不阻塞;
- 例子:老三告诉UP主“更新后通知我”,UP主完成视频后主动发给老三,老三无需等待。

6.3 I/O多路复用
6.3.1 定义
I/O多路复用允许一个进程/线程同时监控多个Socket的I/O事件,实现“多连接复用一个进程/线程”,解决传统“一连接一线程”的资源耗尽问题(如C10K问题)。
6.3.2 三种实现机制对比
| 机制 | 核心原理 | 优点 | 缺点 |
|---|---|---|---|
| select | 将Socket集合(fd_set)拷贝到内核,内核遍历检查事件;结果拷贝回用户态再遍历 | 跨平台支持(Linux、Windows均支持) | 1. fd_set大小受限(默认1024);2. 两次遍历(内核+用户态);3. 拷贝开销大 |
| poll | 用动态链表存储Socket,突破fd_set大小限制 | 无Socket数量限制 | 仍需两次遍历,拷贝开销随Socket数量增加而增大(时间复杂度O(n)) |
| epoll | 1. 红黑树存储Socket(增删查O(logn));2. 事件驱动,链表记录就绪Socket | 1. 无数量限制;2. 无需遍历(O(1));3. 减少拷贝 | 仅支持Linux系统,跨平台性差;是解决C10K问题的核心机制 |


