Linux內核學習筆記(五)——系統調用

1、與內核通信

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

  • 爲用戶空間提供一種硬件的抽象接口。
  • 系統調用保證了系統的穩定和安全。
  • 系統調用時用戶空間訪問內核的唯一手段(異常和陷入除外)。

2、API、POSIX和C庫

  應用程序通過用戶空間實現的應用編程接口(API)而不是直接通過系統調用來編程。
  
  Unix最流行的應用編程接口是基於POSIX標準的,POSIX定義的API函數和系統調用之間有着直接關係。
  
  C庫實現了Unix系統的主要API,包括標準C庫函數和系統調用接口。


3、系統調用

  getpid()系統調用的實現:

SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

  SYSCALL_DEFINE0是一個宏,定義一個無參數(0)的系統調用,展開後如下:

asmlinkage long sys_getpid(void)

  asmlinkage是一個編譯指令,通知編譯器僅從棧中提取該函數的參數。所有系統調用都使用這個限定詞。sys_name()是Linux系統系統調用的命名規則。

3.1 系統調用號

  Linux中,每個系統調用被賦予一個系統調用號。
  
  系統調用號分配後不能變更。如果一個系統調用被刪除,它所佔用的系統調用號不允許被回收利用。
  sys_ni_syscall()表示未實現系統調用,只返回-ENOSYS,爲無效的系統調用而設。
  
  系統調用的列表存儲在sys_call_table中。

3.2 系統調用的性能

  Linux系統調用比其他操作系統執行的快。因爲Linux上下文切換時間很短,系統調用處理程序和每個系統調用本身非常簡潔。


4、系統調用處理程序

  用戶空間的程序無法直接執行內核代碼,不能直接調用內核空間的函數,因爲內核駐留在受保護的地址空間上。
  
  應用程序通過軟中斷通知內核執行系統調用,引發一個異常來使系統切換到內核態去執行異常處理程序(系統調用處理程序)。
  
  X86系統預定義的軟中斷爲中斷號128,通過int $0x80指令觸發,執行異常處理程序system_call()。x86處理器新增指令sysenter,更快、更專業地陷入內核。

4.1 指定恰當的系統調用

  x86系統上,系統調用號通過eax寄存器傳遞給內核。system_call()將系統調用號與NR_syscalls比較,大於或等於NR_syscalls返回-ENOSYS。執行系統調用代碼:

call *sys_call_table(, %rax, 8);

  系統調用表中表項以64位(8字節)類型存放,x86-32系統上用4代替8。

4.2 參數傳遞

  x86-32系統上,ebx、ecx、edx、esi和edi按照順序存放前五個參數。
  
  給用戶空間的返回值存放在eax寄存器上。


5、系統調用的實現

5.1 實現系統調用

  系統調用的接口應該簡潔,參數儘可能少,儘量爲將來多做考慮。
  
  標誌不是用來讓單個系統調用具有多個不同的行爲,而是爲了即使增加新的功能和選項,也不破壞向後兼容或不需要的增加新的系統調用。

5.2 參數驗證

  系統調用必須仔細檢查所有的參數是否合法有效。
  
  例如在接收一個用戶空間的指針之前,內核必須保證:

  • 指針指向的內存區域屬於用戶空間。
  • 指針指向的內存區域在該進程的地址空間。
  • 如果是讀,該內存應被標記爲可讀;如果是寫,該內存應被標記爲可寫;如果是可執行,該內存被標記爲可執行。

  
  向用戶空間寫入數據,內核提供了copy_to_user()方法。第一個參數是進程空間中的目的內存地址,第二個是內核空間內的源地址,最後一個是需要拷貝的數據長度。
  從用戶空間讀取數據,內核提供了copy_from_user()方法。把第二個參數指定位置上的數據拷貝到第一個參數指定的位置上。
  執行失敗,函數返回沒能完成拷貝的數據的字節數。成功返回0。函數可能會引起阻塞。
  
  內核會檢查是否有合法權限。使用capable()函數來檢查是否有權對指定的資源進行操作,返回非0有權操作。


6、系統調用上下文

  內核在執行系統調用的時候處於進程上下文。current指針指向引發系統調用的進程。
  
  進程上下文中,內核可以休眠並且可以被搶佔。能夠休眠說明系統調用可以使用內核提供的絕大部分功能;當前的進程可以被其他進程搶佔,使用相同的系統調用時要保證系統調用時可重入的。

6.1 綁定系統調用

  註冊正式的系統調用:
- 在系統調用表的最後加入一個表項。系統調用號從0開始計算。
- 對於所支持的各種體系結構,系統調用號都必須定義於

6.2 從用戶空間訪問系統調用

  Linux使用_syscalln()宏調用系統調用,其中n的範圍從0到6,代表需要傳遞給系統調用的參數個數:

//open()系統調用定義
long open(const char *filename, int flags, int mode)

//使用系統調用的宏的形式
#define NR_open    5
_syscall113(long, open, const char*, filename, int, flags, int, mode)

  第一個參數是系統調用的返回值類型,第二個參數是系統調用的名稱,之後是系統調用每個參數的類型和名稱。

6.3 系統調用利弊

  通常不通過系統調用的方式實現。
  
  好處:

  • 系統調用創建容易且使用方便。
  • Linux系統調用的高性能顯而易見。

  
  問題:

  • 需要一個系統調用號,需要一個內核在處於開發版本的時候官方分配給你。
  • 系統調用被加入穩定內核後就被固化了,接口不允許做改動。
  • 需要將系統調用分別註冊到每個需要支持的體系結構中去。
  • 在腳本中不容易調用系統調用,也不能從文件系統直接訪問系統調用。
  • 由於需要系統調用號,在主內核樹之外很難維護和使用系統調用。
  • 如果僅僅進行簡單的信息交換,系統調用大材小用。

  
  替代方法:

  • 像信號量這樣的接口,可以用文件描述符來表示,使用read()等方法對其進行操作。
  • 把增加的信息作爲一個文件放在sysfs的合適位置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章