第十二章 操作系统、网络与高性能 I/O
第十二章 操作系统、网络与高性能 I/O
1. 用户态、内核态和系统调用之间是什么关系?
问题分析
这道题考查是否建立了用户态与内核态边界、网络协议和 I/O 模型的清晰概念边界,并能把类别、职责和限制对应起来。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:用户程序在受限权限下运行,不能直接执行某些特权操作或任意访问内核内存。
- 再讲机制:围绕“权限边界 → 成本与边界”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:腾讯 Linux 后台一面
问题讲解
一、权限边界
用户程序在受限权限下运行,不能直接执行某些特权操作或任意访问内核内存。系统调用是进入内核服务的受控入口:用户态准备编号和参数,通过特定指令陷入内核,内核验证参数、执行操作,再返回用户态。库函数可能包装系统调用,也可能完全在用户态完成。

二、成本与边界
系统调用涉及权限切换、参数检查和可能的调度/拷贝,但“用户态到内核态”等于完整进程切换是错误的:通常仍是同一线程执行。vDSO 等机制可让部分查询避免真正陷入。减少系统调用要基于批处理和测量,不能牺牲正确性。
三、常见错误
- 把每次函数调用都称为系统调用。
- 认为系统调用一定发生进程上下文切换。
- 直接信任用户指针,忽略内核必须验证和复制。
- 认为 errno 是系统调用唯一错误承载方式。
回答自检
- 两种权限态的安全边界。
- 系统调用的陷入、验证、执行和返回流程。
- 权限切换与进程切换的区别。
- 库函数不等于系统调用。
面试官可能追问的问题
- 库函数和系统调用什么关系? 库可包装一个、多次或零次系统调用。
- 为什么要区分权限级别? 隔离故障和恶意代码,集中管理硬件资源。
- 阻塞系统调用会怎样? 当前线程可能被挂起,调度器运行其他线程。
2. 进程有哪些状态?僵尸进程和孤儿进程有什么区别?
问题分析
这道题不只是让你罗列名词,而是考查能否从用户态与内核态边界、网络协议和 I/O 模型做出有依据的比较和选择。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:抽象上进程可处于新建、就绪、运行、阻塞/睡眠和终止状态;
- 再讲机制:围绕“常见状态”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:美团 Linux 后台一面
问题讲解
一、常见状态
抽象上进程可处于新建、就绪、运行、阻塞/睡眠和终止状态;具体操作系统还细分可中断睡眠、停止等。运行态等待 CPU 会回到就绪,等待 I/O 会进入阻塞,事件完成后回到就绪。

子进程退出后,内核保留退出状态等少量信息,等待父进程 wait/waitpid,此时是僵尸。孤儿进程是父进程先退出、子进程仍运行,之后通常由系统指定的收养者接管并负责回收。这里比较两种典型时序;“父进程是否还在”和“子进程是否已退出未回收”是不同维度,不能把孤儿与僵尸当成始终互斥的状态枚举。
二、常见错误
- 把孤儿进程当作僵尸。
- 认为 kill 僵尸可以让它完全消失;应让父进程回收。
- 父进程创建大量子进程却不处理 SIGCHLD/wait。
- 把阻塞状态理解为占用 CPU 忙等。
回答自检
- 就绪、运行、阻塞之间的迁移。
- 终止与回收不是同一步。
- 僵尸和孤儿的父子时序差异。
- wait 在资源回收中的作用。
面试官可能追问的问题
- 僵尸占什么资源? 主要占进程表项、PID 和退出信息,不再执行代码。
- 如何避免? 父进程及时 wait,或采用可靠子进程管理策略。
- 孤儿一定有害吗? 不一定,守护化历史上会有意让进程被收养。
3. TCP 服务端为什么要依次调用 socket、bind、listen 和 accept?
问题分析
这道题考查能否从可观察现象追溯到用户态与内核态边界、网络协议和 I/O 模型中的具体机制,并说明规则被破坏后的后果。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:
socket创建内核 Socket 对象并选择地址族、类型和协议; - 再讲机制:围绕“服务端生命周期 → 边界与错误处理”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:百度 C++ 后端一面
问题讲解
一、服务端生命周期
socket 创建内核 Socket 对象并选择地址族、类型和协议;bind 关联本地 IP/端口;listen 把它变为被动监听 Socket 并建立连接队列;accept 从已完成连接队列取出一条连接,返回新的已连接 Socket。监听 Socket 继续接受后续连接。

