一. 概述
系統調用是應用程序與內核交互的一種方式。系統調用作為一種接口,通過系統調用,應用程序能夠進入操作系統內核,從而使用內核提供的各種資源,比如操作硬件,開關中斷,改變特權模式等等。首先,系統調用是一個軟中斷,既然是中斷那么一般就具有中斷號和中斷處理程序兩個屬性,Linux使用0x80號中斷作為系統調用的入口,而中斷處理程序的地址放在中斷向量表里。
二. 過程
基於linux-2.6.38,以read()系統調用函數為例進行說明。
在用戶空間,read()函數的聲明位於#include<unistd.h>,原型為:ssize_t read(int fd, void *buf, size_t count)。下面是read()函數在用戶空間的定義的偽代碼:
1 ssize_t read(int fd, void *buf, size_t count) 2 { 3 long res; 4 %eax = __NR_read 5 %ebx = fd 6 %ecx = (long)buf 7 %edx= count 8 int $0x80 9 res = %eax 10 return res; 11 }
第4行,用eax寄存器保存read()的系統調用號,在/arch/x86/include/asm/unistd_32.h里定義(#define __NR_read 3);第5~7行,分別將三個參數放入三個寄存器(通過寄存器來傳遞參數);第8行,執行系統調用,進入內核;第9行,獲取eax寄存器所保存的函數返回值。
執行第8行后已經進入了系統內核,由於這是一個中斷,因此程序進入到中斷向量表中記錄0x80號的中斷處理程序,中斷向量表的初始化在/arch/x86/kernel/traps.c中定義:
1 void __init trap_init(void) 2 { 3 ................... 4 5 #ifdef CONFIG_X86_32 6 set_system_trap_gate(SYSCALL_VECTOR, &system_call); 7 set_bit(SYSCALL_VECTOR, used_vectors); 8 #endif 9 ................... 10 }
如第6行所示。SYSCALL_VECTOR是系統調用的中斷號,在/arch/x86/include/asm/irq_vectors.h中定義:
1 #ifdef CONFIG_X86_32 2 # define SYSCALL_VECTOR 0x80 3 #endif
正好是0x80。而system_call是系統調用的中斷處理函數指針,用戶執行int $0x80后會執行到這個函數,它在/arch/x86/kernel/entry_32.S中定義:
1 ENTRY(system_call) 2 RING0_INT_FRAME # can't unwind into user space anyway 3 pushl_cfi %eax # save orig_eax 4 SAVE_ALL 5 GET_THREAD_INFO(%ebp) 6 # system call tracing in operation / emulation 7 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) 8 jnz syscall_trace_entry 9 cmpl $(nr_syscalls), %eax 10 jae syscall_badsys 11 syscall_call: 12 call *sys_call_table(,%eax,4) 13 movl %eax,PT_EAX(%esp) # store the return value
...........
第4行,SAVE_ALL是一個宏,也在這個文件里定義:
1 .macro SAVE_ALL 2 cld 3 PUSH_GS 4 pushl_cfi %fs 5 /*CFI_REL_OFFSET fs, 0;*/ 6 pushl_cfi %es 7 /*CFI_REL_OFFSET es, 0;*/ 8 pushl_cfi %ds 9 /*CFI_REL_OFFSET ds, 0;*/ 10 pushl_cfi %eax 11 CFI_REL_OFFSET eax, 0 12 pushl_cfi %ebp 13 CFI_REL_OFFSET ebp, 0 14 pushl_cfi %edi 15 CFI_REL_OFFSET edi, 0 16 pushl_cfi %esi 17 CFI_REL_OFFSET esi, 0 18 pushl_cfi %edx 19 CFI_REL_OFFSET edx, 0 20 pushl_cfi %ecx 21 CFI_REL_OFFSET ecx, 0 22 pushl_cfi %ebx 23 CFI_REL_OFFSET ebx, 0 24 movl $(__USER_DS), %edx 25 movl %edx, %ds 26 movl %edx, %es 27 movl $(__KERNEL_PERCPU), %edx 28 movl %edx, %fs 29 SET_KERNEL_GS %edx 30 .endm
主要作用就是將各個寄存器壓入棧中。
第9行,比較eax的值是否大於等於nr_syscalls,nr_syscalls是比最大有效系統調用號大1的值,在/arch/x86/kernel/entry_32.S中定義:
1 #define nr_syscalls ((syscall_table_size)/4)
其中syscall_table_size就是系統調用表的大小(單位:字節),syscall_table_size其實是一個數組,數組里存放的是各個系統調用函數的地址,元素類型是long型,除以4剛好是系統調用函數的個數。
如果從eax寄存器傳進來的系統調用號有效,那么就執行第12行,在系統調用表里找到相應的系統調用服務程序,sys_call_table在/arch/x86/kernel/syscall_table_32.S中定義:
1 ENTRY(sys_call_table) 2 .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */ 3 .long sys_exit 4 .long ptregs_fork 5 .long sys_read 6 .long sys_write 7 .long sys_open /* 5 */ 8 .long sys_close 9 .................
*sys_call_table(,%eax,4)指的是sys_call_table里偏移量為%eax*4上的那個值指向的函數,這里%eax=3,那么第5行的sys_read()函數就會被調用。sys_read()在/fs/read_write.c中定義:
1 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) 2 { 3 struct file *file; 4 ssize_t ret = -EBADF; 5 int fput_needed; 6 7 file = fget_light(fd, &fput_needed); 8 if (file) { 9 loff_t pos = file_pos_read(file); 10 ret = vfs_read(file, buf, count, &pos); 11 file_pos_write(file, pos); 12 fput_light(file, fput_needed); 13 } 14 15 return ret; 16 }
可見,參數的形式和用戶空間的一樣。SYSCALL_DEFINE3是一個宏,在/include/linux/syscalls.h中定義:
1 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
SYSCALL_DEFINEx也是一個宏,也在此文件中定義:
1 #define SYSCALL_DEFINEx(x, sname, ...) \ 2 __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) 3 ..... 4 #define __SYSCALL_DEFINEx(x, name, ...) \ 5 asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)) 6 ......
宏展開后,就是聲明了這么一個函數:
asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);
asmlingage是一個宏,定義為:__attribute__((regparm(0))),作用是讓這個函數只從棧上獲取參數(因為之前的SAVE_ALL將參數壓到了棧里面)。
當執行完中斷處理程序后,后面會調用RESTORE_REGS來恢復各個寄存器:
1 .............. 2 CFI_REMEMBER_STATE 3 je ldt_ss # returning to user-space with LDT SS 4 restore_nocheck: 5 RESTORE_REGS 4 # skip orig_eax/error_code 6 ...............
第5行,RESTORE_REGS的定義:
1 .macro RESTORE_REGS pop=0 2 RESTORE_INT_REGS 3 1: popl_cfi %ds 4 /*CFI_RESTORE ds;*/ 5 2: popl_cfi %es 6 /*CFI_RESTORE es;*/ 7 3: popl_cfi %fs 8 /*CFI_RESTORE fs;*/ 9 POP_GS \pop 10 .................
第2行,RESTORE_INT_REGS的定義:
1 .macro RESTORE_INT_REGS 2 popl_cfi %ebx 3 CFI_RESTORE ebx 4 popl_cfi %ecx 5 CFI_RESTORE ecx 6 popl_cfi %edx 7 CFI_RESTORE edx 8 popl_cfi %esi 9 CFI_RESTORE esi 10 popl_cfi %edi 11 CFI_RESTORE edi 12 popl_cfi %ebp 13 CFI_RESTORE ebp 14 popl_cfi %eax 15 CFI_RESTORE eax 16 .endm
到這里差不多了,再對read()跟蹤的話就會涉及文件系統方面的內容,以后會說的。