01.15 linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

包括 linux,大多現代操作系統都提供了用戶進程和內核交互的接口。通過這些接口,用戶進程能夠

在內核的監督下訪問硬件設備,創建新進程或者與其他進程通信。可以說,這些接口充當了用戶進程和內核的中轉站。

在內核的監督下,可以避免用戶進程的肆意妄為,做出一些損害系統的事情。

系統調用

在 linux 中,除了異常和陷入,系統調用是用戶空間訪問內核空間的唯一合法手段。

系統調用介於用戶空間和內核空間之間,充當信息轉達中間人的角色。有了系統調用,內核可以基於用戶所在組等權限信息決定是否響應用戶空間的操作,從而保證系統的安全性。

linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

此外,系統調用層可以為硬件虛擬出一套通用的接口供用戶空間的進程使用。例如,用戶空間進程採取系統調用讀寫文件,就無需關心磁盤的類型、介質、甚至文件系統了,系統調用會根據不同的情況自適應的完成需求。

而且,如果用戶空間進程不通過系統調用直接訪問硬件,linux 內核就無法得知這些信息,也就無法實現多任務的協調安排,整個系統的穩定性就無從談起。

linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

一般來說,用戶空間進程並不直接使用系統調用,而是通過同樣在用戶空間實現的 API(Application Program Interface,應用程序接口)使用。定義一個 API 可以使用一個系統調用,也可以使用多個系統調用,不使用任何系統調用也是可以的。對於C語言程序開發來說,這些 API 主要由 C 庫提供。

這樣的設計其實很清晰,程序員並不需要關心繫統調用,只需要使用好 API 就可以了。而 linux 內核則不需關心 API,只需要處理好系統調用即可。

系統調用處理程序

之前我們討論過,進程運行在虛擬空間裡,linux 內核空間則駐留在受保護的地址空間裡,所以用戶空間進程無法直接調用內核空間裡的函數。

如果用戶空間進程需要執行一個系統調用,只能以某種方式向 linux 內核申請,之後內核才有可能代表用戶進程在內核空間執行系統調用。

例如,用戶空間進程可以利用軟中斷機制實現,通過引發一個異常來促使系統切換到內核態執行異常處理程序,這裡的“異常處理程序”其實就是系統調用處理程序。

linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

linux 內核已支持的系統調用都有一個唯一的系統調用號,例如:

 8 #define __NR_restart_syscall 0
9 #define __NR_exit 1
10 #define __NR_fork 2
11 #define __NR_read 3
12 #define __NR_write 4
13 #define __NR_open 5
14 #define __NR_close 6
15 #define __NR_waitpid 7
16 #define __NR_creat 8
17 #define __NR_link 9
18 #define __NR_unlink 10
19 #define __NR_execve 11
20 #define __NR_chdir 12
21 #define __NR_time 13

22 #define __NR_mknod 14
23 #define __NR_chmod 15
24 #define __NR_lchown 16
25 #define __NR_break 17
26 #define __NR_oldstat 18
27 #define __NR_lseek 19
...
linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

用戶空間進程申請系統調用時,可以把系統調用號和參數通過寄存器傳遞給內核,這樣內核就能知道該執行哪一個系統調用。程序員也可以直接調用 syscall() 函數申請系統調用,該函數的使用手冊可以通過 man 命令查詢:

linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

定義自己的系統調用

重新定義一個系統調用並不難,以 my_fun() 作為新定義的系統調用函數名為例。首先,將其加入系統調用表:

 18 ENTRY(sys_call_table)
19 .long sys_restart_syscall /* 0 - old "setup()" system call*/
20 .long sys_exit
21 .long sys_fork
22 .long sys_read
23 .long sys_write
24 .long sys_open /* 5 */

...
343 .long sys_fallocate
344 .long sys_timerfd_settime /* 325 */
345 .long sys_timerfd_gettime
346 .long sys_my_fun
linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

注意,為了不影響原系統調用,應將其加到表的最後。另外,雖然沒有明確指明系統調用號,但是系統仍然可以根據表的順序自動計算出 my_fun() 的系統調用號。

接著,將系統調用號加入到 asm/unistd_32.h 裡:

 8 #define __NR_restart_syscall 0
9 #define __NR_exit 1
10 #define __NR_fork 2
11 #define __NR_read 3
12 #define __NR_write 4
13 #define __NR_open 5
...
329 #define __NR_signalfd 321
330 #define __NR_timerfd_create 322
331 #define __NR_eventfd 323
332 #define __NR_fallocate 324
333 #define __NR_timerfd_settime 325
334 #define __NR_timerfd_gettime 326
335 #define __NR_my_fun 327
...
linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

準備工作做好以後,就可以寫實現 my_fun() 的C語言代碼了:

#include 
asmlinkage long sys_my_fun(void)
{
// do something
return 0;
}

寫好 my_fun() 的C語言代碼後,可以將其放在功能相關的文件裡。至此,我們就實現了一個新的系統調用。需要說明的是,若想使用 my_fun() 系統調用,必須先將其編譯進內核映像,不能編譯成模塊。

慎用系統調用

容易看出,定義一個新的系統調用非常簡單,而且 linux 系統調用的性能也非常高效,但是仍然應儘量使用其他方法代替新建一個系統調用。

linux學習16,一文弄懂為何要使用系統調用,而不是直接訪問內核

新建系統調用需要系統調用號,雖然可以將其放在系統調用表最後,但是不能保證不會與以後的 linux 官方版本新定義的系統調用衝突。

而且,系統調用一旦加入內核,就被固化了,為了保持兼容性,可能之後很多年都不允許修改。此外,因為系統調用不能從文件系統直接訪問,所以一些腳本也就不容易調用該功能了。

歡迎在評論區一起討論,質疑。文章都是手打原創(本文部分參考linux內核原理和設計),每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。


分享到:


相關文章: