系统调用 ABI
OpenOS 使用 x86_64 的 syscall 指令实现用户态到内核态的快速切换。这是 x86_64 上最高效的用户→内核转换方式。
| 寄存器 | syscall 入口时 | sysretq 返回时 |
|---|---|---|
RAX | 系统调用号 | 返回值 |
RDI | 参数 1 | 保留 |
RSI | 参数 2 | 保留 |
RDX | 参数 3 | 保留 |
R10 | 参数 4(保留) | 保留 |
RCX | 被 CPU 覆盖(用户 RIP) | 被 CPU 覆盖 |
R11 | 被 CPU 覆盖(用户 RFLAGS) | 被 CPU 覆盖 |
CPU 自动操作
Section titled “CPU 自动操作”执行 syscall 指令时,CPU 自动执行:
- 保存返回地址 -
RIP → RCX - 保存标志寄存器 -
RFLAGS → R11 - 加载内核段 - 从 STAR MSR 加载
CS/SS - 禁用中断 - 清除
RFLAGS中的IF位
执行 sysretq 指令时,CPU 自动执行:
- 恢复返回地址 -
RCX → RIP - 恢复标志寄存器 -
R11 → RFLAGS - 加载用户段 - 从 STAR MSR 加载
CS/SS
RAX = 结果值 (正数)RAX = 错误码 | (1 << 63) (高位为 1)错误检测:
; 检查是否发生错误test rax, raxjs error_handler ; 如果符号位为 1,跳转到错误处理错误码提取:
; 提取错误码(去除高位标志)and rax, 0x7FFFFFFFFFFFFFFF| 编号 | 名称 | 说明 |
|---|---|---|
| 0 | (保留) | — |
| 1 | WRITE | 写入控制台 |
| 2 | READ | 读取输入 |
| 3 | EXIT | 终止进程 |
| 4 | YIELD | 让出 CPU |
| 5 | PORT_CREATE | 创建端口 |
| 6 | SEND | 发送消息 |
| 7 | RECEIVE | 接收消息 |
用户态汇编模板
Section titled “用户态汇编模板”基本系统调用包装器
Section titled “基本系统调用包装器”; OpenOS 系统调用包装器; 输入:rax = 系统调用号, rdi = 参数1, rsi = 参数2, rdx = 参数3; 输出:rax = 结果(检查第 63 位判断是否错误)
section .text
openos_syscall: mov r10, rcx ; 保存参数 4(如果需要) syscall ret
; 示例:SYS_WRITE; 输入:rdi = 缓冲区指针, rsi = 长度openos_write: mov rax, 1 ; SYS_WRITE syscall ret
; 示例:SYS_READ; 输入:rdi = 缓冲区指针, rsi = 最大长度openos_read: mov rax, 2 ; SYS_READ syscall ret
; 示例:SYS_EXIT; 输入:rdi = 退出码openos_exit: mov rax, 3 ; SYS_EXIT syscall ; 不会返回完整示例程序
Section titled “完整示例程序”; user/hello.asm; OpenOS 用户态示例程序
section .data msg: db "Hello from user-space!", 10 msg_len equ $ - msg
section .text global _start
_start: ; 写入消息 mov rax, 1 ; SYS_WRITE lea rdi, [rel msg] ; 缓冲区指针 mov rsi, msg_len ; 长度 syscall
; 检查错误 test rax, rax js .error
; 退出 mov rax, 3 ; SYS_EXIT xor rdi, rdi ; 退出码 0 syscall
.error: ; 错误处理 mov rax, 3 ; SYS_EXIT mov rdi, 1 ; 退出码 1 syscallC/汇编 ABI 总结
Section titled “C/汇编 ABI 总结”| 参数 | 寄存器 | 大小 |
|---|---|---|
| 系统调用号 | RAX | u64 |
| 参数 1 | RDI | u64 |
| 参数 2 | RSI | u64 |
| 参数 3 | RDX | u64 |
| 参数 4 | R10 | u64(保留) |
| 返回值 | RAX | u64 |
MSR 配置
Section titled “MSR 配置”STAR MSR (0xC0000081)
Section titled “STAR MSR (0xC0000081)”位 0-31: SYSCALL 目标地址(RIP)位 32-47: SYSCALL CS/SS(内核段选择子)位 48-63: SYSRET CS/SS(用户段选择子)LSTAR MSR (0xC0000082)
Section titled “LSTAR MSR (0xC0000082)”SYSCALL 入口点地址(RIP)FMASK MSR (0xC0000084)
Section titled “FMASK MSR (0xC0000084)”SYSCALL 时清除的 RFLAGS 位掩码GDT 布局
Section titled “GDT 布局”为了支持 SYSCALL/SYSRET,GDT 必须按特定顺序排列:
Index 0: 空描述符 (0x00)Index 1: 内核代码 (0x08, Ring 0)Index 2: 内核数据 (0x10, Ring 0)Index 3: 用户数据 (0x18, Ring 3) ← 必须在用户代码之前Index 4: 用户代码 (0x20, Ring 3)Index 5-6: TSS注意: 用户数据必须在用户代码之前,这是 SYSRET 的要求。
执行 syscall 时,CPU 自动禁用中断(清除 IF 位)。这意味着:
- 系统调用是原子的 - 不会被中断打断
- 需要手动启用中断 - 如果系统调用处理函数需要中断
- 避免死锁 - 不需要在系统调用开始时禁用中断
- 寄存器传递 - 所有参数通过寄存器传递,无内存访问
- 无内存屏障 - syscall/sysret 不需要内存屏障
- 中断禁用 - 避免在系统调用期间处理中断
- 参数验证 - 需要验证用户态指针
- 内存复制 - 消息传递需要复制数据
- 上下文切换 - 如果需要调度
用户态指针验证
Section titled “用户态指针验证”/// 验证用户态指针fn validate_user_ptr(ptr: *const u8, len: usize) -> bool { // 检查指针是否在用户空间 if ptr as usize >= 0x800000000000 { return false; }
// 检查长度是否合理 if len > MAX_MESSAGE_SIZE { return false; }
// 检查指针是否对齐 if ptr as usize % core::mem::align_of::<u8>() != 0 { return false; }
true}/// 检查系统调用权限fn check_syscall_permission(number: u64) -> bool { // 某些系统调用需要特殊权限 match number { SYS_WRITE | SYS_READ | SYS_EXIT | SYS_YIELD => true, SYS_PORT_CREATE | SYS_SEND | SYS_RECEIVE => true, _ => false, }}查看系统调用跟踪
Section titled “查看系统调用跟踪”使用 GDB 跟踪系统调用:
# 启动 QEMUmake debug
# 在 GDB 中(gdb) break syscall::handle_syscall(gdb) continue
# 查看系统调用参数(gdb) print number(gdb) print arg1(gdb) print arg2(gdb) print arg3问题:系统调用返回 -1
- 可能原因:无效的系统调用号
- 解决:检查 RAX 中的系统调用号
问题:系统调用卡住
- 可能原因:中断未正确处理
- 解决:检查 PIC 初始化和 EOI 发送