fd3、fd4、fd5是教学编号,不是系统保证的分配结果。
二、边界与错误处理
服务端通常设置地址复用、非阻塞、close-on-exec,并循环 accept。非阻塞监听 fd 在队列暂时为空时返回 EAGAIN/EWOULDBLOCK。accept 成功后要为连接设置资源上限、超时和关闭协议;监听 backlog 也不是无限并发连接容量的完整控制。
三、常见错误
- 在监听 Socket 上直接收发某个客户端数据。
- accept 一次后关闭监听 Socket,无法接受后续连接。
- 忽略短读短写和中断错误。
- 把 listen 等同于已经和客户端连接。
回答自检
- 四个调用逐步建立的状态。
- 监听 Socket 与连接 Socket 的区别。
- accept 队列和非阻塞错误。
- 连接资源和业务并发不是 backlog 一项决定。
面试官可能追问的问题
- 客户端为什么通常不显式 bind? connect 时内核可自动选择本地地址和临时端口。
- accept 是否完成三次握手? 它通常取出内核已完成的连接,握手主要由 TCP 栈处理。
- 一个端口如何服务多客户端? 连接由本地/远端 IP 和端口组合区分。
4. 管道、共享内存和 Unix domain socket 如何选择?
问题分析
这道题不只是让你罗列名词,而是考查能否从用户态与内核态边界、网络协议和 I/O 模型做出有依据的比较和选择。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:管道提供字节流,匿名管道常用于亲缘进程,命名管道可通过路径连接;
- 再讲机制:围绕“三类机制”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:腾讯 Linux 后台二面
问题讲解
一、三类机制
管道提供字节流,匿名管道常用于亲缘进程,命名管道可通过路径连接;共享内存让多个进程映射同一物理页,数据路径拷贝少,但必须自行设计同步、布局和崩溃恢复;Unix domain socket 提供本机双向通信,可用流或数据报语义,并支持权限和传递文件描述符。
| 维度 | 管道 | 共享内存 | Unix domain socket |
|---|---|---|---|
| 通信形态 | 字节流,常单向 | 共享数据结构 | 双向流/数据报 |
| 数据拷贝 | 经内核缓冲 | 映射后少拷贝 | 经内核 Socket 缓冲 |
| 同步 | 内核阻塞语义 | 应用自行同步 | 内核就绪与队列 |
| 适合 | 简单父子流水线 | 大数据高吞吐 | 本机服务化接口 |

二、常见错误
- 认为共享内存自动同步和保证对象生命周期。
- 在共享内存直接放含普通指针、mutex 或 STL 容器的对象。
- 把管道一次 write 对应一次 read 当作普遍消息边界。
- 忽略 UDS 路径权限和清理。
回答自检
- 三类 IPC 的数据路径和拓扑。
- 共享内存性能换来的同步复杂度。
- 字节流与消息边界问题。
- 进程地址空间对数据布局的限制。
面试官可能追问的问题
- 共享内存如何通知? 可搭配信号量、进程共享同步原语、eventfd 等。
- 为什么不能放普通指针? 各进程映射地址可能不同,指针只对本地址空间有意义。
- UDS 比 TCP 少什么? 不经过 IP 路由,但仍有 Socket 缓冲和协议处理。
5. TCP 和 UDP 有什么区别?分别适合什么场景?
问题分析
这道题不只是让你罗列名词,而是考查能否从用户态与内核态边界、网络协议和 I/O 模型做出有依据的比较和选择。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:TCP 是面向连接的可靠有序字节流,提供序号、确认、重传、流量控制和拥塞控制,但不保留应用消息边界。
- 再讲机制:围绕“传输语义 → 场景与边界”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:美团后台一面
问题讲解
一、传输语义
TCP 是面向连接的可靠有序字节流,提供序号、确认、重传、流量控制和拥塞控制,但不保留应用消息边界。UDP 是无连接数据报服务,保留每个数据报边界,不保证到达、顺序、去重或拥塞控制,应用可自行构建所需协议。
| 维度 | TCP | UDP |
|---|---|---|
| 数据模型 | 字节流 | 数据报 |
| 可靠有序 | 提供 | 不提供 |
| 连接状态 | 有 | 内核通常无连接握手状态 |
| 消息边界 | 不保留 | 保留 |
| 广播/组播 | 不支持 | 可支持 |

