--- ingested: true ingestedAt: 2026-05-17 --- 标题: 汇编语言 - 系统调用 链接: https://www.runoob.com/assembly/assembly-syscall.html 内容: 系统调用(System Call)是用户程序与操作系统内核交互的唯一途径。通过系统调用,汇编程序可以读写文件、分配内存、创建进程等。 --- ## 什么是系统调用 用户程序运行在受限的 用户态(User Mode),无法直接访问硬件或执行特权操作。 当程序需要执行 IO 操作、内存分配等内核级功能时,必须通过 系统调用 向操作系统内核请求服务。 系统调用的流程是:用户程序发起系统调用 → CPU 切换到内核态 → 内核执行对应服务 → 返回用户态继续执行。 > 可以把系统调用理解为:**用户程序给操作系统打个电话,请求帮忙做一件自己没有权限做的事情。** --- ## Linux 系统调用机制 在 Linux 32 位系统中,系统调用通过以下步骤完成: 1. 将 系统调用号 放入 EAX 寄存器 2. 将参数放入 EBX、ECX、EDX、ESI、EDI 等寄存器 3. 执行 `int 0x80` 指令触发软件中断 4. CPU 切换到内核态,内核根据 EAX 中的调用号执行对应服务 5. 内核将返回值放入 EAX,切换回用户态继续执行 --- ## 常用系统调用一览 | 系统调用 | 调用号(EAX) | 功能 | 参数 | | ---------- | -------- | ------------- | --------------------------------- | | sys\_exit | 1 | 退出程序 | EBX = 返回值(exit code) | | sys\_fork | 2 | 创建子进程 | 无参数 | | sys\_read | 3 | 读取文件/输入 | EBX=fd, ECX=buf, EDX=count | | sys\_write | 4 | 写入文件/输出 | EBX=fd, ECX=buf, EDX=count | | sys\_open | 5 | 打开文件 | EBX=filename, ECX=flags, EDX=mode | | sys\_close | 6 | 关闭文件 | EBX=fd | | sys\_creat | 8 | 创建文件 | EBX=filename, ECX=mode | | sys\_lseek | 19 | 移动文件指针 | EBX=fd, ECX=offset, EDX=whence | | sys\_brk | 45 | 调整数据段大小(内存分配) | EBX=新地址 | > 完整的系统调用列表可在 `/usr/include/asm/unistd_32.h` 中查看。注意 32 位和 64 位的系统调用号不同。 --- ## sys\_write - 输出字符串 最常用的系统调用,用于向屏幕打印内容: ## 实例 ; 文件路径:write\_syscall.asm ; 使用 sys\_write 输出字符串 section .data msg db 'Hello, runoob! Welcome to assembly.', 0xA len equ $ \- msg section .text global \_start \_start: ; 调用 sys\_write (4) mov eax, 4 ; 系统调用号 4 = sys\_write mov ebx, 1 ; 文件描述符 1 = stdout(标准输出) mov ecx, msg ; 要输出的数据地址 mov edx, len ; 数据长度(字节数) int 0x80 ; 触发系统调用 ; 成功时 EAX 返回实际写入的字节数 ; 退出程序 mov eax, 1 mov ebx, 0 int 0x80 运行结果: $ nasm -f elf32 write_syscall.asm -o write_syscall.o $ ld -m elf_i386 write_syscall.o -o write_syscall $ ./write_syscall Hello, runoob! Welcome to assembly. --- ## sys\_read - 读取用户输入 从标准输入(键盘)读取数据: ## 实例 ; 文件路径:read\_syscall.asm ; 使用 sys\_read 读取用户输入并回显 section .bss buffer resb 64 ; 预留 64 字节输入缓冲区 section .data prompt db 'Please enter your name: ' prompt\_len equ $ \- prompt output\_msg db 'Hello, ' output\_msg\_len equ $ \- output\_msg section .text global \_start \_start: ; 输出提示信息 mov eax, 4 mov ebx, 1 mov ecx, prompt mov edx, prompt\_len int 0x80 ; 读取用户输入 mov eax, 3 ; 系统调用号 3 = sys\_read mov ebx, 0 ; 文件描述符 0 = stdin(标准输入) mov ecx, buffer ; 输入缓冲区地址 mov edx, 64 ; 最多读取 64 字节 int 0x80 ; EAX 返回实际读取的字节数(包含末尾的换行符) mov esi, eax ; 保存实际输入长度到 esi ; 输出 "Hello, " mov eax, 4 mov ebx, 1 mov ecx, output\_msg mov edx, output\_msg\_len int 0x80 ; 输出用户输入的内容 mov eax, 4 mov ebx, 1 mov ecx, buffer mov edx, esi ; 使用实际输入的字节数 int 0x80 ; 退出程序 mov eax, 1 mov ebx, 0 int 0x80 运行结果: $ nasm -f elf32 read_syscall.asm -o read_syscall.o $ ld -m elf_i386 read_syscall.o -o read_syscall $ ./read_syscall Please enter your name: runoob Hello, runoob --- ## sys\_exit - 退出程序 每个程序都必须调用 sys\_exit 来正常退出,否则 CPU 会继续执行后面的垃圾数据导致段错误。 ## 实例 ; 使用不同退出码退出 mov eax, 1 ; 系统调用号 1 = sys\_exit mov ebx, 0 ; 退出码 0 = 正常退出(也可以是非零值) int 0x80 退出码可以在 shell 中通过 `$?` 查看: $ ./program $ echo $? 0 --- ## 系统调用通用模板 编写系统调用时,可以遵循以下通用模板: ## 实例 ; 系统调用通用模板 ; 适用于 Linux 32 位系统 ; 步骤1:将系统调用号放入 EAX mov eax, 系统调用号 ; 步骤2:按顺序放入参数 mov ebx, 第1个参数 mov ecx, 第2个参数 mov edx, 第3个参数 mov esi, 第4个参数 mov edi, 第5个参数 ; 步骤3:触发系统调用 int 0x80 ; 步骤4:检查返回值(在 EAX 中) ; 通常负值表示错误 cmp eax, 0 jl error\_handler ; 如果 EAX < 0,跳转到错误处理 > `int 0x80` 触发后,EAX 存放返回值。大多数系统调用成功时返回非负值,失败时返回负的错误码(如 -1 表示 EPERM)。