linux內核剖析(六)Linux系統調用詳解(實現機制分析)

本文介紹了系統調用的一些實現細節。首先分析了系統調用的意義,它們與庫函數和應用程序接口(API)有怎樣的關係。而後,咱們考察了Linux內核如何實現系統調用,以及執行系統調用的連鎖反應:陷入內核,傳遞系統調用號和參數,執行正確的系統調用函數,並把返回值帶回用戶空間。最後討論瞭如何增長系統調用,並提供了從用戶空間訪問系統調用的簡單例子。
參考 《Linux內核設計與實現》讀書筆記(五)- 系統調用html

系統調用概述


計算機系統的各類硬件資源是有限的,在現代多任務操做系統上同時運行的多個進程都須要訪問這些資源,爲了更好的管理這些資源進程是不容許直接操做的,全部對這些資源的訪問都必須有操做系統控制。也就是說操做系統是使用這些資源的惟一入口,而這個入口就是操做系統提供的系統調用(System Call)。在linux中系統調用是用戶空間訪問內核的惟一手段,除異常和陷入外,他們是內核惟一的合法入口。linux

通常狀況下應用程序經過應用編程接口API,而不是直接經過系統調用來編程。在Unix世界,最流行的API是基於POSIX標準的。程序員

操做系統通常是經過中斷從用戶態切換到內核態。中斷就是一個硬件或軟件請求,要求CPU暫停當前的工做,去處理更重要的事情。好比,在x86機器上能夠經過int指令進行軟件中斷,而在磁盤完成讀寫操做後會向CPU發起硬件中斷。算法

中斷有兩個重要的屬性,中斷號和中斷處理程序。中斷號用來標識不一樣的中斷,不一樣的中斷具備不一樣的中斷處理程序。在操做系統內核中維護着一箇中斷向量表(Interrupt Vector Table),這個數組存儲了全部中斷處理程序的地址,而中斷號就是相應中斷在中斷向量表中的偏移量。sql

通常地,系統調用都是經過軟件中斷實現的,x86系統上的軟件中斷由int $0x80指令產生,而128號異常處理程序就是系統調用處理程序system_call(),它與硬件體系有關,在entry.S中用匯編寫。接下來就來看一下Linux下系統調用具體的實現過程。編程

爲何須要系統調用


linux內核中設置了一組用於實現系統功能的子程序,稱爲系統調用。系統調用和普通庫函數調用很是類似,只是系統調用由操做系統核心提供,運行於內核態,而普通的函數調用由函數庫或用戶本身提供,運行於用戶態api

通常的,進程是不能訪問內核的。它不能訪問內核所佔內存空間也不能調用內核函數。CPU硬件決定了這些(這就是爲何它被稱做「保護模式」(詳細參見深刻理解計算機系統-之-內存尋址(二)–存儲保護機制(CPU實模式與保護模式)))。數組

爲了和用戶空間上運行的進程進行交互,內核提供了一組接口。透過該接口,應用程序能夠訪問硬件設備和其餘操做系統資源。這組接口在應用程序和內核之間扮演了使者的角色,應用程序發送各類請求,而內核負責知足這些請求(或者讓應用程序暫時擱置)。實際上提供這組接口主要是爲了保證系統穩定可靠,避免應用程序肆意妄行,惹出大麻煩。安全

系統調用在用戶空間進程和硬件設備之間添加了一箇中間層。該層主要做用有三個:網絡

  • 它爲用戶空間提供了一種統一的硬件的抽象接口。好比當須要讀些文件的時候,應用程序就能夠不去管磁盤類型和介質,甚至不用去管文件所在的文件系統究竟是哪一種類型。

  • 系統調用保證了系統的穩定和安全。做爲硬件設備和應用程序之間的中間人,內核能夠基於權限和其餘一些規則對須要進行的訪問進行裁決。舉例來講,這樣能夠避免應用程序不正確地使用硬件設備,竊取其餘進程的資源,或作出其餘什麼危害系統的事情。

  • 每一個進程都運行在虛擬系統中,而在用戶空間和系統的其他部分提供這樣一層公共接口,也是出於這種考慮。若是應用程序能夠隨意訪問硬件而內核又對此一無所知的話,幾乎就無法實現多任務和虛擬內存,固然也不可能實現良好的穩定性和安全性。在Linux中,系統調用是用戶空間訪問內核的唯一手段;除異常和中斷外,它們是內核唯一的合法入口。