二、场景与边界
HTTP/1.1、数据库连接、文件传输常基于 TCP;实时音视频、游戏状态、DNS 常使用 UDP 或在 UDP 上构建 QUIC 等协议。UDP 不等于天然更快,若应用需要完整可靠性,重做不佳可能更慢、更不公平。
三、常见错误
- 认为 TCP 一次 send 对应一次 recv。
- 认为 UDP 不会丢包或乱序。
- 大 UDP 数据报依赖 IP 分片而不控制 MTU。
- 自定义 UDP 协议不做拥塞控制。
回答自检
- 字节流与数据报的根本差异。
- TCP 提供的可靠和流控机制。
- UDP 留给应用的责任。
- 选择取决于语义而非绝对快慢。
面试官可能追问的问题
- TCP 是否保证应用处理成功? 只保证字节交付到对端协议栈/Socket 语义,业务确认需应用协议。
- UDP connect 有何意义? 可固定默认对端并过滤来源,但不产生 TCP 握手。
- QUIC 为什么基于 UDP? 在用户态实现安全、多路复用、可靠与拥塞控制并便于演进。
6. TCP 三次握手和四次挥手分别解决什么问题?
问题分析
这道题考查能否用用户态与内核态边界、网络协议和 I/O 模型解释题目中的语义,而不是停留在定义记忆。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:客户端发 SYN 和初始序号,服务端回 SYN+ACK,既确认客户端序号又发送自己的序号;
- 再讲机制:围绕“建立连接 → 关闭连接”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:百度后端一面
问题讲解
一、建立连接
客户端发 SYN 和初始序号,服务端回 SYN+ACK,既确认客户端序号又发送自己的序号;客户端再 ACK,确认服务端方向。三次交互让双方确认双向通信能力并同步序号,减少旧重复报文误建连接。

序号100、500是无数据的独立教学例;SYN各占一个序号。
二、关闭连接
TCP 是全双工,两方向可独立关闭。收到 FIN 只表示对方不再发送,对方仍可能继续接收,因此常见关闭需要 FIN、ACK、FIN、ACK 四段;ACK 与 FIN 也可能合并。主动关闭方常进入 TIME_WAIT,等待旧报文过期并能重发最后 ACK。
图示常见非同时关闭、没有额外数据的路径,ACK与FIN也可能合并。
三、常见错误
- 说三次握手只为“连接更可靠”。
- 认为挥手永远恰好四个独立包。
- 把 FIN 当作双方立即都不能收发。
- 认为 TIME_WAIT 必然是故障并粗暴关闭。
回答自检
- 双方初始序号如何确认。
- 全双工方向为何独立关闭。
- FIN、ACK 合并的边界。
- TIME_WAIT 和 CLOSE_WAIT 的含义。
面试官可能追问的问题
- 为什么不能两次握手? 发起方无法确认对方的 SYN/初始序号已被确认,旧 SYN 处理也更困难。
- CLOSE_WAIT 多说明什么? 本端收到 FIN 但应用迟迟未 close。
- TIME_WAIT 在哪一方? 在常见非同时关闭中,首先发送 FIN 的主动关闭方在收到对端 FIN 并确认后进入 TIME_WAIT;同时关闭时双方都可能进入。不能按“谁发最后一个 FIN”判断。参见TCP 状态机。
7. TCP 粘包和拆包为什么发生?应用层协议如何处理?
问题分析
这道题考查能否从可观察现象追溯到用户态与内核态边界、网络协议和 I/O 模型中的具体机制,并说明规则被破坏后的后果。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:TCP 只提供有序字节流,不保留发送调用边界。
- 再讲机制:围绕“发生原因 → 常见 framing”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:腾讯后台一面
问题讲解
一、发生原因
TCP 只提供有序字节流,不保留发送调用边界。一次 send 的数据可能被分段,多次 send 也可能合并;接收方一次 recv 只得到当前可用的一段字节。因此应用层必须定义怎样从缓冲区解析完整消息。
长度前缀协议的解析器必须维护“读指针”,只有 header + body 全部到齐才产出一帧;不完整数据继续留在缓冲区等待下一次 recv。
二、常见 framing
固定长度适合结构固定消息;分隔符适合文本但需转义/限制长度;长度前缀最常见,先解析固定头再等待完整 body;自描述协议仍需外层长度或可判定结束。解析器必须处理半包、多包、恶意超长长度和剩余字节。
| 方案 | 优点 | 风险 |
|---|---|---|
| 固定长度 | 简单 | 浪费空间、扩展差 |
| 分隔符 | 文本友好 | 转义和扫描成本 |
| 长度前缀 | 二进制通用 | 必须校验长度上限 |
| 连接关闭定界 | 简单响应 | 无法复用连接 |
![]() |
示意协议用两字节大端长度,消息体分别是ABC与DE;接收批次仅为可能的教学交错。
三、常见错误
- 假设一次 send 等于一次 recv。
- recv 不足一条消息就当作连接错误。
- 不限制长度字段,导致内存攻击。
- 解析一帧后丢掉同批接收的剩余字节。
回答自检
- send/recv 与消息边界无对应关系。
- 接收缓冲区为何必须累计。
- 四种 framing 的取舍。
- 长度校验和剩余字节处理。
面试官可能追问的问题
- UDP 有粘包吗? UDP 保留数据报边界,但仍可能丢失、截断或分片。
- 短写如何处理? 循环发送剩余字节,并处理非阻塞 EAGAIN。
- 长度前缀用什么端序? 协议规定网络字节序或明确编码。
8. 阻塞、非阻塞、select、poll 和 epoll 有什么区别?
问题分析
这道题不只是让你罗列名词,而是考查能否从用户态与内核态边界、网络协议和 I/O 模型做出有依据的比较和选择。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:阻塞/非阻塞描述单个 I/O 调用在暂不可完成时是否等待;
- 再讲机制:围绕“两个维度”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:美团基础架构二面
问题讲解
一、两个维度
阻塞/非阻塞描述单个 I/O 调用在暂不可完成时是否等待;select/poll/epoll 用于等待一组 fd 的就绪事件。select 每次传位图且受 fd 集合表示限制;poll 使用数组无固定位图上限但仍线性扫描;epoll 在 Linux 内核维护关注集合,等待时返回就绪事件,更适合大量连接中少量活跃场景。

