🧩 总结
你的系统调用流程长这样:
-
用户态:
- first_main里调用msleep();
- 函数
sys_call()
构造参数; - 执行
lcall
调用门。
-
CPU:
- 查找调用门,进入 GDT 中设置的目标(
exception_handler_syscall
); - 切换特权级;
- 自动压栈 CS/EIP/EFLAGS;
- 跳到偏移 0,即执行
exception_handler_syscall
。
- 查找调用门,进入 GDT 中设置的目标(
-
内核态:
- 保存上下文;
- 设置段寄存器;
- 调用
do_handler_syscall()
真正处理系统调用。
这段代码是一个自定义的系统调用包装函数,使用了 调用门(call gate) 来从用户空间进入内核态执行系统调用。下面是详细解析:
📌 函数功能
static inline int sys_call (syscall_args_t * args);
这个函数通过 lcall
指令(长调用)和调用门机制,将系统调用的参数传入内核态,执行指定系统调用,并返回结果。
📦 参数结构
syscall_args_t * args;
这个结构体应包含如下字段(从代码可以推断):
typedef struct {
int id;
unsigned long arg0;
unsigned long arg1;
unsigned long arg2;
unsigned long arg3;
} syscall_args_t;
🔐 调用门地址定义
const unsigned long sys_gate_addr[] = {0, SELECTOR_SYSCALL | 0};
这是调用门的“伪指针”,lcall
使用的格式是段选择子(selector)+ 偏移地址(offset)。这里偏移是 0,选择子是 SELECTOR_SYSCALL | 0
,后者假设是定义在 GDT(全局描述符表)中的一个 调用门 条目,权限为 0(即内核级)。
🎯 调用门的好处
- 调用门是 x86 实模式/保护模式提供的一种机制,允许从低权限代码(比如用户态)安全地跳转到高权限代码(比如内核态)。
- 参数通过压栈的方式传入调用门,因此内核代码可以直接从栈中读取参数,无需再从寄存器取。
- 相比传统寄存器传参、软件中断(如 int 0x80),这种方式更安全、可控。
🧠 汇编部分解释
push %[arg3]
push %[arg2]
push %[arg1]
push %[arg0]
push %[id]
lcalll *(%[gate])
这一串汇编做了以下事:
- 将
syscall_args_t
的参数逆序压栈。 - 最后使用
lcall
(带远调用门)调用内核中的系统调用入口函数。
📤 返回值
:"=a"(ret)
这是 GCC 的内联汇编语法,"=a"
表示输出结果放在 EAX 寄存器中,ret
用来接收系统调用返回值。
✅ 小结
这个 sys_call()
函数是一个基于调用门机制的系统调用封装实现:
这是一个非常好的问题,涉及到调用门机制与中断/异常处理的统一框架。虽然你用的是 lcall
(调用门),但为什么最后还是会跳转到一个像 exception_handler_syscall
的异常处理器?我们来分析一下背后的机制。
🔍 一句话解释
你的调用门设置的目标地址(GDT 中的段描述符)其实指向了 exception_handler_syscall
函数,所以 lcall
执行时,就直接跳到这个汇编入口了。
🧠 执行流程详解
1. sys_call()
调用:
lcalll *(%[gate]) // 实际执行的是长调用到某个调用门
它跳转到了一个内核态段(通过 GDT 设置的调用门),偏移为 0。
2. GDT 设置了调用门
在你的系统中,GDT 中有一个调用门,大概是这样设置的(在 C 或汇编中初始化):
set_call_gate(GDT[SYSCALL_INDEX], KERNEL_CS, exception_handler_syscall, DPL_USER);
意思是:
- 用户态可以通过调用门执行
lcall
; - CPU 自动切换到内核栈、内核段;
- 跳转到
exception_handler_syscall
函数。
3. exception_handler_syscall
是真正的系统调用入口
这段代码就像中断或异常的处理器:
exception_handler_syscall:
pusha ; 保存所有通用寄存器
push %ds
push %es
push %fs
push %gs
pushf
mov $(KERNEL_SELECTOR_DS), %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
- 做了标准的上下文保护;
- 设置段寄存器为内核数据段,避免使用用户空间的段;
- 调用你真正的系统调用处理函数:
mov %esp, %eax
push %eax ; syscall_args_t 结构体所在地址
call do_handler_syscall ; 真正的系统调用处理器
🧩 总结
你的系统调用流程长这样:
-
用户态:
- 函数
sys_call()
构造参数; - 执行
lcall
调用门。
- 函数
-
CPU:
- 查找调用门,进入 GDT 中设置的目标(
exception_handler_syscall
); - 切换特权级;
- 自动压栈 CS/EIP/EFLAGS;
- 跳到偏移 0,即执行
exception_handler_syscall
。
- 查找调用门,进入 GDT 中设置的目标(
-
内核态:
- 保存上下文;
- 设置段寄存器;
- 调用
do_handler_syscall()
真正处理系统调用。
🧪 提示调试方法
你可以检查你在设置调用门时,是否使用了类似:
set_call_gate(&gdt[SYSCALL_INDEX], selector_code_kernel, (unsigned)exception_handler_syscall, DPL_USER);
如果你能看到这个设置,那就能确认 lcall
会跳转到 exception_handler_syscall
。