Content #
i386 目录下的 sysdep.h 文件。
/* Linux takes system call arguments in registers:
syscall number %eax call-clobbered
arg 1 %ebx call-saved
arg 2 %ecx call-clobbered
arg 3 %edx call-clobbered
arg 4 %esi call-saved
arg 5 %edi call-saved
arg 6 %ebp call-saved
......
*/
#define DO_CALL(syscall_name, args) \
PUSHARGS_##args \
DOARGS_##args \
movl $SYS_ify (syscall_name), %eax; \
ENTER_KERNEL \
POPARGS_##args
将请求参数放在寄存器里面,根据系统调用的名称,得到系统调用号,放在寄存器 eax 里面,然后执行 ENTER_KERNEL。
这里面的 ENTER_KERNEL 是什么呢?
# define ENTER_KERNEL int $0x80
int 就是 interrupt,也就是“中断”的意思。int $0x80 就是触发一个软中断,通过它就可以陷入(trap)内核。
在内核启动的时候,还记得有一个 trap_init(),其中有这样的代码:
set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);
这是一个软中断的陷入门。当接收到一个系统调用的时候,entry_INT80_32 就被调用了。
ENTRY(entry_INT80_32)
ASM_CLAC
pushl %eax /* pt_regs->orig_ax */
SAVE_ALL pt_regs_ax=$-ENOSYS /* save rest */
movl %ebp, %eax
call do_syscall_32_irqs_on
.Lsyscall_32_done:
......
.Lirq_return:
INTERRUPT_RETURN
通过 push 和 SAVE_ALL 将当前用户态的寄存器,保存在 pt_regs 结构里面。进入内核之前,保存所有的寄存器,然后调用 do_syscall_32_irqs_on。它的实现如下:
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
{
struct thread_info *ti = current_thread_info();
unsigned int nr = (unsigned int)regs->orig_ax;
......
if (likely(nr < IA32_NR_syscalls)) {
regs->ax = ia32_sys_call_table[nr](
(unsigned int)regs->bx, (unsigned int)regs->cx,
(unsigned int)regs->dx, (unsigned int)regs->si,
(unsigned int)regs->di, (unsigned int)regs->bp);
}
syscall_return_slowpath(regs);
}
将系统调用号从 eax 里面取出来,然后根据系统调用号,在系统调用表中找到相应的函数进行调用,并将寄存器中保存的参数取出来,作为函数参数。如果仔细比对,就能发现,这些参数所对应的寄存器,和 Linux 的注释是一样的。
根据宏定义,#define ia32_sys_call_table sys_call_table,系统调用就是放在这个表里面。
当系统调用结束之后,在 entry_INT80_32 之后,紧接着调用的是 INTERRUPT_RETURN,能够找到它的定义,也就是 iret。
#define INTERRUPT_RETURN iret
iret 指令将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候用户态进程恢复执行。