调用门之系统调用例如“msleep()”具体实现

🧩 总结

你的系统调用流程长这样:

  1. 用户态:

    • first_main里调用msleep();
    • 函数 sys_call() 构造参数;
    • 执行 lcall 调用门。
  2. CPU:

    • 查找调用门,进入 GDT 中设置的目标(exception_handler_syscall);
    • 切换特权级;
    • 自动压栈 CS/EIP/EFLAGS;
    • 跳到偏移 0,即执行 exception_handler_syscall
  3. 内核态:

    • 保存上下文;
    • 设置段寄存器;
    • 调用 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])

这一串汇编做了以下事:

  1. syscall_args_t 的参数逆序压栈。
  2. 最后使用 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    ; 真正的系统调用处理器

🧩 总结

你的系统调用流程长这样:

  1. 用户态:

    • 函数 sys_call() 构造参数;
    • 执行 lcall 调用门。
  2. CPU:

    • 查找调用门,进入 GDT 中设置的目标(exception_handler_syscall);
    • 切换特权级;
    • 自动压栈 CS/EIP/EFLAGS;
    • 跳到偏移 0,即执行 exception_handler_syscall
  3. 内核态:

    • 保存上下文;
    • 设置段寄存器;
    • 调用 do_handler_syscall() 真正处理系统调用。

🧪 提示调试方法

你可以检查你在设置调用门时,是否使用了类似:

set_call_gate(&gdt[SYSCALL_INDEX], selector_code_kernel, (unsigned)exception_handler_syscall, DPL_USER);

如果你能看到这个设置,那就能确认 lcall 会跳转到 exception_handler_syscall

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值