系统调用
100%

内核 · 第 3 课

系统调用

学习用户空间代码如何调用 Linux 内核服务,以及如何使用 `strace` 安全地检查调用。

系统调用是进入内核的一种规定入口,用户空间代码通过它请求打开文件、映射内存、创建进程或发送网络数据等操作。执行请求前,内核会验证参数、凭据、对象状态和安全策略。

库与系统调用 ABI

应用程序通常调用 C 库函数,而不是自行编写与架构相关的进入指令。库包装函数按照系统调用 ABI 准备寄存器和内存,进入内核,再将结果转换成该语言层面的约定。

函数与系统调用并不总是一一对应:

  • 一个库函数可以组合多个系统调用
  • 有些函数完全在用户空间中运行
  • 经过优化的 vDSO 函数可以在不进行完整模式切换的情况下取得某些由内核维护的数据
  • 一个系统调用可以支持许多高层 API

典型的 libc 系统调用包装函数会做什么?

进入和退出内核

包装函数将系统调用号和参数放在架构规定的位置,然后执行进入指令,例如 x86-64 上的 syscall 或 AArch64 上的 svc。处理器切换到配置好的特权入口点,内核随后分派该请求。

操作完成后,内核返回一个值或错误指示。C 库包装函数通常在出错时返回 -1,并设置线程局部的 errno。其他语言和运行时会公开不同的错误类型。

把每个入口都称为“软件中断”并不能准确描述现代架构;陷阱、快速系统调用指令和监管者调用以不同方式实现相近的受控转换。

谁负责验证系统调用的参数和授权?

编号与兼容性

系统调用号和调用约定与架构相关。同一个符号调用在另一种 ABI 上可能具有不同的编号或结构布局。内核版本可以添加系统调用,而稳定的用户空间 ABI 则以保留现有行为为目标。

非特权进程不能向正在运行的内核系统调用表中任意插入新处理程序。扩展接口需要编写内核代码并谨慎设计 ABI。seccomp 等功能可以过滤允许进程发出的调用,但不能创建新的内核实现。

为什么应用程序不应硬编码来自另一种架构的系统调用号?

使用 `strace` 跟踪

跟踪一条简单命令,并将输出单独保存:

$ strace -o trace.log -- ls

在获得授权的情况下,可用 -f 跟踪子进程,也可以使用表达式缩小输出范围:

$ strace -f -e trace=%file -o trace.log -- command

strace 可能暴露路径、参数、来自环境的数据、网络地址、文件内容片段,以及被错误放入参数的凭据。应使用严格权限保存跟踪记录,并按照事件数据管理策略将其删除。

strace 主要观察什么?

谨慎解读跟踪记录

跟踪会改变时序,并可能产生很大开销。失败的调用可能只是预期的探测,最终可见的错误也可能源于更早的操作或应用程序策略。应解析文件描述符、跟踪进程关系,并与应用程序日志相互印证。

权限和 ptrace 安全策略会限制可以跟踪哪些进程。未经授权,不要附加到其他用户的进程或生产进程;暂停和时序变化都可能影响服务行为。

跟踪记录中出现一次失败的系统调用,是否一定表示应用程序已损坏?

课程已完成

你已完成 系统调用

现在,你可以从库 API 一直追踪到经过验证的内核工作。

  • 区分高层函数和系统调用 ABI。

  • 理解架构进入指令与受控内核分派的关系。

  • 将系统调用号和结构视为架构特有内容。

  • 使用过滤后的 strace 输出,同时保护敏感数据。

  • 结合应用程序上下文解读失败和跟踪开销。

保存学习进度

创建免费账户即可保存本课进度,并在任意设备上继续学习。

创建免费账户
下一节
返回 内核