系统调用(System Calls)
Linux 下通过 syscall 指令(x86-64)或 int $0x80(32 位)陷入内核。系统调用号放入 %rax,参数依次放入 %rdi %rsi %rdx %r10 %r8 %r9,返回值在 %rax。
重要:第 4 个参数在 syscall ABI 中使用 %r10 而非 %rcx——因为 syscall 指令会把返回地址存入 %rcx、把 RFLAGS 存入 %r11,这两个寄存器总是被破坏。除此之外,内核保留其余寄存器(除 %rax 返回值外)。
64 位:syscall
常用调用号(x86-64 /usr/include/asm/unistd_64.h):
| 调用 | 号(%rax) | 参数 |
|---|---|---|
read |
0 | fd(rdi), buf(rsi), count(rdx) |
write |
1 | fd(rdi), buf(rsi), count(rdx) |
open |
2 | path(rdi), flags(rsi), mode(rdx) |
close |
3 | fd(rdi) |
lseek |
8 | fd(rdi), offset(rsi), whence(rdx) |
mmap |
9 | addr(rdi), len(rsi), prot(rdx), flags(r10), fd(r8), off(r9) |
ioctl |
16 | fd(rdi), req(rsi), arg(rdx) |
getpid |
39 | 无 |
exit |
60 | code(rdi) |
exit_group |
231 | code(rdi)(终止整个进程组,libc 的 exit 使用) |
# write(1, msg, len)
movq $1, %rax # syscall number: write
movq $1, %rdi # fd = 1 (stdout)
leaq msg(%rip), %rsi # buf(PIC 推荐写法;非 PIE 也可 movq $msg, %rsi 绝对地址)
movq $13, %rdx # count
syscall
# exit(0)
movq $60, %rax
xorq %rdi, %rdi
syscall
32 位:int $0x80
调用号放 %eax,参数 %ebx %ecx %edx %esi %edi %ebp:
movl $4, %eax # write
movl $1, %ebx # fd
movl $msg, %ecx # buf
movl $13, %edx # count
int $0x80
movl $1, %eax # exit
xorl %ebx, %ebx
int $0x80
现代 32 位内核优先走
sysenter快速路径(libc 内部处理),int $0x80是兼容旧路径,可直接使用但较慢。
vDSO:不陷入内核的"系统调用"
gettimeofday、clock_gettime、getcpu 等高频调用由内核把代码映射到每个进程的 vDSO 页,用户态直接执行,不真正陷入内核。用 strace 观察程序时看不到这些调用,属正常现象。
完整最小程序(64 位,直接系统调用,无需 libc)
.section .rodata
msg: .ascii "Hello, World!\n"
.set len, . - msg
.section .text
.globl _start
_start:
movq $1, %rax
movq $1, %rdi
leaq msg(%rip), %rsi # RIP 相对取地址(PIC)
movq $len, %rdx
syscall
movq $60, %rax
xorq %rdi, %rdi
syscall
ld 默认入口符号是 _start;用 ld -e my_entry 可改。经 gcc -nostdlib 链接同样如此。
错误处理
系统调用失败时 %rax 为负的 errno(如 -14 表示 EFAULT)。用户态需取绝对值得到 errno:
cmpq $0, %rax
jge ok # rax >= 0 成功
negq %rax # 失败:rax = -errno -> errno
ok:
另外注意:shell 里 $? 只能看到退出码的低 8 位(0–255),用退出码传递大数值不可靠。