API/POSIX/C庫的區別與聯繫


通常狀況下,應用程序經過應用編程接口(API)而不是直接經過系統調用來編程。這點很重要,由於應用程序使用的這種編程接口實際上並不須要和內核提供的系統調用一一對應。

一個API定義了一組應用程序使用的編程接口。它們能夠實現成一個系統調用,也能夠經過調用多個系統調用來實現,而徹底不使用任何系統調用也不存在問題。實際上,API能夠在各類不一樣的操做系統上實現,給應用程序提供徹底相同的接口,而它們自己在這些系統上的實現卻可能迥異。

在Unix世界中,最流行的應用編程接口是基於POSIX標準的,其目標是提供一套大致上基於Unix的可移植操做系統標準。POSIX是說明API和系統調用之間關係的一個極好例子。在大多數Unix系統上,根據POSIX而定義的API函數和系統調用之間有着直接關係。

Linux的系統調用像大多數Unix系統同樣,做爲C庫的一部分提供以下圖所示。C庫實現了 Unix系統的主要API,包括標準C庫函數和系統調用。全部的C程序均可以使用C庫,而因爲C語言自己的特色,其餘語言也能夠很方便地把它們封裝起來使用。

從程序員的角度看,系統調用可有可無,他們只須要跟API打交道就能夠了。相反,內核只跟系統調用打交道;庫函數及應用程序是怎麼使用系統調用不是內核所關心的。

關於Unix的界面設計有一句通用的格言「提供機制而不是策略」。換句話說,Unix的系統調用抽象出了用於完成某種肯定目的的函數。至幹這些函數怎麼用徹底不須要內核去關心。區別對待機制(mechanism)和策略(policy)是Unix設計中的一大亮點。大部分的編程問題均可以被切割成兩個部分:「須要提供什麼功能」(機制)和「怎樣實現這些功能」(策略)。

區別


api是函數的定義,規定了這個函數的功能,跟內核無直接關係。而系統調用是經過中斷向內核發請求,實現內核提供的某些服務。

聯繫


一個api可能會須要一個或多個系統調用來完成特定功能。通俗點說就是若是這個api須要跟內核打交道就須要系統調用,不然不須要。
程序員調用的是API(API函數),而後經過與系統調用共同完成函數的功能。
所以,API是一個提供給應用程序的接口,一組函數,是與程序員進行直接交互的。
系統調用則不與程序員進行交互的,它根據API函數,經過一個軟中斷機制向內核提交請求,以獲取內核服務的接口。
並非全部的API函數都一一對應一個系統調用,有時,一個API函數會須要幾個系統調用來共同完成函數的功能,甚至還有一些API函數不須要調用相應的系統調用(所以它所完成的不是內核提供的服務)

系統調用的實現原理


基本機制


前文已經提到了Linux下的系統調用是經過0x80實現的,可是咱們知道操做系統會有多個系統調用(Linux下有319個系統調用),而對於同一個中斷號是如何處理多個不一樣的系統調用的?最簡單的方式是對於不一樣的系統調用採用不一樣的中斷號,可是中斷號明顯是一種稀缺資源,Linux顯然不會這麼作;還有一個問題就是系統調用是須要提供參數,而且具備返回值的,這些參數又是怎麼傳遞的?也就是說,對於系統調用咱們要搞清楚兩點:

  1. 系統調用的函數名稱轉換。
  2. 系統調用的參數傳遞。

