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下系統調用的實現

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

        1. 系統調用的函數名稱轉換。

        2. 系統調用的參數傳遞。

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

在/usr/include/asm/unistd_32.h,能夠通過find / -name unistd_32.h -print查找)

  1. #ifndef _ASM_X86_UNISTD_32_H  
  2. #define _ASM_X86_UNISTD_32_H  
  3.   
  4. /* 
  5.  * This file contains the system call numbers. 
  6.  */  
  7.   
  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  
      所以詳細調用fork的過程是:將2存入%eax中,然后進行系統調用,偽代碼:

[plain]  view plain copy print ?
  1. mov     eax, 2  
  2. int     0x80  
        對於參數傳遞,Linux是通過寄存器完畢的。Linux最多同意向系統調用傳遞6個參數,分別依次由%ebx,%ecx,%edx,%esi,%edi這5個寄存器完畢,須要6個及以上參數情況不多見。另外應該有一個單獨的寄存器存放指向全部這些參數在用戶空間的地址的指針。給用戶空間的返回值通過eax寄存器傳遞。比方,調用exit(1),偽代碼是:

[plain]  view plain copy print ?

  1. mov    eax, 2  
  2. mov    ebx, 1  
  3. int    0x80  
        由於exit須要一個參數1,所以這里僅僅須要使用ebx。這6個寄存器可能已經被使用,所以在傳參前必須把當前寄存器的狀態保存下來,待系統調用返回后再恢復,這個在后面棧切換再詳細講。


        Linux中,在用戶態和內核態執行的進程使用的棧是不同的。分別叫做用戶棧和內核棧,兩者各自負責對應特權級別狀態下的函數調用。當進行系統調用時。進程不僅要從用戶態切換到內核態。同一時候也要完畢棧切換。這樣處於內核態的系統調用才干在內核棧上完畢調用。系統調用返回時,還要切換回用戶棧,繼續完畢用戶態下的函數調用。

        寄存器%esp(棧指針。指向棧頂)所在的內存空間叫做當前棧,比方%esp在用戶空間則當前棧就是用戶棧。否則是內核棧。棧切換主要就是%esp在用戶空間和內核空間間的來回賦值。

在Linux中,每一個進程都有一個私有的內核棧,當從用戶棧切換到內核棧時,需完畢保存%esp以及相關寄存器的值(%ebx。%ecx...)並將%esp設置成內核棧的對應值。

而從內核棧切換會用戶棧時。須要恢復用戶棧的%esp及相關寄存器的值以及保存內核棧的信息。一個問題就是用戶棧的%esp和寄存器的值保存到什么地方,以便於恢復呢?答案就是內核棧,在調用int指令機型系統調用后會把用戶棧的%esp的值及相關寄存器壓入內核棧中。系統調用通過iret指令返回。在返回之前會從內核棧彈出用戶棧的%esp和寄存器的狀態。然后進行恢復。

        相信大家一定聽過說,系統調用非常耗時。要盡量少用。通過上面描寫敘述系統調用的實現原理,大家也應該知道這當中的原因了。

第一,系統調用通過中斷實現,須要完畢棧切換。

第二,使用寄存器傳參,這須要額外的保存和恢復的過程。

        上面關於系統調用的闡述,如有錯誤歡迎指正。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM