32位系统调用过程

32位系统调用过程

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 指令将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候用户态进程恢复执行。

Viewpoints #

From #

09 | 系统调用:公司成立好了就要开始接项目