首先看第一個問題。實際上,Linux中每一個系統調用都有相應的系統調用號做爲惟一的標識,內核維護一張系統調用表,sys_call_table,表中的元素是系統調用函數的起始地址,而系統調用號就是系統調用在調用表的偏移量。在x86上,系統調用號是經過eax寄存器傳遞給內核的。好比fork()的實現:

用戶空間的程序沒法直接執行內核代碼。它們不能直接調用內核空間中的函數,由於內核駐留在受保護的地址空間上。若是進程能夠直接在內核的地址空間上讀寫的話,系統安全就會失去控制。因此,應用程序應該以某種方式通知系統,告訴內核本身須要執行一個系統調用,但願系統切換到內核態,這樣內核就能夠表明應用程序來執行該系統調用了。

通知內核的機制是靠軟件中斷實現的。首先,用戶程序爲系統調用設置參數。其中一個參數是系統調用編號。參數設置完成後,程序執行「系統調用」指令。x86系統上的軟中斷由int產生。這個指令會致使一個異常:產生一個事件,這個事件會導致處理器切換到內核態並跳轉到一個新的地址,並開始執行那裏的異常處理程序。此時的異常處理程序實際上就是系統調用處理程序。它與硬件體系結構緊密相關。

新地址的指令會保存程序的狀態,計算出應該調用哪一個系統調用,調用內核中實現那個系統調用的函數,恢復用戶程序狀態,而後將控制權返還給用戶程序。系統調用是設備驅動程序中定義的函數最終被調用的一種方式。

從系統分析的角度,linux的系統調用涉及4個方面的問題。

響應函數sys_xxx


響應函數名以「sys_」開頭,後跟該系統調用的名字。

例如

系統調用fork()的響應函數是sys_fork()(見Kernel/fork.c),

exit()的響應函數是sys_exit()(見kernel/fork.)。

系統調用表與系統調用號-=>數組與下標


文件include/asm/unisted.h爲每一個系統調用規定了惟一的編號。
系統調用號

在咱們系統中/usr/include/asm/unistd_32.h,能夠經過find / -name unistd_32.h -print查找)
而內核中的頭文件路徑不一樣的內核版本以及不一樣的發行版,文件的存儲結構可能有所區別

linux-3.0
這裏寫圖片描述

linux-2.6

這裏寫圖片描述

假設用name表示系統調用的名稱,那麼系統調用號與系統調用響應函數的關係是:以系統調用號_NR_name做爲下標,可找出系統調用表sys_call_table(見arch/i386/kernel/entry.S)中對應表項的內容,它正好是該系統調用的響應函數sys_name的入口地址。

系統調用表sys_call_table記錄了各sys_name函數在表中的位置,共190項。有了這張表,就很容易根據特定系統調用

sys_call_table

在表中的偏移量,找到對應的系統調用響應函數的入口地址。系統調用表共256項,餘下的項是可供用戶本身添加的系統調用空間。

在Linux中,每一個系統調用被賦予一個系統調用號。這樣,經過這個獨一無二的號就能夠關聯繫統調用。當用戶空間的進程執行一個系統調用的時候,這個系統調用號就被用來指明究竟是要執行哪一個系統調用。進程不會說起系統調用的名稱。

系統調用號至關關鍵,一旦分配就不能再有任何變動,不然編譯好的應用程序就會崩潰。Linux有一個「未實現」系統調用sys_ni_syscall(),它除了返回一ENOSYS外不作任何其餘工做,這個錯誤號就是專門針對無效的系統調用而設的。

由於全部的系統調用陷入內核的方式都同樣,因此僅僅是陷入內核空間是不夠的。所以必須把系統調用號一併傳給內核。在x86上,系統調用號是經過eax寄存器傳遞給內核的。在陷人內核以前,用戶空間就把相應系統調用所對應的號放入eax中了。這樣系統調用處理程序一旦運行,就能夠從eax中獲得數據。其餘體系結構上的實現也都相似。

