轉自:http://blog.chinaunix.net/uid-25374603-id-3401045.html
說明:
系統調用是內核和應用程序間的接口,應用程序要訪問硬件設備和其他操作系統資源,可以通過系統調用來完成。
在linux中,系統調用是用戶空間訪問內核的一種手段,除異常和中斷外,他們是進入內核的合法入口。系統調用的數量很少,在i386上只有大概300個左右。
應用程序員通過C庫中的應用程序接口(API)而不是直接通過系統調用來編程。
C庫中的函數可以不調用系統調用,也可以只是簡單封裝一個系統調用,還可以通過調用多個系統調用來實現一個功能。
linux>本身提供的一組宏來對系統調用直接進行訪問。man 2 syscall。
從程序員的角度來看,系統調用無關緊要,他們只需要跟API打交道就可以了;
從內核的角度來看,內核只跟系統調用打交道,庫函數及應用程序怎么使用系統調用不是內核所關心的。
linux內核中所有的系統調用函數都用sys_開頭。
函數定義中的asmlinkage宏,用於通知編譯器,使用局部堆棧來傳遞參數;
函數定義中的FASTCALL宏,用於通知編譯器,使用寄存器來傳遞參數。
如果上面兩個宏都沒有,則使用默認傳參規則,前4個參數通過R0~R3寄存器傳遞,其余更多的參數通過棧傳遞。
調用鏈:APP --> LIB --> kernel (syscall) --> module --> hardware
因為系統調用要從用戶空間進入內核空間,所以不可能通過簡單的函數調用完成,必須通過一些處理器支持的特殊機制(所謂的軟中斷)。
在x86上,這一特殊機制就是匯編指令int $0x80, 而在arm上,就是匯編指令SWI。
這條指令被封裝到C庫中的函數里,當程序執行到這一條指令后,cpu會進入一個特殊的異常模式(或軟中斷模式),並將程序指針跳轉到特點的位置(如arm為中斷向量表的0x8處)。
內核中實現了很多的系統調用,這些系統調用的地址被按順序放在一個系統調用表中,這個表是一個名為sys_call_table的數組,共有NR_syscalls個表項。
通過這個表,就可以調用到內核定義的所有sys_函數。
調用匯編指令int $0x80 或SWI 時,要同時傳遞一個系統調用號,這個系統調用號將作為索引,從sys_call_table中選擇對應的系統調用。
int80將系統調用號保存在eax寄存器中,而SWI將其直接集成在指令中(如SWI 0x124)。
過程:swi 系統調用號 --> 系統調用表 --> 系統調用
內核中處理系統調用的函數定義在arch/x86/kernel/entry.s中的system_call,而arm系統在arch/arm/kernel/entry-common.s中的vector_swi。
x86系統的系統調用表定義在arch/x86/kernel/syscall_table.s(或直接定義在entry.s)中,而arm定義在arch/arm/kernel/calls.s中。
x86系統調用號定義在arch/x86/include/asm/unistd.h中,arm系統調用號定義在arch/arm/include/asm/unistd.h中。
系統調用必須仔細檢查傳入參數的有效性,尤其是用戶提供的指針,必須確保:
*指針指向的內存區域屬於用戶空間,進程不能哄騙內核去讀內核空間的數據。
*指針指向的內存區域屬於進程的地址空間,不能哄騙內核去讀其他進程的數據。
*進程不能繞過內存訪問權限。
內核在執行系統調用的時候處於進程上下文,可以休眠,也可以被搶占,所以必須保證系統調用是可重入的。
跟蹤linux內核中arm架構的sys_getpid()系統調用
系統調用號在文件arch/arm/include/asm/unistd.h中,如下:
說明:可見sys_getpid()的系統調用號是20,此調用號其實就是系統調用表(數組)的下標。所以系統調用表中sys_getpid()肯定在第20項。
特別注意:系統調用號17之類,此系統調用已經棄用,但為了兼容性及不至於日后混亂,所以調用號不能重用,只能空着(跳過).
系統調用表在文件arch/arm/kernel/calls.s,如下:
說明:可見sys_getpid()在系統調用表中第20項,和其系統調用號一致。
特別注意:系統調用號17對應的表項,對於已經棄用的系統調用,linux系統統一賦予sys_ni_syscall()系統調用。
系統調用的聲明在文件 include/linux/syscalls.h,如下:
系統調用的實現在文件kernel/timer.c,如下:
說明:源代碼中不能直接找到sys_getpid()的實現代碼,因為64為系統的BUG,所以源代碼中的系統調用sys_ABC,都用SYSCALL_DEFINEx(ABC)封裝了一層,以解決BUG。
SYSCALL_DEFINE在文件include/linux/syscalls.h,如下:
手工添加一個自己實現的系統調用:
首先,模仿原來代碼,在文件arch/arm/include/asm/unistd.h添加一個系統調用號,如下:
特別說明:自己新加的系統調用號只能在原來最大值得基礎上加1,所以我的系統調用號是361,對應系統調用是sys_mysyscall()
然后,模仿原來代碼,在文件arch/arm/kernel/calls.S添加一個系統調用表項,如下:
最后:編寫系統調用的實現代碼,此代碼必須能保證編譯進內核,如下:
因為我們知道文件init/main.c一定編譯進內核,所以我們的實現代碼在此文件中添加。
特別注意:系統調用必須返回long型值。
要使用此系統調用,必須重新編譯內核,並且開發板必須使用新內核,如下:
程序:測試系統調用的實現效果
創建目錄/nfsroot/kern/2012-04-16/02/。
創建文件/nfsroot/kern/2012-04-16/02/test.c,內容如下:
創建文件/nfsroot/kern/2012-04-16/02/Makefile,內容如下:
在主機端編譯程序,過程如下:
在開發板端運行測試程序,過程如下:
說明:可見getpid的兩種結果一直,我們自己的系統調用361也正確運行。
特別說明:
系統調用號是linux內核維護人員確定的,自己添加的系統調用,其他人開發的應用程序不會使用到,因為只有自己知道有這個系統調用。
這種系統調用需要直接修改內核源代碼,而且必須編譯進內核才能使用,而且系統調用號是自己設定的,所以一般不會這樣手動添加系統調用。
若自己sys_open,sys_read等系統調用,可以通過模塊來實現,從而實現系統調用的復用。