Linux 系統調用詳解(實現機制分析)

系統調用概述


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

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

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

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

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

爲什麼需要系統調用


linux 內核中設置了一組用於實現系統功能的子程序,稱爲系統調用。系統調用和普通庫函數調用非常相似,只是系統調用由操作系統核心提供,運行於內核態,而普通的函數調用由函數庫或用戶自己提供,運行於用戶態。

一般的,進程是不能訪問內核的。它不能訪問內核所佔內存空間也不能調用內核函數。CPU 硬件決定了這些(這就是爲什麼它被稱作 “保護模式”)。

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

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

1、它爲用戶空間提供了一種統一的硬件的抽象接口。比如當需要讀些文件的時候,應用程序就可以不去管磁盤類型和介質,甚至不用去管文件所在的文件系統到底是哪種類型。

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

3、每個進程都運行在虛擬系統中,而在用戶空間和系統的其餘部分提供這樣一層公共接口,也是出於這種考慮。如果應用程序可以隨意訪問硬件而內核又對此一無所知的話,幾乎就沒法實現多任務和虛擬內存,當然也不可能實現良好的穩定性和安全性。在 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. 系統調用的函數名稱轉換。
    1. 系統調用的參數傳遞。

首先看第一個問題。實際上,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 查找) 而內核中的頭文件路徑不同的內核版本以及不同的發行版,文件的存儲結構可能有所區別

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

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

在表中的偏移量,找到對應的系統調用響應函數的入口地址。系統調用表共 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)1

由於系統調用表中的表項是以 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, esi 和 edi 按照順序存放前五個參數。需要六個或六個以上參數的情況不多見,此時,應該用一個單獨的寄存器存放指向所有這些參數在用戶空間地址的指針。

給用戶空間的返回值也通過寄存器傳遞。在 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_read。read() 系統調用編號是 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_module 和 cleanup_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 系統調用的一個列表,包含了大部分常用系統調用和由系統調用派生出的的函數。

進程控制

文件系統控制


文件讀寫操作

文件系統操作

系統控制

內存管理

網絡管理

socket 控制

用戶管理

進程間通信

信號

消息

管道

信號量

共享內存

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ql3nlh56KPw3HFJx1UxFrA