內核記錄了系統調用表中的全部已註冊過的系統調用的列表,存儲在sys_call_table中。它與體系結構有關,通常在entry.s中定義。這個表中爲每個有效的系統調用指定了唯一的系統調用號。sys_call_table是一張由指向實現各類系統調用的內核函數的函數指針組成的表:
system_call()函數經過將給定的系統調用號與NR_syscalls作比較來檢查其有效性。若是它大於或者等於NR syscalls,該函數就返回一ENOSYS。不然,就執行相應的系統調用。
這裏寫圖片描述

call *sys_ call-table(,%eax, 4)

 

因爲系統調用表中的表項是以32位(4字節)類型存放的,因此內核須要將給定的系統調用號乘以4,而後用所得的結果在該表中查詢其位置

進程的系統調用命令轉換爲INT 0x80中斷的過程


宏定義_syscallN()include/asm/unisted.h)用於系統調用的格式轉換和參數的傳遞。N取0~5之間的整數。

參數個數爲N的系統調用由_syscallN()負責格式轉換和參數傳遞。系統調用號放入EAX寄存器,啓動INT 0x80後,規定返回值送EAX寄存器。

系統調用功能模塊的初始化


對系統調用的初始化也就是對INT 0x80的初始化。

系統啓動時,彙編子程序setup_idt(見arch/i386/kernel/head.S)準備了1張256項的idt表,由start_kernel()(見init/main.c),trap_init()(見arch/i386/kernel/traps.c)調用的C語言宏定義set_system_gate(0x80,&system_call)(見include/asm/system.h)設置0x80號軟中斷的服務程序爲 system_call(見arch/i386/kernel/entry.S), system.call就是全部系統調用的總入口。

內核如何爲各類系統調用服務


當進程須要進行系統調用時,必須以C語言函數的形式寫一句系統調用命令。該命令若是已在某個頭文件中由相應的_syscallN()展開,則用戶程序必須包含該文件。當進程執行到用戶程序的系統調用命令時,實際上執行了由宏命令_syscallN()展開的函數。系統調用的參數 由各通用寄存器傳遞,而後執行INT 0x80,之內核態進入入口地址system_call

ret_from_sys_call


ret_from_sys_call入口的彙編程序段在linux進程管理中起到了十分重要的做用。

全部系統調用結束前以及大部分中斷服務返回前,都會跳轉至此處入口地址。 該段程序不只僅爲系統調用服務,它還處理中斷嵌套、CPU調度、信號等事務。

內核如何爲系統調用的參數傳遞參數


參數傳遞


除了系統調用號之外,大部分系統調用都還須要一些外部的參數輸人。因此,在發生異常的時候,應該把這些參數從用戶空間傳給內核。最簡單的辦法就是像傳遞系統調用號同樣把這些參數也存放在寄存器裏。在x86系統上,ebx, ecx, edx, esiedi按照順序存放前五個參數。須要六個或六個以上參數的狀況很少見,此時,應該用一個單獨的寄存器存放指向全部這些參數在用戶空間地址的指針。

給用戶空間的返回值也經過寄存器傳遞。在x86系統上,它存放在eax寄存器中。接下來許多關於系統調用處理程序的描述都是針對x86版本的。但不用擔憂,全部體系結構的實現都很相似。

參數驗證


系統調用必須仔細檢查它們全部的參數是否合法有效。舉例來講,與文件I/O相關的系統調用必須檢查文件描述符是否有效。與進程相關的函數必須檢查提供的PID是否有效。必須檢查每一個參數,保證它們不但合法有效,並且正確。

最重要的一種檢查就是檢查用戶提供的指針是否有效。試想,若是一個進程能夠給內核傳遞指針而又無須被檢查,那麼它就能夠給出一個它根本就沒有訪問權限的指針,哄騙內核去爲它拷貝本不容許它訪問的數據,如本來屬於其餘進程的數據。在接收一個用戶空間的指針以前,內核必須保證:

  • 指針指向的內存區域屬於用戶空間。進程決不能哄騙內核去讀內核空間的數據。

  • 指針指向的內存區域在進程的地址空間裏。進程決不能哄騙內核去讀其餘進程的數據。

  • 若是是讀,該內存應被標記爲可讀。若是是寫,該內存應被標記爲可寫。進程決不能繞過內存訪問限制。

