Hang Zhengyang
English ↗

Week 2:System Call / Trap(高密度版)

先统一术语

  • Trap:用户态执行触发受控转移到内核态(syscall、异常、中断统称入口机制)。
  • System Call:有意触发的 trap,用于请求内核服务。
  • Exception/Fault:执行异常(如 page fault)导致的 trap,语义不同于主动 syscall。

核心不是“怎么进内核”,而是:跨 boundary 的固定成本 + 可变成本分别是什么。

一次 syscall 的成本分解

可抽象为:

  1. 用户态准备参数(寄存器/栈)
  2. 特权级切换(保存必要上下文)
  3. 进入内核入口路径(校验、分发)
  4. 执行内核逻辑(可能阻塞、可能唤醒)
  5. 返回用户态(恢复上下文,继续执行)

固定成本来源:

  • 模式切换和控制流重定向
  • 安全检查(参数、权限、地址合法性)
  • pipeline/BTB 预测状态扰动(微架构相关)

可变成本来源:

  • 内核路径长度(VFS、网络栈、锁竞争)
  • copy_to/from_user
  • 睡眠唤醒与调度

为什么“少 syscall”常常成立

不是因为 syscall “慢一点”,而是它破坏了热路径连续性:

  • 用户态计算被频繁切断,指令局部性下降。
  • 频繁 copy 和边界检查拉高 cycles/req。
  • 高并发下,进入内核后更容易遇到共享资源竞争。

因此经验法则:

  • batch > 单次调用
  • mmap/共享内存 > 频繁 read/write
  • io_uring/async(当适配场景)> 同步小调用风暴

Trap 与页故障的关系

page fault 也是 trap,但语义是“执行无法继续,需内核修复现场”:

  • 缺页分配(anonymous demand paging)
  • COW 复制
  • 权限错误(非法访问 -> 信号/终止)

所以 week1 与 week2 不是两章独立知识,而是同一条边界的两种触发方式:

  • syscall:主动跨边界
  • fault:被动跨边界

接口设计视角:如何“少而稳”

批处理(Batching)

  • 合并多个小请求,摊薄固定切换成本。
  • 代价:更复杂的错误处理与重试语义。

零拷贝路径

  • 避免 user<->kernel 多次拷贝,降低带宽与 cache 污染。
  • 代价:生命周期管理复杂(buffer pinning、引用计数、回收时机)。

共享内存 + 门铃机制

  • 数据面在共享区,控制面用少量 syscall 唤醒。
  • 常见于低延迟撮合、行情分发、日志管线。

观测框架(先证据后优化)

你要能把“慢”拆成三段:

  • 边界切换是否过密(calls/sec)
  • 单次内核路径是否过长(avg cycles / tail latency)
  • 是否有阻塞扩散(run queue 增长、上下文切换激增)

实践中常见指标组合:

  • syscall 频率分布(而非总量)
  • p50/p99 syscall latency
  • voluntary/involuntary context switch
  • 内核热点函数火焰图

高频误判

  • “把函数内逻辑优化 20%,系统就会快很多。”
    -> 如果瓶颈在边界切换,用户态微优化收益很有限。
  • “syscall 都一样慢。”
    -> 错。路径深度、锁、拷贝、阻塞行为差异巨大。
  • “异步一定更快。”
    -> 错。队列化、回调/状态机复杂度和 cache 行为可能反噬。

低延迟系统的落地 checklist

  • 合并可合并的调用,控制 syscall burst。
  • 热路径避免动态分配和可阻塞系统调用。
  • 明确区分数据面与控制面接口。
  • 把边界成本预算写进 SLA(不是事后调参)。