SO2 课程 02——系统调用

System Calls

System Calls as Kernel services

 

+-------------+           +-------------+
|   应用程序   |           |   应用程序   |
+-------------+           +-------------+
  |                           |
  |read(fd, buff, len)        |fork()
  |                           |
  v                           v
+---------------------------------------+
|                 内核                  |
+---------------------------------------+

System Call Setup

Linux system call setup

Example of Linux system call setup and handling

     +-------------+   dup2    +-----------------------------+
     |   应用程序   |-----+     |  libc                       |
     +-------------+     |     |                             |
                         +---->| C7590 dup2:                 |
                               | ...                         |
                               | C7592 movl 0x8(%esp),%ecx   |
                               | C7596 movl 0x4(%esp),%ebx   |
                               | C759a movl $0x3f,%eax       |
+------------------------------+ C759f int $0x80             |
|                              | ...                         +<-----+
|                              +-----------------------------+      |
|                                                                   |
|                                                                   |
|                                                                   |
|                                                                   |
|    +------------------------------------------------------------+ |
|    |                         内核                               | |
|    |                                                            | |
+--->|ENTRY(entry_INT80_32)                                       | |
     | ASM_CLAC                                                   | |
     | pushl   %eax                    # pt_regs->orig_ax         | |
     | SAVE_ALL pt_regs_ax=$-ENOSYS    # save rest                | |
     | ...                                                        | |
     | movl   %esp, %eax                                          | |
     | call   do_int80_syscall_32                                 | |
     | ....                                                       | |
     | RESTORE_REGS 4                  # skip orig_eax/error_code | |
     | ...                                                        | |
     | INTERRUPT_RETURN                                           +-+
     +------------------------------------------------------------+

Linux System Call Dispatcher

/* 处理(handle)int $0x80 */
__visible void do_int80_syscall_32(struct pt_regs *regs)
{
    enter_from_user_mode();
    local_irq_enable();
    do_syscall_32_irqs_on(regs);
}

/* Linux x86 32 位系统调用分发器的简化版本 */
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
{
    unsigned int nr = regs->orig_ax;

    if (nr < IA32_NR_syscalls)
        regs->ax = ia32_sys_call_table[nr](regs->bx, regs->cx,
                                           regs->dx, regs->si,
                                           regs->di, regs->bp);
    syscall_return_slowpath(regs);
}

Inspecting dup2 system call

 

System Call Flow Summary

System Call Table

#define __SYSCALL_I386(nr, sym, qual) [nr] = sym,

const sys_call_ptr_t ia32_sys_call_table[] = {
  [0 ... __NR_syscall_compat_max] = &sys_ni_syscall,
  #include <asm/syscalls_32.h>
};
__SYSCALL_I386(0, sys_restart_syscall)
__SYSCALL_I386(1, sys_exit)
__SYSCALL_I386(2, sys_fork)
__SYSCALL_I386(3, sys_read)
__SYSCALL_I386(4, sys_write)
#ifdef CONFIG_X86_32
__SYSCALL_I386(5, sys_open)
#else
__SYSCALL_I386(5, compat_sys_open)
#endif
__SYSCALL_I386(6, sys_close)

系统调用指针参数

指向内核空间的指针

处理无效指针的方法

页面故障处理

  • 写时复制(Copy on Write)、需求分页(demand paging)、交换(swapping):故障地址和引发故障的地址都在用户空间;故障地址有效(在用户地址空间进行检查)。
  • 在系统调用中使用无效指针:引发故障的地址在内核空间;故障地址在用户空间且无效。
  • 内核错误(内核访问无效指针):与上述情况相同。

标记访问用户空间的内核代码

指针检查与故障处理的成本分析

成本 指针检查 故障处理
有效地址 地址空间搜索 可忽略的
无效地址 地址空间搜索 异常表搜索

虚拟动态共享对象(VDSO)

检查 VDSO

 

虚拟系统调用(vsyscalls)

通过系统调用访问用户空间

/* 如果 user_ptr 无效,则返回 -EFAULT */
if (copy_from_user(&kernel_buffer, user_ptr, size))
    return -EFAULT;

/* 只有当 user_ptr 有效时才能工作,否则会导致内核崩溃 */
memcpy(&kernel_buffer, user_ptr, size);

get_user 实现

#define get_user(x, ptr)                                          \
({                                                                \
  int __ret_gu;                                                   \
  register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX);            \
  __chk_user_ptr(ptr);                                            \
  might_fault();                                                  \
  asm volatile("call __get_user_%P4"                              \
               : "=a" (__ret_gu), "=r" (__val_gu),                \
                  ASM_CALL_CONSTRAINT                             \
               : "0" (ptr), "i" (sizeof(*(ptr))));                \
  (x) = (__force __typeof__(*(ptr))) __val_gu;                    \
  __builtin_expect(__ret_gu, 0);                                  \
})

get_user 伪代码

#define get_user(x, ptr)                \
    movl ptr, %eax                      \
    call __get_user_1                   \
    movl %edx, x                        \
    movl %eax, result                   \

get_user_1 实现

.text
ENTRY(__get_user_1)
    mov PER_CPU_VAR(current_task), %_ASM_DX
    cmp TASK_addr_limit(%_ASM_DX),%_ASM_AX
    jae bad_get_user
    ASM_STAC
1:  movzbl (%_ASM_AX),%edx
    xor %eax,%eax
    ASM_CLAC
    ret
ENDPROC(__get_user_1)

bad_get_user:
    xor %edx,%edx
    mov $(-EFAULT),%_ASM_AX
    ASM_CLAC
    ret
END(bad_get_user)

_ASM_EXTABLE(1b,bad_get_user)

异常表条目

/* 异常表条目 */
# define _ASM_EXTABLE_HANDLE(from, to, handler)           \
  .pushsection "__ex_table","a" ;                         \
  .balign 4 ;                                             \
  .long (from) - . ;                                      \
  .long (to) - . ;                                        \
  .long (handler) - . ;                                   \
  .popsection

# define _ASM_EXTABLE(from, to)                           \
  _ASM_EXTABLE_HANDLE(from, to, ex_handler_default)

异常表构建

#define EXCEPTION_TABLE(align)                                    \
  . = ALIGN(align);                                               \
  __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {               \
          VMLINUX_SYMBOL(__start___ex_table) = .;                 \
          KEEP(*(__ex_table))                                     \
          VMLINUX_SYMBOL(__stop___ex_table) = .;                  \
  }

异常表处理

bool ex_handler_default(const struct exception_table_entry *fixup,
                        struct pt_regs *regs, int trapnr)
{
    regs->ip = ex_fixup_addr(fixup);
    return true;
}

int fixup_exception(struct pt_regs *regs, int trapnr)
{
    const struct exception_table_entry *e;
    ex_handler_t handler;

    e = search_exception_tables(regs->ip);
    if (!e)
        return 0;

    handler = ex_fixup_handler(e);
    return handler(e, regs, trapnr);
}