內核提供了兩個方法來完成必須的檢查和內核空間與用戶空間之間數據的來回拷貝。注意,內核不管什麼時候都不能輕率地接受來自用戶空間的指針!這兩個方法中必須有一個被調用。爲了向用戶空間寫入數據,內核提供了copy_to_user(),它須要三個參數。第一個參數是進程空間中的目的內存地址。第二個是內核空間內的源地址。最後一個參數是須要拷貝的數據長度(字節數)。

爲了從用戶空間讀取數據,內核提供了copy_from_ user(),它和copy-to-User()類似。該函數把第二個參數指定的位置上的數據拷貝到第一個參數指定的位置上,拷貝的數據長度由第三個參數決定。

若是執行失敗,這兩個函數返回的都是沒能完成拷貝的數據的字節數。若是成功,返回0。當出現上述錯誤時,系統調用返回標準-EFAULT。

注意copy_to_user()copy_from_user()都有可能引發阻塞。當包含用戶數據的頁被換出到硬盤上而不是在物理內存上的時候,這種狀況就會發生。此時,進程就會休眠,直到缺頁處理程序將該頁從硬盤從新換回物理內存。

系統調用的返回值


系統調用(在Linux中常稱做syscalls)一般經過函數進行調用。它們一般都須要定義一個或幾個參數(輸入)並且可能產生一些反作用,例如寫某個文件或向給定的指針拷貝數據等等。爲防止和正常的返回值混淆,系統調用並不直接返回錯誤碼,而是將錯誤碼放入一個名爲errno的全局變量中。一般用一個負的返回值來代表錯誤。返回一個0值一般代表成功。若是一個系統調用失敗,你能夠讀出errno的值來肯定問題所在。經過調用perror()庫函數,能夠把該變量翻譯成用戶能夠理解的錯誤字符串。

errno不一樣數值所表明的錯誤消息定義在errno.h中,你也能夠經過命令」man 3 errno」來察看它們。須要注意的是,errno的值只在函數發生錯誤時設置,若是函數不發生錯誤,errno的值就無定義,並不會被置爲0。另外,在處理errno前最好先把它的值存入另外一個變量,由於在錯誤處理過程當中,即便像printf()這樣的函數出錯時也會改變errno的值。

固然,系統調用最終具備一種明確的操做。舉例來講,如getpid()系統調用,根據定義它會返回當前進程的PID。內核中它的實現很是簡單:

asmlinkage long sys_ getpid(void) { return current-> tgid; }

 

上述的系統調用盡管很是簡單,但咱們仍是能夠從中發現兩個特別之處。首先,注意函數聲明中的asmlinkage限定詞,這是一個小戲法,用於通知編譯器僅從棧中提取該函數的參數。全部的系統調用都須要這個限定詞。其次,注意系統調用get_pid()在內核中被定義成sys_ getpid。這是Linux中全部系統調用都應該遵照的命名規則。

訪問系統調用


系統調用上下文


內核在執行系統調用的時候處於進程上下文。current指針指向當前任務,即引起系統調用的那個進程。

在進程上下文中,內核能夠休眠而且能夠被搶佔。這兩點都很重要。首先,可以休眠說明系統調用可使用內核提供的絕大部分功能。休眠的能力會給內核編程帶來極大便利。在進程上下文中可以被搶佔,其實代表,像用戶空間內的進程同樣,當前的進程一樣能夠被其餘進程搶佔。由於新的進程可使用相同的系統調用,因此必須當心,保證該系統調用是可重人的。固然,這也是在對稱多處理中必須一樣關心的問題。

當系統調用返回的時候,控制權仍然在system_call()中,它最終會負責切換到用戶空間並讓用戶進程繼續執行下去。

系統調用訪問示例


