轉載自:http://blog.csdn.net/orange_os/article/details/7485069
目錄:
1. Linux系統調用原理
2. 系統調用的實現
3. Linux系統調用分類及列表
4.系統調用、使用者編程介面(API)、系統命令和核心功能的關係
5. Linux系統調用執行個體
6. Linux自訂系統調用 1.系統調用原理
系統調用,顧名思義,說的是作業系統提供給使用者程式調用的一組“特殊”介面。使用者程式可以通過這組“特殊”介面來獲得作業系統核心提供的服務,比如使用者可以通過檔案系統相關的調用請求系統開啟檔案、關閉檔案或讀寫檔案,可以通過時鐘相關的系統調用獲得系統時間或設定定時器等。
從邏輯上來說,系統調用可被看成是一個核心與使用者空間程式互動的介面——它好比一個中間人,把使用者進程的請求傳達給核心,待核心把請求處理完畢後再將處理結果送回給使用者空間。
系統服務之所以需要通過系統調用來提供給使用者空間的根本原因是為了對系統進行“保護”,因為我們知道Linux的Runspace分為核心空間與使用者空間,它們各自運行在不同的層級中,邏輯上相互隔離。所以使用者進程在通常情況下不允許訪問核心資料,也無法使用核心功能,它們只能在使用者空間操作使用者資料,調用使用者空間函數。比如我們熟悉的“hello world”程式(執行時)就是標準的使用者空間進程,它使用的列印函數printf就屬於使用者空間函數,列印的字元“hello word”字串也屬於使用者空間資料。
但是很多情況下,使用者進程需要獲得系統服務(調用系統程式),這時就必須利用系統提供給使用者的“特殊介面”——系統調用了,它的特殊性主要在於規定了使用者進程進入核心的具體位置;換句話說,使用者訪問核心的路徑是事先規定好的,只能從規定位置進入核心,而不準許肆意跳入核心。有了這樣的陷入核心的統一訪問路徑限制才能保證核心安全無虞。我們可以形象地描述這種機制:作為一個遊客,你可以買票要求進入野生動物園,但你必須老老實實地坐在觀光車上,按照規定的路線觀光遊覽。當然,不準下車,因為那樣太危險,不是讓你丟掉小命,就是讓你嚇壞了野生動物。
備忘: 在一些嵌入式作業系統中,作業系統往往通過API的形式提供給使用者一些介面,然後通過靜態連結的方式實現對系統的調用,因此這種模式系統態和使用者態不明顯,即使用者可以在其線程中直接調用系統的函數,並沒有切換到核心態。
2.系統調用的實現
Linux中實現系統調用利用了0x86體繫結構中的軟體中斷。軟體中斷和我們常說的中斷(硬體中斷)不同之處在於,它是通過軟體指令觸發而並非外設引發的中斷,也就是說,又是編程人員開發出的一種異常(該異常為正常的異常),具體的講就是調用int $0x80彙編指令,這條彙編指令將產生向量為0x80的編程異常。
之所以系統調用需要藉助異常來實現,是因為當使用者態的進程調用一個系統調用時,CPU便被切換到核心態執行核心功能,而我們在i386體繫結構部分已經講述過了進入核心——進入高特權層級——必須經過系統的門機制,這裡的異常實際上就是通過系統門陷入核心(除了int 0x80外使用者空間還可以通過int3——向量3、into——向量4 、bound——向量5等異常指令進入核心,而其他異常無法被使用者空間程式利用,都是由系統使用的)。
我們更詳細地解釋一下這個過程。int $0x80指令的目的是產生一個編號為0x80的編程異常,這個編程異常對應的是中斷描述符表IDT中的第128項——也就是對應的系統門描述符。門描述符中含有一個預設的核心空間地址,它指向了系統調用處理常式:system_call()(別和系統調用服務程式混淆,這個程式在entry.S檔案中用組合語言編寫)。
很顯然,所有的系統調用都會統一地轉到這個地址,但Linux一共有2、3百個系統調用都從這裡進入核心後又該如何派發到它們到各自的服務程式去呢。別發昏,解決這個問題的方法非常簡單:首先Linux為每個系統調用都進行了編號(0—NR_syscall),同時在核心中儲存了一張系統調用表,該表中儲存了系統調用編號和其對應的服務常式,因此在系統調入通過系統門陷入核心前,需要把系統調用號一併傳入核心,在x86上,這個傳遞動作是通過在執行int0x80前把調用號裝入eax寄存器實現的。這樣系統調用處理常式一旦運行,就可以從eax中得到資料,然後再去系統調用表中尋找相應服務常式了。
除了需要傳遞系統調用號以外,許多系統調用還需要傳遞一些參數到核心,比如sys_write(unsigned int fd, const char * buf, size_t count)調用就需要傳遞檔案描述符fd、要寫入的內容buf、以及寫入位元組數count等幾個內容到核心。碰到這種情況,Linux會有6個寄存器可被用來傳遞這些參數:eax (存放系統調用號)、 ebx、ecx、edx、esi及edi來存放這些額外的參數(以字母遞增的順序)。具體做法是在system_call( )中使用SAVE_ALL宏把這些寄存器的值儲存在核心態堆棧中.
備忘:
系統調用其實很簡單,就是所以作業系統的API都是通過軟體的中斷動態調用,通過調用int $0x80 觸發軟體中斷,然後通過一些寄存器將參數傳入,實現對作業系統API的調用。 在嵌入式作業系統中有非強制中斷的概念,該非強制中斷是指將硬中斷中次優先順序的任務交給非強制中斷處理,其運行於系統棧中,優先順序高於任務,和本章所提及的軟體中斷有很大的區別,軟體中斷處理和硬中斷處理流程相同,只是該中斷由軟體觸發。
3.系統調用、使用者編程介面(API)、系統命令和核心功能的關係
系統調用並非直接和程式員或系統管理員打交道,它僅僅是一個通過非強制中斷機制(我們後面講述)向核心提交請求,擷取核心服務的介面。而在實際使用中程式員調用的多是使用者編程介面——API,而管理員使用的則多是系統命令。
使用者編程介面其實是一個函數定義,說明了如何獲得一個給定的服務,比如read( )、malloc( )、free( )、abs( )等。它有可能和系統調用形式上一致,比如read()介面就和read系統調用對應,但這種對應並非一一對應,往往會出現幾種不同的API內部用到同一個系統調用,比如malloc( )、free( )內部利用brk( )系統調用來擴大或縮小進程的堆;或一個API利用了好幾個系統調用組合完成服務。更有些API甚至不需要任何系統調用——因為它並不是必需要使用核心服務,如計算整數絕對值的abs()介面。
另外要補充的是Linux的使用者編程介面遵循了在Unix世界中最流行的應用編程介面標準——POSIX標準,這套標準定義了一系列API。在Linux中(Unix也如此),這些API主要是通過C庫(libc)實現的,它除了定義的一些標準的C函數外,一個很重要的任務就是提供了一套封裝常式(wrapper routine)將系統調用在使用者空間封裝後供使用者編程使用。
下一個需要解釋一下的問題是核心功能和系統調用的關係。大家不要把核心功能想像的過於複雜,其實它們和普通函數很像,只不過在核心實現,因此要滿足一些核心編程的要求。系統調用是一層使用者進入核心的介面,它本身並非核心功能,進入核心後,不同的系統調用會找到對應到各自的核心功能——換個專業說法就叫:系統調用服務常式。實際上針對請求提供服務的是核心功能而非調用介面。
比如系統調用 getpid實際上就是調用核心功能sys_getpid。
asmlinkage long sys_getpid(void)
{
return current->tpid;
}
Linux系統中存在許多核心功能,有些是核心檔案中自己使用的,有些則是可以export出來供核心其他部分共同使用的,具體情況自己決定。
核心公開的核心功能——export出來的——可以使用命令ksyms 或 cat /proc/ksyms來查看。另外,網上還有一本歸納分類核心功能的書叫作《The Linux Kernel API Book》,有興趣的讀者可以去看看。
總而言之,從使用者角度向核心看,依次是系統命令、編程介面、系統調用和核心功能。在講述了系統調用實現後,我們會回過頭來看看整個執行路徑。
備忘: 核心功能是作業系統自己使用的一些函數,它不對外展現,不提供給使用者使用,因此介面可以變化。 使用者編程介面API是直接呈現給使用者的介面,它可以使用多個系統調用構造出一個API,也可以一個系統調用被多個API使用,同時API也不可以使用系統調用,Linux的API有別於ucos作業系統的API,後者直接調用API函數進行靜態串連,系統代碼也串連到API中。 命令在我看來應該是可執行檔程式,它單獨將API編譯成可執行檔檔案進行處理。
4. Linux系統調用分類及列表
以下是Linux系統調用的一個列表,包含了大部分常用系統調用和由系統調用派生出的的函數。這可能是你在互連網上所能看到的唯一一篇中文注釋的Linux系統調用列表,即使是簡單的字母序英文列表,能做到這麼完全也是很罕見的。
按照慣例,這個列表以manpages第2節,即系統調用節為藍本。按照筆者的理解,對其作了大致的分類,同時也作了一些小小的修改,刪去了幾個僅供核心使用,不允許使用者調用的系統調用,對個別本人稍覺不妥的地方作了一些小的修改,並對所有列出的系統調用附上簡要注釋。
其中有一些函數的作用完全相同,只是參數不同。(可能很多熟悉C++朋友馬上就能聯想起函數重載,但是別忘了Linux核心是用C語言寫的,所以只能取成不同的函數名)。還有一些函數已經過時,被新的更好的函數所代替了(gcc在連結這些函數時會發出警告),但因為相容的原因還保留著,這些函數我會在前面標上“*”號以示區別。
Linux系統調用很多地方繼承了Unix的系統調用,但Linux相比傳統Unix的系統調用做了很多揚棄,它省去了許多Unix系統冗餘的系統調用,僅僅保留了最基本和最有用的系統調用,所以Linux全部系統調用只有250個左右(而有些作業系統系統調用多達1000個以上)。
系統調用主要分為以下幾類: 控制硬體——系統調用往往作為硬體資源和使用者空間的抽象介面,比如讀寫檔案時用到的write/read調用。 設定系統狀態或讀取核心資料——因為系統調用是使用者空間和核心的唯一通訊手段,所以使用者佈建系統狀態,比如開/關某項核心服務(設定某個核心變數),或讀取核心資料都必須通過系統調用。比如getpgid、getpriority、setpriority、sethostname 進程管理——一系統調用介面是用來保證系統中進程能以多任務在虛擬記憶體環境下得以運行。比如 fork、clone、execve、exit等
2.1進程式控制制:
| fork |
建立一個新進程 |
| clone |
按指定條件建立子進程 |
| execve |
運行可執行檔 |
| exit |
中止進程 |
| _exit |
立即中止當前進程 |
| getdtablesize |
進程所能開啟的最大檔案數 |
| getpgid |
擷取指定進程組標識號 |
| setpgid |
設定指定進程組標誌號 |
| getpgrp |
擷取當前進程組標識號 |
| setpgrp |
設定當前進程組標誌號 |
| getpid |
擷取進程標識號 |
| getppid |
擷取父進程標識號 |
| getpriority |
擷取調度優先順序 |
| setpriority |
設定調度優先順序 |
| modify_ldt |
讀寫進程的本地描述表 |
| nanosleep |
使進程睡眠指定的時間 |
| nice |
改變分時進程的優先順序 |
| pause |
掛起進程,等待訊號 |
| personality |
設定進程運行域 |
| prctl |
對進程進行特定操作 |
| ptrace |
進程跟蹤 |
| sched_get_priority_max |
取得靜態優先順序的上限 |
| sched_get_priority_min |
取得靜態優先順序的下限 |
| sched_getparam |
取得進程的調度參數 |
| sched_getscheduler |
取得指定進程的調度策略 |
| sched_rr_get_interval |
取得按RR演算法調度的即時進程的時間片長度 |
| sched_setparam |
設定進程的調度參數 |
| sched_setscheduler |
設定指定進程的調度策略和參數 |
| sched_yield |
進程主動讓出處理器,並將自己等候調度隊列隊尾 |
| vfork |
建立一個子進程,以供執行新程式,常與execve等同時使用 |
| wait |
等待子進程終止 |
| wait3 |
參見wait |
| waitpid |
等待指定子進程終止 |
| wait4 |
參見waitpid |
| capget |
擷取進程許可權 |
| capset |
設定進程許可權 |
| getsid |
擷取會晤標識號 |
| setsid |
設定會晤標識號 |
1.2檔案操作
| fcntl |
檔案控制 |
| open |
開啟檔案 |
| creat |
建立新檔案 |
| close |
關閉檔案描述字 |
| read |
讀檔案 |
| write |
寫檔案 |
| readv |
從檔案讀入資料到緩衝數組中 |
| writev |
將緩衝數組裡的資料寫入檔案 |
| pread |
對檔案隨機讀 |
| pwrite |
對檔案隨機寫 |
| lseek |
移動檔案指標 |
| _llseek |
在64位地址空間裡移動檔案指標 |
| dup |
複製已開啟的檔案描述字 |
| dup2 |
按指定條件複製檔案描述字 |
| flock |
檔案加/解鎖 |
| poll |
I/O多路轉換 |
| truncate |
截斷檔案 |
| ftruncate |
參見truncate |
| umask |
設定檔案許可權掩碼 |
| fsync |
把檔案在記憶體中的部分寫回磁碟 |
1.3檔案系統操作
| access |
確定檔案的可存取性 |
| chdir |
改變當前工作目錄 |
| fchdir |
參見chdir |
| chmod |
改變檔案方式 |
| fchmod |
參見chmod |
| chown |
改變檔案的屬主或使用者組 |
| fchown |
參見chown |
| lchown |
參見chown |
| chroot |
改變根目錄 |
| stat |
取檔案狀態資訊 |
| lstat |
參見stat |
| fstat |
參見stat |
| statfs |
取檔案系統資訊 |
| fstatfs |
參見statfs |
| readdir |
讀取目錄項 |
| getdents |
讀取目錄項 |
| mkdir |
建立目錄 |
| mknod |
建立索引節點 |
| rmdir |
刪除目錄 |
| rename |
檔案改名 |
| link |
建立連結 |
| symlink |
建立符號連結 |
| unlink |
刪除連結 |
| readlink |
讀符號連結的值 |
| mount |
安裝檔案系統 |
| umount |
卸下檔案系統 |
| ustat |
取檔案系統資訊 |
| utime |
改變檔案的訪問修改時間 |
| utimes |
參見utime |
| quotactl |
控制磁碟配額 |
1.4系統控制
| ioctl |
I/O總控制函數 |
| _sysctl |
讀/寫系統參數 |
| acct |
啟用或禁止進程記賬 |
| getrlimit |
擷取系統資源上限 |
| setrlimit |
設定系統資源上限 |
| getrusage |
擷取系統資源使用方式 |
| uselib |
選擇要使用的二進位函數庫 |
| ioperm |
設定連接埠I/O許可權 |
| iopl |
改變進程I/O權限等級 |
| outb |
低級連接埠操作 |
| reboot |
重新啟動 |
| swapon |
開啟分頁檔和裝置 |
| swapoff |
關閉分頁檔和裝置 |
| bdflush |
控制bdflush守護進程 |
| sysfs |
取核心支援的檔案系統類型 |
| sysinfo |
取得系統資訊 |
| adjtimex |
調整系統時鐘 |
| alarm |
設定進程的鬧鐘 |
| getitimer |
擷取計時器值 |
| setitimer |
設定計時器值 |
| gettimeofday |
取時間和時區 |
| settimeofday |
設定時間和時區 |
| stime |
設定系統日期和時間 |
| time |
取得系統時間 |
| times |
取進程已耗用時間 |
| uname |
擷取當前UNIX系統的名稱、版本和主機等資訊 |
| vhangup |
掛起當前終端 |
| nfsservctl |
對NFS守護進程進行控制 |
| vm86 |
進入類比8086模式 |
| create_module |
建立可裝載的模組項 |
| delete_module |
刪除可裝載的模組項 |
| init_module |
初始化模組 |
| query_module |
查詢模組資訊 |
|