linux系統學習1:系統調用

glibc 對系統調用的封裝
在用戶態進程裏面調用 open 函數

int open(const char *pathname, int flags, mode_t mode) 

64 位系統調用過程:

x86_64 下的 sysdep.h 文件

/* The Linux/x86-64 kernel expects the system call parameters in
   registers according to the following table:
    syscall number  rax
    arg 1    rdi
    arg 2    rsi
    arg 3    rdx
    arg 4    r10
    arg 5    r8
    arg 6    r9
......
*/
#define DO_CALL(syscall_name, args)                \
  lea SYS_ify (syscall_name), %rax;                \
  syscall

將系統調用名稱轉換爲系統調用號,放到寄存器 rax。這裏是真正進行調用,不是用中斷了,而是改用 syscall 指令了。並且,通過註釋我們也可以知道,傳遞參數的寄存器也變了。
syscall 指令還使用了一種特殊的寄存器,我們叫特殊模塊寄存器(Model Specific Registers,簡稱 MSR)。這種寄存器是 CPU 爲了完成某些特殊控制功能爲目的的寄存器,其中就有系統調用
在系統初始化的時候,trap_init 除了初始化上面的中斷模式,這裏面還會調用 cpu_init->syscall_init。這裏面有這樣的代碼:  

wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

rdmsr 和 wrmsr 是用來讀寫特殊模塊寄存器的。MSR_LSTAR 就是這樣一個特殊的寄存器,當 syscall 指令調用的時候,會從這個寄存器裏面拿出函數地址來調用,也就是調用 entry_SYSCALL_64。
在 arch/x86/entry/entry_64.S 中定義了 entry_SYSCALL_64


ENTRY(entry_SYSCALL_64)
        /* Construct struct pt_regs on stack */
        pushq   $__USER_DS                      /* pt_regs->ss */
        pushq   PER_CPU_VAR(rsp_scratch)        /* pt_regs->sp */
        pushq   %r11                            /* pt_regs->flags */
        pushq   $__USER_CS                      /* pt_regs->cs */
        pushq   %rcx                            /* pt_regs->ip */
        pushq   %rax                            /* pt_regs->orig_ax */
        pushq   %rdi                            /* pt_regs->di */
        pushq   %rsi                            /* pt_regs->si */
        pushq   %rdx                            /* pt_regs->dx */
        pushq   %rcx                            /* pt_regs->cx */
        pushq   $-ENOSYS                        /* pt_regs->ax */
        pushq   %r8                             /* pt_regs->r8 */
        pushq   %r9                             /* pt_regs->r9 */
        pushq   %r10                            /* pt_regs->r10 */
        pushq   %r11                            /* pt_regs->r11 */
        sub     $(6*8), %rsp                    /* pt_regs->bp, bx, r12-15 not saved */
        movq    PER_CPU_VAR(current_task), %r11
        testl   $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11)
        jnz     entry_SYSCALL64_slow_path
......
entry_SYSCALL64_slow_path:
        /* IRQs are off. */
        SAVE_EXTRA_REGS
        movq    %rsp, %rdi
        call    do_syscall_64           /* returns with IRQs disabled */
return_from_SYSCALL_64:
  RESTORE_EXTRA_REGS
  TRACE_IRQS_IRETQ
  movq  RCX(%rsp), %rcx
  movq  RIP(%rsp), %r11
    movq  R11(%rsp), %r11
......
syscall_return_via_sysret:
  /* rcx and r11 are already restored (see code above) */
  RESTORE_C_REGS_EXCEPT_RCX_R11
  movq  RSP(%rsp), %rsp
  USERGS_SYSRET64

這裏先保存了很多寄存器到 pt_regs 結構裏面,例如用戶態的代碼段、數據段、保存參數的寄存器,然後調用 entry_SYSCALL64_slow_pat->do_syscall_64。


__visible void do_syscall_64(struct pt_regs *regs)
{
        struct thread_info *ti = current_thread_info();
        unsigned long nr = regs->orig_ax;
......
        if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) {
                regs->ax = sys_call_table[nr & __SYSCALL_MASK](
                        regs->di, regs->si, regs->dx,
                        regs->r10, regs->r8, regs->r9);
        }
        syscall_return_slowpath(regs);
}

 在 do_syscall_64 裏面,從 rax 裏面拿出系統調用號,然後根據系統調用號,在系統調用表 sys_call_table 中找到相應的函數進行調用,並將寄存器中保存的參數取出來,作爲函數參數。

系統調用表

64 位的系統調用定義在文件 arch/x86/entry/syscalls/syscall_64.tbl 裏。例如 open 是這樣定義的: 

2  common  open      sys_open

第一列的數字是系統調用號。

第三列是系統調用的名字。

第四列是系統調用在內核的實現函數。

系統調用在內核中的實現函數要有一個聲明。聲明往往在 include/linux/syscalls.h 文件中。例如 sys_open 是這樣聲明的:


asmlinkage long sys_open(const char __user *filename,
                                int flags, umode_t mode);

真正的實現這個系統調用,一般在一個.c 文件裏面,例如 sys_open 的實現在 fs/open.c 裏面,


SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
        if (force_o_largefile())
                flags |= O_LARGEFILE;
        return do_sys_open(AT_FDCWD, filename, flags, mode);
}

 SYSCALL_DEFINE3 是一個宏系統調用最多六個參數,根據參數的數目選擇宏。具體是這樣定義的:


#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)


#define SYSCALL_DEFINEx(x, sname, ...)                          \
        SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)


#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...)                                 \
        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \
                __attribute__((alias(__stringify(SyS##name))));         \
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));  \
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__));      \
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))       \
        {                                                               \
                long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));  \
                __MAP(x,__SC_TEST,__VA_ARGS__);                         \
                __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));       \
                return ret;                                             \
        }                                                               \
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)

接下來,在編譯的過程中,需要根據 syscall_64.tbl 生成 unistd_64.h。生成方式在 arch/x86/entry/syscalls/Makefile 中。

這裏面會使用兩個腳本,其中第一個腳本 arch/x86/entry/syscalls/syscallhdr.sh,會在文件中生成 #define __NR_open;

第二個腳本 arch/x86/entry/syscalls/syscalltbl.sh,會在文件中生成 __SYSCALL(__NR_open, sys_open)。

這樣,unistd_64.h 是對應的系統調用號和系統調用實現函數之間的對應關係。 

在文件 arch/x86/entry/syscall_64.c,定義了這樣一個表,裏面 include 了這個頭文件,這樣所有的 sys_ 系統調用就都在這個表裏面了


/* System call table for x86-64. */
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
  /*
   * Smells like a compiler bug -- it doesn't work
   * when the & below is removed.
   */
  [0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章