【Note】《深入理解Linux内核》Chapter 10 :Linux 内核中的系统调用机制全解析
《深入理解Linux内核》Chapter 10 :Linux 内核中的系统调用机制全解析
摘要:系统调用、内核态、用户态、中断门、syscall 表、寄存器传参、x86 调用号、调用路径、安全检查、性能优化
一、什么是系统调用?
系统调用(System Call)是操作系统为用户程序提供的 受控访问内核资源 的接口。
用户态程序不能直接访问内核内存、I/O、文件、进程管理等,必须通过系统调用请求服务。
典型的系统调用包括:
- 文件操作:open(), read(), write(), close()
- 进程控制:fork(), execve(), exit(), wait()
- 内存管理:mmap(), brk()
- 网络通信:socket(), bind(), recv(), send()
二、系统调用的实现机制总览
系统调用流程可分为以下几个步骤:
- 用户程序调用 glibc 封装函数(如
read()
); - glibc 使用特定指令触发 CPU 进入内核态;
- 内核接收调用号和参数,执行对应服务;
- 系统调用完成后返回用户态,恢复现场。
三、系统调用号与调用表
每个系统调用对应一个唯一编号(call number),在源码中定义:
// include/uapi/asm-generic/unistd.h
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
...
3.1 系统调用表
Linux 使用系统调用表(system call table)将编号映射为内核函数指针:
// arch/x86/kernel/syscall_table.S
ENTRY(sys_call_table).quad sys_read.quad sys_write.quad sys_open
当用户程序触发系统调用后,内核根据调用号(保存在寄存器中)查表找到实际执行函数。
四、用户态与内核态的切换机制
4.1 切换指令:int 0x80 与 syscall
指令 | 架构 | 说明 |
---|---|---|
int 0x80 | x86(32 位) | 软中断,早期实现 |
syscall | x86-64 | 新指令,更高效 |
sysenter | Intel x86 | 特定快速调用方式 |
x86-64 上,glibc 封装函数使用 syscall
指令:
mov $60, %rax ; syscall号:exit
mov $0, %rdi ; 参数1:返回码
syscall ; 进入内核
- 系统调用号存入
%rax
- 参数存入
%rdi
,%rsi
,%rdx
,%r10
,%r8
,%r9
- 返回值存入
%rax
五、系统调用的内核路径:从入口到处理函数
以 64 位系统为例,系统调用进入路径如下:
5.1 系统调用入口
// arch/x86/entry/entry_64.S
entry_SYSCALL_64:...call do_syscall_64
5.2 调用处理函数
// arch/x86/kernel/syscall.c
asmlinkage __visible void do_syscall_64(struct pt_regs *regs)
{syscall_nr = regs->orig_rax;...syscall_fn = syscall_table[syscall_nr];return syscall_fn(...);
}
5.3 参数获取与返回
- 参数从
regs
中提取; - 执行调用逻辑;
- 结果写回
%rax
;
六、内核中定义系统调用
6.1 定义系统调用函数
// kernel/sys.c
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{// 实现细节
}
SYSCALL_DEFINEn
宏自动生成封装函数,处理参数验证与展开;- 宏展开后生成
sys_read()
、参数提取与类型转换逻辑。
6.2 注册系统调用
系统调用在构建过程中自动注册进 sys_call_table
:
sys_call_table:.quad sys_read.quad sys_write
修改或添加系统调用需更新
unistd.h
、syscall_table.S
等。
七、系统调用与安全性机制
系统调用是用户空间进入内核的唯一合法入口,内核必须防止以下安全风险:
7.1 用户空间指针校验
copy_from_user()
copy_to_user()
防止内核直接访问非法用户地址。
7.2 访问控制
- 文件权限检查;
- 信号发送需检查目标进程权限;
- 特权系统调用如
mount()
要求 CAP_SYS_ADMIN;
7.3 seccomp 限制调用集
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
或使用 BPF 定义调用白名单:
struct sock_filter prog[] = {BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr_offset),BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_write, 0, 1),BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)
};
八、系统调用的性能优化
8.1 syscall vs 用户空间函数
系统调用涉及:
- 用户态到内核态切换;
- TLB 刷新;
- 中断门上下文保护。
因此较 memcpy()
等纯用户函数慢数十倍。
8.2 减少系统调用开销的方法
- 合并调用(如
readv()
/writev()
); - 避免频繁
gettimeofday()
; - 使用
mmap()
替代频繁读写; - 使用
epoll()
替代反复poll()
;
九、系统调用与 glibc 的关系
glibc 是用户程序的标准 C 库,它通过封装系统调用提供 API 接口:
ssize_t read(int fd, void *buf, size_t count) {return syscall(SYS_read, fd, buf, count);
}
glibc 可提供缓存(如 malloc()
)、错误处理、异步支持。
一些函数并非系统调用,而是用户空间实现(如 printf()
)。
十、系统调用调试与分析工具
工具 | 用途 |
---|---|
strace | 捕捉系统调用及其参数、返回值 |
perf trace | 性能事件与系统调用时间分析 |
ltrace | 跟踪动态库函数调用 |
gdb | 可设置断点在 syscall 封装层或内核 |
/proc/<pid>/syscall | 显示进程当前正在执行的系统调用 |
BPF + bcc 工具 | 内核层系统调用跟踪 |
十一、Linux 的系统调用分类
类型 | 说明 | 示例函数 |
---|---|---|
进程管理 | 创建、终止、等待、执行 | fork(), execve(), wait() |
文件系统 | 打开、读写、关闭、seek | open(), read(), close() |
内存管理 | 分配、映射、调整 | mmap(), brk(), mprotect() |
网络通信 | 套接字、连接、接收发送 | socket(), send(), recv() |
IPC | 管道、共享内存、信号、消息队列 | pipe(), shmget(), kill() |
时间 | 计时、延迟 | nanosleep(), gettimeofday() |
特权操作 | 装载模块、挂载文件系统 | mount(), reboot() |
十二、系统调用兼容性与体系结构差异
Linux 针对不同体系结构定义不同调用号及参数寄存器规范:
架构 | 系统调用号寄存器 | 参数传递顺序 |
---|---|---|
x86-64 | rax | rdi, rsi, rdx, r10, r8, r9 |
x86-32 | eax | ebx, ecx, edx, esi, edi, ebp |
ARM | r7 | r0-r6 |
不同架构维护各自的 syscall 表和入口代码。
十三、自定义系统调用实验建议(内核开发者)
- 修改
arch/x86/entry/syscalls/syscall_64.tbl
添加自定义 syscall 编号; - 在
kernel/sys.c
添加实现函数(SYSCALL_DEFINEx
); - 编译内核并加载;
- 使用内核模块或测试程序调用 syscall;
- 使用
strace
或 BPF 验证调用行为;
十四、与中断/异常/陷阱机制的关系
系统调用本质上是一种用户主动触发的陷阱(trap):
- 与硬件异常(page fault)不同,系统调用是显式行为;
- 使用
int 0x80
或syscall
指令建立陷阱门; - 进入中断门时自动保存上下文并切换内核栈。
十五、总结与对比表
项目 | 描述 |
---|---|
定义方式 | 使用 SYSCALL_DEFINE() 宏定义内核函数 |
注册机制 | 添加到 sys_call_table 映射中 |
调用方式 | 通过特定 CPU 指令触发陷阱进入内核 |
参数传递方式 | 寄存器传参(架构相关) |
安全检查 | 检查用户空间指针、权限、合法性等 |
性能特点 | 上下文切换、用户态-内核态来回切换 |
扩展机制 | 可用 BPF 或 seccomp 进行控制或过滤 |