系统调用
系统调用是用户程序与操作系统内核交互的接口。本章介绍 Linux 和 Windows 下的系统调用方法。
系统调用概述
用户态与内核态
现代操作系统将程序运行分为两个特权级:
- 用户态(Ring 3):应用程序运行,权限受限
- 内核态(Ring 0):操作系统内核运行,完全权限
系统调用是用户态程序请求内核服务的机制。
系统调用过程
用户程序
│
│ 1. 准备参数
│ 2. 执行系统调用指令
▼
┌─────────────┐
│ 系统调用 │
│ 入口/出口 │
└─────────────┘
│
│ 3. 切换到内核态
▼
┌─────────────┐
│ 内核处理 │
│ 系统调用 │
└─────────────┘
│
│ 4. 返回结果
▼
用户程序继续
Linux 系统调用
x86-64 系统调用
在 x86-64 Linux 中,使用 syscall 指令进行系统调用:
参数传递
| 寄存器 | 用途 |
|---|---|
| RAX | 系统调用号 |
| RDI | 第 1 个参数 |
| RSI | 第 2 个参数 |
| RDX | 第 3 个参数 |
| R10 | 第 4 个参数 |
| R8 | 第 5 个参数 |
| R9 | 第 6 个参数 |
| RAX | 返回值 |
常用系统调用号
| 编号 | 名称 | 描述 |
|---|---|---|
| 0 | read | 读取文件 |
| 1 | write | 写入文件 |
| 2 | open | 打开文件 |
| 3 | close | 关闭文件 |
| 9 | mmap | 内存映射 |
| 12 | brk | 调整堆 |
| 39 | getpid | 获取进程 ID |
| 57 | fork | 创建进程 |
| 59 | execve | 执行程序 |
| 60 | exit | 退出进程 |
| 61 | wait4 | 等待进程 |
| 62 | kill | 发送信号 |
基本系统调用示例
退出程序
; void _start()
_start:
mov rax, 60 ; exit 系统调用号
xor rdi, rdi ; 退出码 0
syscall
输出字符串
section .data
message db "Hello, World!", 0xA
len equ $ - message
section .text
global _start
_start:
; write(stdout, message, len)
mov rax, 1 ; write 系统调用号
mov rdi, 1 ; 文件描述符:stdout
mov rsi, message ; 缓冲区地址
mov rdx, len ; 长度
syscall
; exit(0)
mov rax, 60
xor rdi, rdi
syscall
读取输入
section .bss
buffer resb 1024
section .text
global _start
_start:
; read(stdin, buffer, 1024)
mov rax, 0 ; read 系统调用号
xor rdi, rdi ; 文件描述符:stdin
mov rsi, buffer ; 缓冲区地址
mov rdx, 1024 ; 最大读取字节数
syscall
; RAX = 实际读取的字节数
; exit(0)
mov rax, 60
xor rdi, rdi
syscall
文件操作
打开文件
; int open(const char *filename, int flags, mode_t mode)
; 参数:RDI = 文件名,ESI = 标志,EDX = 模式
; 返回:RAX = 文件描述符(-1 表示错误)
; 标志定义
O_RDONLY equ 0
O_WRONLY equ 1
O_RDWR equ 2
O_CREAT equ 0100o
O_TRUNC equ 01000o
section .data
filename db "test.txt", 0
section .text
mov rax, 2 ; open 系统调用号
mov rdi, filename
mov rsi, O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0644o ; 文件权限
syscall
; RAX = 文件描述符
读写文件
; 假设 RAX = 文件描述符
mov r12, rax ; 保存文件描述符
; 写入数据
mov rax, 1 ; write
mov rdi, r12 ; 文件描述符
mov rsi, buffer ; 数据地址
mov rdx, buffer_len ; 数据长度
syscall
; 关闭文件
mov rax, 3 ; close
mov rdi, r12
syscall
内存操作
内存映射
; void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
; 参数:RDI=addr, RSI=length, RDX=prot, R10=flags, R8=fd, R9=offset
; 返回:RAX = 映射地址
; 保护标志
PROT_READ equ 1
PROT_WRITE equ 2
PROT_EXEC equ 4
; 映射标志
MAP_SHARED equ 1
MAP_PRIVATE equ 2
MAP_ANONYMOUS equ 32
; 分配 4096 字节内存
mov rax, 9 ; mmap 系统调用号
xor rdi, rdi ; 让内核选择地址
mov rsi, 4096 ; 大小
mov rdx, PROT_READ | PROT_WRITE
mov r10, MAP_PRIVATE | MAP_ANONYMOUS
xor r8, r8 ; fd = -1(匿名映射)
dec r8
xor r9, r9 ; offset = 0
syscall
; RAX = 映射地址
进程操作
创建进程
; pid_t fork(void)
mov rax, 57 ; fork 系统调用号
syscall
; RAX = 0(子进程)或子进程 PID(父进程)
test rax, rax
jz child_process
parent_process:
; 父进程代码
jmp done
child_process:
; 子进程代码
done:
执行程序
; int execve(const char *filename, char *const argv[], char *const envp[])
section .data
program db "/bin/ls", 0
argv0 db "ls", 0
argv1 db "-l", 0
argv dq argv0, argv1, 0
envp dq 0
section .text
mov rax, 59 ; execve 系统调用号
mov rdi, program
mov rsi, argv
mov rdx, envp
syscall
; 如果成功,不会返回
32 位系统调用
在 32 位模式下使用 int 0x80 进行系统调用:
; 32 位系统调用
mov eax, 4 ; write 系统调用号
mov ebx, 1 ; stdout
mov ecx, message ; 缓冲区
mov edx, len ; 长度
int 0x80
Windows 系统调用
系统调用方式
Windows 不直接暴露系统调用接口,而是通过 DLL 导出函数:
; Windows x64 调用约定
; 参数:RCX, RDX, R8, R9,然后栈
extern ExitProcess
extern GetStdHandle
extern WriteFile
section .data
message db "Hello, Windows!", 0xD, 0xA
len equ $ - message
section .bss
written resq 1
section .text
global main
main:
sub rsp, 40 ; 影子空间 + 对齐
; GetStdHandle(STD_OUTPUT_HANDLE)
mov rcx, -11 ; STD_OUTPUT_HANDLE
call GetStdHandle
mov r12, rax ; 保存句柄
; WriteFile(hStdOut, message, len, &written, NULL)
mov rcx, r12 ; 文件句柄
mov rdx, message ; 缓冲区
mov r8, len ; 长度
mov r9, written ; 实际写入字节数
mov qword [rsp + 32], 0 ; OVERLAPPED 结构
call WriteFile
; ExitProcess(0)
xor ecx, ecx
call ExitProcess
使用 Windows API
; MessageBox 示例
extern MessageBoxA
section .data
title db "Message", 0
text db "Hello from Assembly!", 0
section .text
global main
main:
sub rsp, 40
; MessageBox(NULL, text, title, MB_OK)
xor rcx, rcx ; hWnd = NULL
mov rdx, text ; 文本
mov r8, title ; 标题
xor r9, r9 ; MB_OK = 0
call MessageBoxA
add rsp, 40
ret
封装系统调用
创建辅助函数
; 辅助函数:输出字符串
; 参数:RSI = 字符串地址
print_string:
push rax
push rdi
push rdx
; 计算长度
mov rdi, rsi
xor eax, eax
mov rcx, -1
cld
repne scasb
not rcx
dec rcx ; RCX = 长度
; write(stdout, rsi, rcx)
mov rax, 1
mov rdi, 1
mov rdx, rcx
syscall
pop rdx
pop rdi
pop rax
ret
; 辅助函数:输出数字
; 参数:RAX = 数字
print_number:
push rax
push rbx
push rcx
push rdx
push rsi
mov rbx, 10
xor ecx, ecx
.divide_loop:
xor edx, edx
div rbx
push dx ; 保存余数
inc ecx
test eax, eax
jnz .divide_loop
mov rsi, rsp
.print_loop:
mov al, [rsi]
add al, '0'
mov [rsp], al
mov rax, 1
mov rdi, 1
mov rdx, 1
syscall
pop ax
loop .print_loop
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
完整示例程序
; 简单的命令行参数打印程序
section .data
newline db 0xA
prefix db "Argument: "
section .text
global _start
_start:
; [rsp] = argc
; [rsp + 8] = argv[0]
; [rsp + 16] = argv[1]
; ...
mov r12, [rsp] ; argc
mov r13, rsp
add r13, 8 ; 指向 argv[0]
xor r14, r14 ; 循环计数器
.print_loop:
cmp r14, r12
jge .done
; 打印前缀
mov rax, 1
mov rdi, 1
mov rsi, prefix
mov rdx, 10
syscall
; 打印参数
mov rsi, [r13 + r14*8]
call print_string
; 打印换行
mov rax, 1
mov rdi, 1
mov rsi, newline
mov rdx, 1
syscall
inc r14
jmp .print_loop
.done:
; exit(0)
mov rax, 60
xor rdi, rdi
syscall
; 辅助函数
print_string:
push rax
push rdi
push rdx
mov rdi, rsi
xor eax, eax
mov rcx, -1
cld
repne scasb
not rcx
dec rcx
mov rax, 1
mov rdi, 1
mov rdx, rcx
syscall
pop rdx
pop rdi
pop rax
ret
小结
本章介绍了系统调用:
- Linux x86-64:使用 syscall 指令,参数通过寄存器传递
- Linux 32 位:使用 int 0x80
- Windows:通过 DLL 导出函数调用 API
- 常用系统调用:read、write、open、close、exit、mmap、fork、execve
系统调用是汇编程序与操作系统交互的基础。下一章将学习汇编与 C 的混合编程。