glibc 對系統調用的封裝
在用戶態進程里調用open函數 【 int open(const char *pathname, int flags, mode_t mode) 】
在glibc 源代碼中有個文件 syscalls.list , 里面咧着所有glibc 的函數對應的系統調用。 另外還有一個腳本 make-syscall.sh ,可根據配置文件,對於每個封裝好的系統調用,生成一個文件。 另外還有一個文件 syscall-template.S 使用上面這個宏,定義系統調用的調用方式。
對於任何一個系統調用,會調用 DO_CALL 。 這個宏32位和64位定義不同。
在源代碼注釋中, int 就是interrupt 。中斷的意思。 int $0x80 就是觸發一個軟中斷,通過它就可以陷入(trap) 內核。
在內核啟動時,其中會有一個軟中斷的陷入門,當接收到一個系統調用的時候,相應的文件就會被調用,然后通過 push 和 SAVE_ALL 將當前用戶態的寄存器,保存在 pt_regs 結構中。 進入內核前,會保持所有的寄存器,然后調用 do_syscall_32_irqs_on 。在這里,將系統調用號從eax里面取出來,然后根據系統調用號,在系統調用表中找到相應的函數進行調用。並將找出來的參數取出來,作為函數參數。且參數對於的寄存器與linux 的注釋是一樣的。
當系統調用結束后會接着調用 INRRUPT_RETURN 。定義的是 iret。 此iret指令會將原來的用戶態保持的現場恢復回來,包含代碼段,指令指針寄存器等。用戶態進程恢復執行
64位系統調用過程
將系統調用名稱轉換為系統調用號,放在寄存器rax ,此時是真正進行調用,不中斷了。 改用syscall 指令。並且傳遞參數的寄存器也變了。 syscall 指令使用了特殊的寄存器,叫特殊模塊寄存器(Model Specific Registers 。簡稱 MSR )
系統初始化時,trap_init 除了初始化上面的中斷模式,還會調用 cpu_init -> syscall_init 其中rdmsr 和 wrmsr 是用來讀寫特殊模塊寄存器的。 MSR_LSTAR 就是這樣的一個特殊的寄存器。 當 sys call 指令調用的時候,會從寄存器里面拿出函數的地址調用( entry_SYSCALL_64)
並且在 arch/x86/entry/entry.64.S 中定義了 entry_SYSCALL_64。 里面保持了很多的寄存器到 pt_regs 結構里面。列入用戶態的代碼段,數據段,保存參數的寄存器。 然后調用 entry_SYSCALL64_slow_pat -> do_syscall_64 。在此調用里,從rax 里面拿出系統調用號,根據系統調用號在系統調用表 sys_call_table 中找到相應的函數進行調用,並將寄存器中保存的參數取出來,作為函數參數。 這些參數所對應的寄存器,與Linux 的注釋又是一樣的。
所以,無論是32位,還是64位,都會到系統調用表 sys_call_table 這里來,64位系統調用返回的時候,執行的是 USERGS_SYSRET64 ,返回用戶態的指令變成了 sysretq 。
系統調用表
系統調用表 sys_call_table 的形成:
32位的系統調用表定義在面 arch/x86/entry/syscalls/syscall_32.tbl 文件里
64位的系統調用表定義在面 arch/x86/entry/syscalls/syscall_64.tbl 文件里
系統調用在內核中的實現函數有一個聲明, 聲明在 include/linux/syscalls.h 文件中 ,真正的實現這個系統調用,一般在一個 .c 的文件里面。 列如 sys_open 的實現在 fs/open.c 里面。 其中 SYSCALL_DEFINE3 是一個宏系統調用最多六個參數,會根據參數的數目選擇宏。至此,聲明和實現都好了,接下來編譯的過程中,需要根據 syscall_32.tbl 和 syscall_64.tbl 生成自己的 unistd_32.h 和 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_32.h 和 unistd_64.h 是對應的系統調用號和系統調用實現函數之間的對應關系。
在 arch/x86/entry/syscall_32.c 定義了一個表,里面 include 了這個頭文件,從而所有的 sys_ 系統調用都在這個表里面了。 同理,在文件 arch/x86/entry.syscall_64.c 文件里也是一樣的定義,sys_系統調用也都在這個表里面