就绪不等于操作一定完整:读取仍可能只有部分数据,多个线程竞争也可能使状态变化。epoll 不是跨平台标准,macOS 常用 kqueue,Windows 有 IOCP。
二、常见错误
- 把非阻塞等同异步 I/O。
- 认为 epoll 单次通知会自动读取数据。
- 边缘触发 fd 仍使用阻塞模式。
- 宣称 epoll 在所有连接数和活跃率下都更快。
回答自检
- 操作模式和多路等待是两个维度。
- 三种多路复用机制的数据结构差异。
- 就绪事件与完整 I/O 的区别。
- epoll 的 Linux 平台边界。
面试官可能追问的问题
- select 为什么每轮重建集合? 内核会修改返回集合,接口要求调用者重新准备。
- 就绪是什么意思? 当前操作预计不会因等待数据而阻塞,不保证满足整个业务消息。
- 非阻塞 connect 如何完成? 等待可写后用
SO_ERROR检查结果。
9. epoll 的 LT 和 ET 模式有什么区别?
问题分析
这道题不只是让你罗列名词,而是考查能否从用户态与内核态边界、网络协议和 I/O 模型做出有依据的比较和选择。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:LT(水平触发)只要 fd 仍处于就绪状态,后续 epoll_wait 会继续报告,编程较稳健。
- 再讲机制:围绕“通知方式 → 正确使用 ET”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:字节 Linux 后端二面
问题讲解
一、通知方式
LT(水平触发)只要 fd 仍处于就绪状态,后续 epoll_wait 会继续报告,编程较稳健。ET(边缘触发)关注就绪事件的变化,但不是所有设备事件都能严格简化成“缓冲区从 0 变非 0 才通知”;若本次没有把数据处理到暂不可继续,可能长时间收不到新边缘。
二、正确使用 ET
fd 必须非阻塞;收到可读后循环 read,处理正返回值,并在 EAGAIN/EWOULDBLOCK、EOF 或错误时按协议停下,收到可写后循环发送待发缓冲直到清空或 EAGAIN。还要同时处理错误、挂断和半关闭。ET 可减少重复通知,但状态机复杂度更高,不应无条件使用。
三、常见错误
- ET 每个事件只 read 一次。
- 使用阻塞 fd,循环第二次 read 卡住整个事件线程。
- 把 EAGAIN 当成连接错误。
- 永久监听可写事件,造成忙循环。
回答自检
- 水平状态通知与边缘变化通知。
- ET 为什么要求非阻塞和读写到 EAGAIN。
- 写事件关注如何避免忙循环。
- 性能收益与状态机复杂度的权衡。
面试官可能追问的问题
- LT 是否也可读到 EAGAIN? 可以,这是一种统一稳健写法。
- 何时关注 EPOLLOUT? 有未发送数据且刚遇到 EAGAIN 时,清空后取消关注。
- ET 一定更快吗? 不一定,取决于活跃度、处理批量和实现复杂度。
10. Reactor、Proactor 和 io_uring 的核心差异是什么?
问题分析
这道题考查是否建立了用户态与内核态边界、网络协议和 I/O 模型的清晰概念边界,并能把类别、职责和限制对应起来。
建议按“结论 → 机制 → 边界”组织回答:
- 先给结论:Reactor 等待 fd 就绪,事件循环收到通知后由应用执行非阻塞 read/write;
- 再讲机制:围绕“两种事件模型 → io_uring 的位置 → 工程边界”说明规则如何生效,把关键对象、时机或状态变化串起来。
- 最后讲边界:结合用户态与内核态边界、网络协议和 I/O 模型说明适用条件、常见误用,以及如何通过代码、编译结果或运行现象验证判断。
- 来源:腾讯基础架构三面
问题讲解
一、两种事件模型
Reactor 等待 fd 就绪,事件循环收到通知后由应用执行非阻塞 read/write;Proactor 提交异步 I/O 操作,系统完成数据传输后通知应用处理结果。前者处理就绪,后者处理完成。具体平台 API 可能混合两种特征,不能只按名称分类。
二、io_uring 的位置
Linux io_uring 使用提交队列和完成队列批量交换请求/结果,覆盖文件、网络等多类操作,减少部分系统调用和数据结构开销。它更接近完成式接口,但具体操作可能在内核异步执行、轮询或退化,性能依内核版本、设备、注册缓冲和工作负载。
| 模型 | 应用收到什么 | 数据搬运由谁发起 | 典型接口 |
|---|---|---|---|
| Reactor | 就绪事件 | 应用收到事件后调用 I/O | epoll/kqueue |
| Proactor | 完成事件 | 系统执行已提交操作 | IOCP 理念 |
| io_uring | 提交/完成队列项 | 按具体操作由内核处理 | Linux io_uring |
| Reactor 在收到就绪后才调用 I/O;完成式读取在提交前就须准备结果缓冲区,并保持其有效,直到原操作明确终止且不会再访问该缓冲区。取消请求自己的完成通知不自动等于原操作已终止。 |

