本节开始系统调用的学习,首先从宏观角度梳理知识框架,避免过早陷入细节。整体流程包括系统调用、文件系统最终到驱动,本章重点分析系统调用的实现原理。
系统调用涉及glibc封装、异常模式、svc指令及内核实现机制。后续文件系统章节将拆解sys_read/write到VFS的分发过程。

本章主要基于ARM64架构展开,这是当前嵌入式SoC的主流架构。专注特定平台能更深入理解底层机制。
1. 系统调用的定义与必要性
通过上节分析的用户态read到驱动fops.read的完整流程,可知open/read/write等都属于系统调用范畴。
int fd = open("test.txt", O_RDONLY);
ssize_t n = read(fd, buf, 128);
ssize_t m = write(fd, buf, 128);
系统调用是Linux内核提供给用户程序运行在内核态的函数接口,成为用户态访问系统资源的唯一入口。

Linux通过内核态与用户态的隔离确保系统安全。用户程序通过系统调用委托内核执行无法直接操作的任务,例如文件操作、进程管理、设备驱动访问等。这些操作本质上是对系统资源的管控,必须通过内核提供的标准化接口——即系统调用来实现。
系统调用构成了Linux内核面向用户空间的完整应用二进制接口(ABI)。
2. glibc的系统调用封装机制
用户空间使用的系统调用由glibc封装实现。作为GNU发布的C运行库,glibc是Linux系统最底层的API,几乎所有其他运行库都依赖于此。
glibc官网:https://www.gnu.org/software/libc/
glibc源码下载:https://ftp.gnu.org/gnu/glibc/
glibc对系统调用的封装考虑跨平台兼容性、POSIX标准符合及多路径I/O等因素。其核心实现可简化为两个步骤:
- 将用户参数保存至CPU寄存器(如fd、buf、count等)
- 执行特定指令触发内核态切换
陷入内核的指令因硬件平台而异:
- AArch64使用
svc 0(Supervisor Call)指令
- x86-64采用
syscall指令(64位快速系统调用入口)
glibc在编译时通过sysdeps目录自动选择对应架构的实现。高层API(如read/write)保持统一,底层汇编指令按架构动态切换。
最新glibc源码中ARM64的实现示例如下(已添加注释):
#define INTERNAL_SYSCALL_RAW(name, nr, args...) \
({ long _sys_result; \
{ \
LOAD_ARGS_##nr (args) /* 1. 参数加载至寄存器 */ \
register long _x8 asm ("x8") = (name); /* 2. 系统调用号 */ \
asm volatile ("svc 0 // syscall " # name \
: "=r" (_x0) \
: "r" (_x8) ASM_ARGS_##nr \
: "memory"); /* 3. 执行svc 0 */ \
_sys_result = _x0; \
} \
_sys_result; })
3. svc指令与异常处理机制
glibc通过svc #0指令完成系统调用切入,这是整个机制的核心。理解该指令需先掌握ARM64架构的特权等级(Exception Level, EL)设计。
ARMv8-A架构定义四个特权层级:
- EL0:用户程序运行层级
- EL1:Linux内核与驱动运行层级
- EL2:虚拟化相关层级
- EL3:安全监控层级
CPU仅在异常发生时从EL0切换至EL1,svc指令正是ARM64提供的异常触发指令。
ARMv8-A将异常划分为四大类,SVC(Supervisor Call)属于同步异常,设计目的是让低特权级软件向高特权级发送服务请求。在Linux中,svc #0被专门用于系统调用入口,这解释了为何系统调用是用户态进入内核的唯一途径。
4. 内核异常处理流程
执行svc指令后,硬件自动完成上下文保存、栈切换和页表切换。控制权移交至Linux内核的异常向量表,入口位于entry.S文件。
处理流程概要:
- 内核识别异常类型为svc后进入el0_svc处理流程
- 经过框架级检查和参数验证后调用invoke_syscall
- 根据系统调用号查找系统调用表,最终执行对应的sys_read/sys_write函数
此后流程进入VFS范畴,本章暂不展开。
5. 核心要点总结
本节梳理了系统调用的整体框架,深入讲解了glibc封装机制、svc指令原理、CPU异常模式及特权等级等关键概念。下节将详细展开流程实现细节。