操作系统高频考点
进程和线程相关
用户态和内核态
内核负责管理和控制硬件资源,为了避免应用程序直接访问硬件资源可能导致的问题,操作系统将应用程序和内核隔离,确保内核独立控制硬件资源。
因此,操作系统内核运行在两种模式下:
- 内核态:操作系统具有最高权限,可以访问所有资源并执行所有指令。
- 用户态:应用程序只能访问授权资源,执行受限指令集,需要通过系统调用来请求操作系统服务。
当应用程序需要进行系统调用(如读写文件)时,会触发用户态向内核态的切换。CPU 会暂停当前进程,保存状态,切换到内核态执行操作,完成后再切换回用户态,恢复进程执行,因此涉及内核态和用户态的切换操作会造成一定的性能开销。
进程,线程和协程
进程:
- 进程是系统资源分配的基本单位。
- 进程拥有独立的执行环境和私有的基本运行资源集合,包括自己的内存空间。
- 一个应用程序可能由多个协作的进程组成。
线程:
- 线程是系统调度的基本单位。
- 线程共享进程的资源,包括内存和打开的文件,减少了系统调用和时间消耗。
- 线程比进程更轻量,包含程序、数据和线程控制块(TCB)。
协程:
- 协程(Coroutine),也称纤程,是用户态的概念,不被操作系统感知。
- 协程允许用户控制执行逻辑流,避免了操作系统级的线程阻塞和资源竞争。
- 协程通过异步方式避免了回调地狱(callback hell),提高了代码的可读性。
引入线程和协程的必要性:
- 线程的引入是为了减少进程间切换的上下文切换成本,允许在一个进程内并发执行多个子任务。
- 协程的引入是为了进一步优化资源利用,允许用户控制任务切换,避免了多线程中的CPU资源竞争和不必要的资源浪费。
进程的状态
操作系统的进程有三种状态:
- 运行(running):进程已获得CPU,处于运行中的状态。在单处理机系统中,只有一个进程处于执行状态,在多处理机系统中,则有多个进程处于执行状态。
- 就绪(ready):进程具备运行条件,只要再获得CPU,便可立即执行,进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个。
- 等待(wait):又称阻塞态或睡眠态,一个进程因(请求I/O、等待I/O完成等)事件而暂停运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。
进程调度算法
| 算法 | 核心思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 时间片轮转 | 固定时间片轮流执行 | 公平、响应快 | 周转时间长、切换开销大 | 分时系统 |
| 优先级调度 | 按优先级高低执行 | 区分任务重要性 | 可能饥饿、需维护优先级 | 实时系统 |
| 多级反馈队列 | 动态调整队列优先级和时间片 | 灵活适应不同任务类型 | 配置复杂、参数敏感 | 通用操作系统 |
时间片轮转调度算法(Round Robin, RR):
- 实现原理:每个进程被分配一个固定时间片(Time Quantum),按就绪队列顺序依次执行。若进程在时间片内未完成,则被抢占并重新加入队列尾部,等待下一次调度。
- 特点:
- 抢占式:时间片结束时强制切换进程。
- 公平性:所有进程获得均等的CPU时间。
优先级调度算法(Priority Scheduling):
- 实现原理:为每个进程分配优先级,调度时选择优先级最高的进程执行。可分为静态优先级(固定)或动态优先级(根据等待时间、执行历史等调整)。
- 特点:
- 抢占式/非抢占式:抢占式允许高优先级进程立即抢占CPU。
- 饥饿问题:低优先级进程可能长期等待,需通过“老化”(逐渐提升优先级)解决。
多级反馈队列调度算法(Multilevel Feedback Queue, MLFQ):
- 实现原理: 设置多个优先级队列,新进程进入最高优先级队列。每个队列的时间片大小递增(如第一级队列时间片短,后续队列时间片加长)。进程未完成则降级到下一队列;若完成或主动释放CPU(如I/O操作),可能重新进入较高队列。
- 特点
- 动态调整:根据进程行为(CPU密集型或I/O密集型)反馈调整优先级。
- 兼顾长短作业:短作业快速响应,长作业减少切换开销。
I/O 相关
操作系统I/O模型
| 模型 | 阻塞阶段 | 用户态参与 | 复杂度 | 性能 |
|---|---|---|---|---|
| 同步阻塞I/O | 全程阻塞 | 被动等待 | 低 | 低(高并发差) |
| 同步非阻塞I/O | 仅拷贝阶段阻塞 | 主动轮询 | 中 | 中(CPU占用高) |
| I/O复用 | 阻塞在复用函数 | 被动监听+主动处理 | 中 | 高(epoll优化) |
| 信号驱动I/O | 仅拷贝阶段阻塞 | 异步信号通知 | 高 | 中(适用场景少) |
| 异步I/O | 全程无阻塞 | 完全异步回调 | 高 | 最高(依赖系统支持) |
同步阻塞型I/O模型(Blocking I/O):
- 实现原理:进程在 recv 系统调用期间阻塞,直到数据从内核复制到用户空间。
- 特点:
-简单易用:代码逻辑直观。
-资源浪费:进程在等待期间无法执行其他任务,高并发时需多线程/进程,开销大。
同步非阻塞型I/O模型(Non-blocking I/O):
- 实现原理:进程调用 recv 时,若内核数据未就绪,立即返回错误,进程需轮询重试直到数据就绪,随后阻塞完成数据拷贝。
- 特点:
- 部分非阻塞:仅等待数据阶段非阻塞,拷贝阶段仍阻塞。
- CPU 空转:频繁轮询消耗大量CPU资源。
I/O 复用模型(I/O Multiplexing)
- 实现原理:通过 select、poll、epoll 系统调用,单线程监听多个文件描述符(fd)。进程阻塞在复用函数上,当任一 fd 数据就绪时,再调用 recv 进行数据拷贝。
- 特点:
- 高效管理多连接:单线程处理多 I/O,减少线程/进程数。
- 两次系统调用:先 select 检测就绪状态,再 recv` 贝数据。
信号驱动I/O模型(Signal-driven I/O)
- 实现原理:通过sigaction注册信号函数,在内核数据准备好时中断当前程序执行信号函数。
- 特点:
- 异步通知:避免轮询,减少CPU消耗。
- 局限性:仅适用于UDP等支持信号驱动的协议,且信号处理需考虑可重入性。
异步 I/O 模型(Asynchronous I/O, AIO)
- 实现原理:通过aio_read系统调用,内核在数据准备好并复制到用户进程空间后执行指定函数,进程在此过程中可以执行其他任务。
- 特点:
- 真正异步:进程全程无阻塞,完全由内核完成I/O操作。
- 复杂性高:需系统支持(如Linux的 io_uring 、Windows的 IOCP)。
select、poll 和 epoll
| 技术 | fd长度限制 | 遍历所有fd | 把fd从用户态copy到内核态 |
|---|---|---|---|
| select | 1024 | 是 | 是 |
| poll | 无限制 | 是 | 是 |
| epoll | 无限制 | 否 | 否 |
select:
- 最早的I/O多路复用技术,可以监听read、write、except的文件描述符(fd)。
- 缺点:单个进程最多只能监听1024个文件描述符,且需要将包含大量fd的数组整体复制于用户态和内核态之间,导致开销随文件描述符数量增加而线性增大。
poll:
- 基于select,支持监听更多的文件描述符,但没有最大限制。
- 同样需要轮询pollfd来获取就绪的fd,所有fds在内核态和用户态中来回切换,影响效率。
epoll:
- 基于Linux 2.4.5,优化了复杂度,支持更多的文件描述符,效率更高。
- 通过epoll_ctl注册事件时,将所有fd拷贝进内核,而不是在epoll_wait时重复拷贝。
- epoll_wait直接使用就绪的fd(O(1)),不需要像select和poll那样手动遍历(O(n))。
- 支持的fd上限是系统最大可打开文件的数目,通常远大于2048,与系统内存大小有关。
epoll的两种模式:
- LT(level trigger)模式:默认模式,事件通知后,应用程序可以不立即处理,下次调用epoll_wait时会再次通知。
- ET(edge trigger)模式:事件通知后,应用程序必须立即处理,否则下次调用epoll_wait时不会再次通知。 ET模式减少了事件被重复触发的次数,效率更高,但需要使用非阻塞socket以避免处理多个文件描述符时的阻塞问题。
内存相关
分段分页
| 方法 | 划分单位 | 碎片问题 | 地址转换 | 硬件需求 | 优势 |
|---|---|---|---|---|---|
| 分段 | 逻辑段 | 外部碎片(需紧凑整理) | 段表(基址+界限) | 段寄存器+界限检查 | 逻辑隔离与保护 |
| 分页 | 固定大小的页 | 仅内部碎片(最后一页浪费) | 页表(虚拟页→物理页帧) | MMU+TLB加速 | 高效内存管理、虚拟内存 |
分段(Segmentation):
- 逻辑划分:将程序的内存空间按逻辑模块划分为多个段(如代码段、数据段、堆栈段等),每个段对应程序的一个功能部分。
- 动态大小:段的大小不固定,由实际需求决定。
- 段表管理:通过段表(Segment Table)记录每个段的基址(起始地址)和界限(长度),实现逻辑地址到物理地址的转换。
在分段的映射方法中,每次换入换出内存的都是整个程序,这样会造成大量的磁盘访问操作,且每个段大小不一,频繁分配/释放后易产生内存碎片,需通过紧凑(Compaction)整理,但开销较大,导致效率低下。
分页(Paging):
- 物理划分:将物理内存和虚拟地址空间划分为固定大小的页(如4KB),消除外部碎片。
- 页表映射:通过页表(Page Table)建立虚拟页到物理页帧的映射,支持非连续分配。
- 硬件支持:由MMU(内存管理单元)加速地址转换,如TLB(快表)缓存常用页表项。
分页方法允许进程使用超过物理内存的地址空间,通过页面置换(如LRU算法)将不活跃页换出到磁盘,固定页大小减少外部碎片,仅存在内部碎片(最后一页未用满)。
现代操作系统的内存管理:
- 段页式结合:如x86架构,先分段再分页,兼顾逻辑保护与物理效率(但x86-64弱化分段,主要用分页)。
- 分页主导:Linux/Windows等系统以分页为核心,分段仅用于兼容性或特定场景(如权限控制)。
- 虚拟内存扩展:分页支持按需调页、写时复制(Copy-on-Write)等高级特性,优化多任务性能。
零拷贝
传统的数据传输,数据需要多次在内核态和用户态之间复制,导致性能损耗,零拷贝技术可以减少或消除数据在内存中的冗余拷贝次数,从而降低 CPU 和内存的开销,提升系统性能。
以下是传统拷贝与零拷贝技术的对比表格,从 拷贝次数、上下文切换、CPU 参与度 等关键维度进行对比:
| 技术 | 数据拷贝 | 上下文切换 | CPU 参与拷贝 | 用户态参与 | 支持系统 |
|---|---|---|---|---|---|
| 传统拷贝 | 2 次 DMA + 2 次 CPU 拷贝 | 4 次 | 2 次 | 是 | 所有系统 |
mmap + write | 2 次 DMA + 1 次 CPU 拷贝 | 3 次 | 1 次 | 是(需调用) | Linux/Unix |
sendfile | 2 次 DMA + 1 次 CPU 拷贝 | 2 次 | 1 次 | 否 | Linux 2.4+ |
sendfile + SG-DMA | 2 次 DMA(零 CPU 拷贝) | 2 次 | 0 次 | 否 | Linux 2.4+(需硬件支持) |
mmap + write:
- 实现原理:使用内存映射(mmap)将内核缓冲区的数据映射到用户空间,避免显式拷贝。
- 过程:
- 磁盘 → 内核缓冲区(DMA 拷贝)。
- 内核缓冲区与用户空间通过 mmap 共享内存(无需 CPU 拷贝)。
- 用户程序调用 write,数据从内核缓冲区 → Socket 缓冲区(CPU 拷贝)。
- Socket 缓冲区 → 网卡(DMA 拷贝)
- 优化效果:四次用户态/内核态的切换,三次数据拷贝,减少了 1 次 CPU 拷贝。
sendfile 系统调用:
- 实现原理:通过 sendfile 系统调用直接将数据从文件描述符传输到 Socket,全程在内核态完成。
- 过程:
- 磁盘 → 内核缓冲区(DMA 拷贝)。
- 内核缓冲区 → Socket 缓冲区(CPU 拷贝)。
- Socket 缓冲区 → 网卡(DMA 拷贝)。
- 优化效果:完全消除用户态参与,三次数据拷贝,仅 1 次 CPU 拷贝。
sendfile + SG-DMA(Scatter-Gather DMA):
- 实现原理:结合 sendfile 和 Scatter-Gather DMA,消除最后的 CPU 拷贝。
- 过程:
- 磁盘 → 内核缓冲区(DMA 拷贝)。
- 内核缓冲区 → 网卡(SG-DMA 直接传输,无需 CPU 拷贝)。
- 优化效果:两次数据拷贝,零 CPU 拷贝,实现真正的“零拷贝”。