三、工程边界
无论模型如何,都需处理短读写、取消、超时、连接关闭、缓冲区所有权和背压。本篇普通单次操作中,异步提交后缓冲区必须存活到原操作的终结完成;取消也可能与正常完成竞态,要同时追踪原操作的完成,不能只看到取消请求 CQE 就释放缓冲。多次完成、零拷贝发送等特殊操作另有生命周期协议。参见io_uring 取消说明。io_uring 是 Linux 特定机制,不应包装成无条件跨平台结论。
四、常见错误
- 把 Reactor 称为“内核已经读完数据”。
- 提交异步 I/O 后立即释放缓冲区。
- 认为 io_uring 对所有负载都比 epoll 快。
- 忽略完成、超时和取消可能同时竞争。
回答自检
- 就绪通知与完成通知的根本差异。
- Reactor 应用主动 I/O 的流程。
- io_uring 的 SQ/CQ 交互模型。
- 缓冲区生命周期、取消和背压。
- 平台机制与抽象模型的边界。
面试官可能追问的问题
- Reactor 线程何时执行 read? 收到就绪事件后由应用主动调用。
- io_uring 为什么能批量? 用户与内核通过共享环形队列提交和收割多个请求。
- 如何做背压? 限制在途请求、发送缓冲和任务队列,完成后再释放额度。
- 如何跨平台抽象? 抽象完成语义和缓冲所有权,后端分别适配 epoll、kqueue、IOCP/io_uring。





