文章參考Linux核心修鍊之道。
1. 系統調用概念
大家都知道作業系統的作用是管理電腦的軟硬體資源,但是作業系統要向使用者提供各種各樣的服務,而使用者應用程式訪問這些服務的方式就是通過系統調用。但是一般來說,我們都是通過作業系統封裝好的API(應用編程介面)來間接使用系統調用的。比如在Windows編程中,你可以調用WinAPI,他以C庫的形式給出。比如在Linux當中,我們調用的read函數:int read(int handle, void *buf, int nbyte), 我們一般成為我們在使用系統調用read,因為相對應標準C函數庫裡面有一個fread。但是,實際上我們並沒有直接的實現系統調用,我們通過系統封裝的read函數去調用了read的系統調用。因為他們名字相同,是對應的關係,所以我們很容易誤解。但是系統調用和封裝的系統API函數並不是一一對應的關係,比如系統的execl、execlp、execle、execv、execvp、execve等都是通過execve系統調用來執行一個可執行檔的。系統調用最終必須有明確的操作,使用者應用程式通過系統調用進入核心後,會執行各個系統調用對應的核心功能,即系統調用的服務常式。比如系統調用getpid的服務常式是核心功能sys_getpid。
2. 系統調用表
核心提供哪些核心功能是對外可用的呢,換句話說核心提供哪些系統調用的服務常式呢?這個在系統調用表裡面sys_call_table,他儲存了所有的系統調用對應的服務常式的函數地址,在arch/i386/kernel/syscall_table.S檔案中,如下:
ENTRY(sys_call_table).long sys_restart_syscall/* 0 - old "setup()" system call, used for restarting */.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open/* 5 */.long sys_close.long sys_waitpid.long sys_creat.long sys_link.long sys_unlink/* 10 */
... ...
可以很清楚的看到,所有的系統調用都是遵循一定的命名規範的,即在系統名稱之前加"sys_"首碼,比如exit系統調用對應的系統服務函數為sys_exit。另外系統總共對外提供的系統調用服務是非常有限的,我們可以看到只有兩三百個而已。
3. 系統調用號
系統調用號是什麼呢?上述的每個系統服務常式都有一個系統調用號,使用者是通過系統調用號來調用這些服務常式而不是通過系統調用的名稱。上述的系統調用表格儲存體了所有系統調用的服務常式的地址,系統調用的過程就是從系統調用表中擷取地址去執行。那麼系統調用符合就必然要和系統調用號聯絡到一起,這就涉及到另外一個檔案,在include/asm-i386/unistd.h檔案。
#define __NR_restart_syscall 0#define __NR_exit 1#define __NR_fork 2#define __NR_read 3#define __NR_write 4#define __NR_open 5#define __NR_close 6#define __NR_waitpid 7#define __NR_creat 8#define __NR_link 9#define __NR_unlink 10
跟上述的系統調用表一比較,發現除了首碼一個是sys_一個是__NR_外,他們的名稱是一模一樣的,而且關鍵他們的順序也是相同的。不錯,核心就是將系統調用號作為下標去擷取系統調用表中的系統調用服務常式的函數地址的。
4. 系統調用服務常式
所有的系統調用服務常式集中聲明在include/linux/syscalls.h這個檔案當中,但是他們的定義分布在不同的檔案中。比如getpid系統調用,他的系統服務常式sys_getpid在kernel/timer.c檔案中定義:
asmlinkage long sys_getpid(void){ return current->tgid;}
asmlinkage標記的含義是僅從堆棧中擷取該資料,不從寄存器及其他地方擷取。
5. 系統調用的使用
(1). 通過調用C語言庫封裝函數,比如前面所述的read函數。
(2). 通過syscall函數,原型為int syscall(int number, ...)。
具體可以通過以下getpid的系統調用來展示系統調用的使用。
#include <unistd.h>#include <sys/syscall.h>#include <sys/types.h>//可以從系統調用號檔案中查看#define __NR_gettid 224int main(){ pid_t = tid; tid = syscall(__NR_gettid); printf("call gettid through syscall function, gettid is %d\n", tid); tid = getpid(); printf("call gettid through getpid function, gettid is %d\n", tid); return 0;}
另外大部分的系統調用都包含了一個SYS_符號常量的宏定義來制定調用號,所以上述syscall函數也可以採用如下方式:
tid = syscall(SYS_gettid);
6. 為什麼要使用系統調用
(1)系統調用可以為使用者空間提供訪問硬體資源的統一介面,以至於應用程式不必理會具體硬體訪問操作。比如,讀寫檔案時,應用程式不用去關心磁碟的類型,不用關心是什麼檔案系統,等等。
(2)系統調用對系統有保護作用,保證系統的安全性和穩定性。系統調用規定了使用者進程進入核心的具體方式。使用者只能通過這種方式安全的進入核心,而不準任意在跳入核心,因而可以保護核心的安全性。