操做系統使用系統調用表將系統調用編號翻譯爲特定的系統調用。系統調用表包含有實現每一個系統調用的函數的地址。例如,read() 系統調用函數名爲sys_readread()系統調用編號是 3,因此sys_read() 位於系統調用表的第四個條目中(由於系統調用起始編號爲0)。從地址 sys_call_table + (3 * word_size) 讀取數據,獲得sys_read()的地址。

找到正確的系統調用地址後,它將控制權轉交給那個系統調用。咱們來看定義sys_read()的位置,即fs/read_write.c文件。這個函數會找到關聯到 fd 編號(傳遞給 read() 函數的)的文件結構體。那個結構體包含指向用來讀取特定類型文件數據的函數的指針。進行一些檢查後,它調用與文件相關的 read() 函數,來真正從文件中讀取數據並返回。與文件相關的函數是在其餘地方定義的 —— 好比套接字代碼、文件系統代碼,或者設備驅動程序代碼。這是特定內核子系統最終與內核其餘部分協做的一個方面。

讀取函數結束後,從sys_read()返回,它將控制權切換給 ret_from_sys。它會去檢查那些在切換回用戶空間以前須要完成的任務。若是沒有須要作的事情,那麼就恢復用戶進程的狀態,並將控制權交還給用戶程序。

從用戶空間直接訪問系統調用


一般,系統調用靠C庫支持。用戶程序經過包含標準頭文件並和C庫連接,就可使用系統調用(或者調用庫函數,再由庫函數實際調用)。但若是你僅僅寫出系統調用,glibc庫恐怕並不提供支持。值得慶幸的是,Linux自己提供了一組宏,用於直接對系統調用進行訪問。它會設置好寄存器並調用陷人指令。這些宏是_syscalln(),其中n的範圍從0到6。表明須要傳遞給系統調用的參數個數,這是因爲該宏必須瞭解到底有多少參數按照什麼次序壓入寄存器。舉個例子,open()系統調用的定義是:

long open(const char *filename, int flags, int mode)

 

而不靠庫支持,直接調用此係統調用的宏的形式爲:

#define NR_ open 5 syscall3(long, open, const char*,filename, int, flags, int, mode)

 

這樣,應用程序就能夠直接使用open()
對於每一個宏來講,都有2+ n個參數。
第一個參數對應着系統調用的返回值類型。
第二個參數是系統調用的名稱。再之後是按照系統調用參數的順序排列的每一個參數的類型和名稱。
_NR_ open<asm/unistd.h>中定義,是系統調用號。該宏會被擴展成爲內嵌彙編的C函數。由彙編語言執行前一節所討論的步驟,將系統調用號和參數壓入寄存器並觸發軟中斷來陷入內核。調用open()系統調用直接把上面的宏放置在應用程序中就能夠了。

讓咱們寫一個宏來使用前面編寫的foo()系統調用,而後再寫出測試代碼炫耀一下咱們所作的努力。

#define NR foo 283 _sysca110(long, foo) int main() { long stack size; stack_ size=foo(); printf("The kernel stack size is 81d/n",stack_ size); return; }

 

添加系統調用


經過修改內核源代碼添加系統調用


linux-2.6.*


經過以上分析linux系統調用的過程,

將本身的系統調用加到內核中就是一件容易的事情。下面介紹一個實際的系統調用,

並把它加到內核中去。要增長的系統調用是:inttestsyscall(),其功能是在控制終端屏幕上顯示hello world,

執行成功後返回0。

編寫int testsyscall()系統調用–響應函數


編寫一個系統調用意味着要給內核增長1個函數,將新函數放入文件kernel/sys.c中。新函數代碼以下:

asmlingkage sys_testsyscall()
{ 
    print("hello world\n"); return 0; }

 

添加系統調用號


編寫了新的系統調用過程後,下一項任務是使內核的其他部分知道這一程序的存在,而後重建包含新的系統調用的內核。爲了把新的函數鏈接到已有的內核中去, 須要編輯2個文件:

1).inculde/asm/unistd.h在這個文件中加入

#define_NR_testsyscall 191

 

系統調用表中添加對應項


2).are/i386/kernel/entry.s這個文件用來對指針數組初始化,在這個文件中增長一行:

.long SYMBOL_NAME(_sys_tsetsycall)

 

.rept NR_syscalls-190改成NR_SYSCALLS-191,而後從新編譯和運行新內核。

使用新的系統調用


在保證的C語言庫中沒有新的系統調用的程序段,必須本身創建其代碼以下

#inculde _syscall0(int,testsyscall) main() { tsetsyscall(); }

 

在這裏使用了_syscall0宏指令,宏指令自己在程序中將擴展成名爲syscall()的函數,它在main()函數內部加以調用。

testsyscall()函數中, 預處理程序產生全部必要的機器指令代碼,包括用系統調用參數值加載相應的cpu寄存器, 而後執行int 0x80中斷指令。

linux-3.*


在linux-3.8.4/kernel/sys.c 文件末尾添加新的系統調用函數如:

asmlinkage int sys_mycall(int number) { printk("這是我添加的第一個系統調用"); return number; }

 

arch/x86/syscall_32.tbl下找到unused 223號調用而後替換如:

223 i386 mycall sys_mycall

 

若是是64位系統,在arch/x86/syscalls/syscall_64.tbl下找到313號系統調用,而後在其下面加上314號本身的中斷如:
`314 common mycall sys_mycall

利用內核模塊添加系統調用


模塊是內核的一部分,可是並無被編譯到內核裏面去。它們被分別編譯並鏈接成一組目標文件, 這些文件能被插入到正在運行的內核,或者從正在運行的內核中移走。內核模塊至少必須有2個函數:

 

init_modulecleanup_module

第一個函數是在把模塊插入內核時調用的;

第二個函數則在刪除該模塊時調用。因爲內核模塊是內核的一部分,因此能訪問全部內核資源。根據對linux系統調用機制的分析,

若是要增長系統調用,能夠編寫本身的函數來實現,而後在sys_call_table表中增長一項,使該項中的指針指向本身編寫的函數,

就能夠實現系統調用。下面用該方法實如今控制終端上打印「hello world」 的系統調用testsyscall()。

編寫系統調用內核模塊


#inculde(linux/kernel.h) #inculde(linux/module.h) #inculde(linux/modversions.h) #inculde(linux/sched.h) #inculde(asm/uaccess.h) #define_NR_testsyscall 191 extern viod *sys_call+table[]; asmlinkage int testsyscall() { printf("hello world\n"); return 0; } int init_module() { sys_call_table[_NR_tsetsyscall]=testsyscall; printf("system call testsyscall() loaded success\n"); return 0; } void cleanup_module() { }

 

使用新的系統調用

#define_NR_testsyscall 191 _syscall0(int,testsyscall) main() { testsyscall(); }

 

內核Linux系統調用的列表


如下是Linux系統調用的一個列表,包含了大部分經常使用系統調用和由系統調用派生出的的函數。

進程控制


系統調用 描述
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 設置會晤標識號

文件系統控制


文件讀寫操做


系統調用 描述
fcntl 文件控制
open 打開文件
creat 建立新文件
close 關閉文件描述字
read 讀文件
write 寫文件
readv 從文件讀入數據到緩衝數組中
writev 將緩衝數組裏的數據寫入文件
pread 對文件隨機讀
pwrite 對文件隨機寫
lseek 移動文件指針
_llseek 在64位地址空間裏移動文件指針
dup 複製已打開的文件描述字
dup2 按指定條件複製文件描述字
flock 文件加/解鎖
poll I/O多路轉換
truncat e 截斷文件
ftruncate 參見truncate
vumask 設置文件權限掩碼
fsync 把文件在內存中的部分寫回磁盤

文件系統操做


系統調用 描述
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 控制磁盤配額

系統控制


系統調用 描述
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 查詢模塊信息
*get_kernel_syms 取得核心符號,已被query_module代替

內存管理


系統調用 描述
brk 改變數據段空間的分配
sbrk 參見brk
mlock 內存頁面加鎖
munlock 內存頁面解鎖
mlockall 調用進程全部內存頁面加鎖
munlockall 調用進程全部內存頁面解鎖
mmap 映射虛擬內存頁
munmap 去除內存頁映射
mremap 從新映射虛擬內存地址
msync 將映射內存中的數據寫回磁盤
mprotect 設置內存映像保護
getpagesize 獲取頁面大小
sync 將內存緩衝區數據寫回硬盤
cacheflush 將指定緩衝區中的內容寫回磁盤

網絡管理


系統調用 描述
getdomainname 取域名
setdomainname 設置域名
gethostid 獲取主機標識號
sethostid 設置主機標識號
gethostname 獲取本主機名稱
sethostname 設置主機名稱

socket控制


系統調用 描述
socketcall socket系統調用
socket 創建socket
bind 綁定socket到端口
connect 鏈接遠程主機
accept 響應socket鏈接請求
send 經過socket發送信息
sendto 發送UDP信息
sendmsg 參見send
recv 經過socket接收信息
recvfrom 接收UDP信息
recvmsg 參見recv
listen 監聽socket端口
select 對多路同步I/O進行輪詢
shutdown 關閉socket上的鏈接
getsockname 取得本地socket名字
getpeername 獲取通訊對方的socket名字
getsockopt 取端口設置
setsockopt 設置端口參數
sendfile 在文件或端口間傳輸數據
socketpair 建立一對已聯接的無名socket

用戶管理


系統調用 描述
getuid 獲取用戶標識號
setuid 設置用戶標誌號
getgid 獲取組標識號
setgid 設置組標誌號
getegid 獲取有效組標識號
setegid 設置有效組標識號
geteuid 獲取有效用戶標識號
seteuid 設置有效用戶標識號
setregid 分別設置真實和有效的的組標識號
setreuid 分別設置真實和有效的用戶標識號
getresgid 分別獲取真實的,有效的和保存過的組標識號
setresgid 分別設置真實的,有效的和保存過的組標識號
getresuid 分別獲取真實的,有效的和保存過的用戶標識號
setresuid 分別設置真實的,有效的和保存過的用戶標識號
setfsgid 設置文件系統檢查時使用的組標識號
setfsuid 設置文件系統檢查時使用的用戶標識號
getgroups 獲取後補組標誌清單
setgroups 設置後補組標誌清單

進程間通訊


系統調用 描述
ipc 進程間通訊總控制調用

信號


系統調用 描述
sigaction 設置對指定信號的處理方法
sigprocmask 根據參數對信號集中的信號執行阻塞/解除阻塞等操做
sigpending 爲指定的被阻塞信號設置隊列
sigsuspend 掛起進程等待特定信號
signal 參見signal
kill 向進程或進程組發信號
*sigblock 向被阻塞信號掩碼中添加信號,已被sigprocmask代替
*siggetmask 取得現有阻塞信號掩碼,已被sigprocmask代替
*sigsetmask 用給定信號掩碼替換現有阻塞信號掩碼,已被sigprocmask代替
*sigmask 將給定的信號轉化爲掩碼,已被sigprocmask代替
*sigpause 做用同sigsuspend,已被sigsuspend代替
sigvec 爲兼容BSD而設的信號處理函數,做用相似sigaction
ssetmask ANSI C的信號處理函數,做用相似sigaction

消息


系統調用 描述
msgctl 消息控制操做
msgget 獲取消息隊列
msgsnd 發消息
msgrcv 取消息

管道


系統調用 描述
pipe 建立管道

信號量


系統調用 描述
semctl 信號量控制
semget 獲取一組信號量
semop 信號量操做

共享內存


系統調用 描述
shmctl 控制共享內存
shmget 獲取共享內存
shmat 鏈接共享內存
shmdt 拆卸共享內存
相關文章
相關標籤/搜索