使用者應用可以通過兩種方式使用系統呼叫。第一種方式是通過c庫函式,包括系統呼叫在c庫中的封裝函式和其他普通函式。
圖5.2 使用系統呼叫的兩種方式
第二種方式是使用_syscall巨集。2.6.18版本之前的核心,在include/asm-i386/unistd.h檔案中定義有7個_syscall巨集,分別是:
_syscall1(int, sysinfo, struct sysinfo *, info);
展開後的形式為:
int sysinfo(struct sysinfo * info)
return (int) (__res);
} while (0);
}
展開後的形式為:
int sysinfo(struct sysinfo * info)
return (int) (__res);
} while (0);
}
可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展開成乙個名為sysinfo的函式,原引數int就是函式的返回型別,原引數struct sysinfo *和info分別構成新函式的引數。
在程式檔案裡使用_syscall巨集定義需要的系統呼叫,就可以在接下來的**中通過系統呼叫名稱直接呼叫該系統呼叫。下面是乙個使用sysinfo系統呼叫的例項。
可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展開成乙個名為sysinfo的函式,原引數int就是函式的返回型別,原引數struct sysinfo *和info分別構成新函式的引數。
在程式檔案裡使用_syscall巨集定義需要的系統呼叫,就可以在接下來的**中通過系統呼叫名稱直接呼叫該系統呼叫。下面是乙個使用sysinfo系統呼叫的例項。
**清單5.1 sysinfo系統呼叫使用例項
#include #include #include #include #include /* for struct sysinfo */
_syscall1(int, sysinfo, struct sysinfo *, info);
int main(void)
大部分系統呼叫都包括了乙個sys_符號常量來指定自己到系統呼叫號的對映,因此上面第10行可重寫為:
tid = syscall(sys_gettid);
系統呼叫執行過程系統呼叫的執行過程主要包括如圖5.3與圖5.4所示的兩個階段:使用者空間到核心空間的轉換階段,以及系統呼叫處理程式system_call函式到系統呼叫服務例程的階段。
圖5.3 使用者空間到核心空間
圖5.4 system_call函式到系統呼叫服務例程
(1)使用者空間到核心空間。
如圖5.3所示,系統呼叫的執行需要乙個使用者空間到核心空間的狀態轉換,不同的平台具有不同的指令可以完成這種轉換,這種指令也被稱作作業系統陷入(operating system trap)指令。
linux通過軟中斷來實現這種陷入,具體對於x86架構來說,是軟中斷0x80,也即int $0x80彙編指令。軟中斷和我們常說的中斷(硬體中斷)不同之處在於-它由軟體指令觸發而並非由硬體外設引發。
int 0x80指令被封裝在c庫中,對於使用者應用來說,基於可移植性的考慮,不應該直接呼叫int $0x80指令。陷入指令的平台依賴性,也正是系統呼叫需要在c庫進行封裝的原因之一。
通過軟中斷0x80,系統會跳轉到乙個預設的核心空間位址,它指向了系統呼叫處理程式(不要和系統呼叫服務例程相混淆),即在arch/i386/kernel/entry.s檔案中使用組合語言編寫的system_call函式。
(2)system_call函式到系統呼叫服務例程。
很顯然,所有的系統呼叫都會統一跳轉到這個位址進而執行system_call函式,但正如前面所述,到2.6.23版為止,核心提供的系統呼叫已經達到了325個,那麼system_call函式又該如何派發它們到各自的服務例程呢?
軟中斷指令int 0x80執行時,系統呼叫號會被放入eax暫存器,同時,sys_call_table每一項占用4個位元組。這樣,如圖5.5所示,system_call函式可以讀取eax暫存器獲得當前系統呼叫的系統呼叫號,將其乘以4生成偏移位址,然後以sys_call_table為基址,基址加上偏移位址所指向的內容即是應該執行的系統呼叫服務例程的位址。
另外,除了傳遞系統呼叫號到eax暫存器,如果需要,還會傳遞一些引數到核心,比如write系統呼叫的服務例程原型為:
sys_write(unsigned int fd, const char * buf, size_t count);呼叫write系統呼叫時就需要傳遞檔案描述符fd、要寫入的內容buf以及寫入位元組數count等幾個內容到核心。ebx、ecx、edx、esi以及edi暫存器可以用於傳遞這些額外的引數。
正如之前所述,系統呼叫服務例程定義中的asmlinkage標記表示,編譯器僅從堆疊中獲取該函式的引數,而不需要從暫存器中獲得任何引數。進入system_call函式前,使用者應用將引數存放到對應暫存器中,system_call函式執行時會首先將這些暫存器壓入堆疊。
對於系統呼叫服務例程,可以直接從system_call函式壓入的堆疊中獲得引數,對引數的修改也可以一直在堆疊中進行。在system_call函式退出後,使用者應用可以直接從暫存器中獲得被修改過的引數。
並不是所有的系統呼叫服務例程都有實際的內容,有乙個服務例程sys_ni_syscall除了返回-enosys外不做任何其他工作,在kernel/sys_ni.c檔案中定義。
asmlinkage long sys_ni_syscall(void)
sys_ni_syscall的確是最簡單的系統呼叫服務例程,表面上看,它可能並沒有什麼用處,但是,它在sys_call_table中佔據了很多位置。多數字置上的sys_ni_syscal都代表了那些已經被核心中淘汰的系統呼叫,比如:
long sys_ni_syscall /* old stty syscall holder */
long sys_ni_syscall /* old gtty syscall holder */
就分別代替了已經廢棄的stty和gtty系統呼叫。如果乙個系統呼叫被淘汰,它所對應的服務例程就要被指定為sys_ni_syscall。
我們並不能將它們的位置分配給其他的系統呼叫,因為一些老的**可能還會使用到它們。否則,如果某個使用者應用試圖呼叫這些已經被淘汰的系統呼叫,所得到的結果,比如開啟了乙個檔案,就會與預期完全不同,這將令人感到非常奇怪。
其實,sys_ni_syscall中的"ni"即表示"not implemented(沒有實現)"。
系統呼叫通過軟中斷0x80陷入核心,跳轉到系統呼叫處理程式system_call函式,並執行相應的服務例程,但由於是代表使用者程序,所以這個執行過程並不屬於中斷上下文,而是處於程序上下文。
因此,系統呼叫執行過程中,可以訪問使用者程序的許多資訊,可以被其他程序搶占(因為新的程序可能使用相同的系統呼叫,所以必須保證系統呼叫可重入),可以休眠(比如在系統呼叫阻塞時或顯式呼叫schedule函式時)。
這些特點涉及程序排程的問題,在此不做深究,讀者只需要理解當系統呼叫完成後,把控制權交回到發起呼叫的使用者程序前,核心會有一次排程。如果發現有優先順序更高的程序或當前程序的時間片用完,那麼就會選擇高優先順序的程序或重新選擇程序執行。
static inline _syscall0(int,fork)
其中_syscall0()是unistd.h中的內嵌巨集**,它以嵌入彙編的形式呼叫linux的系統呼叫中斷int0x80。根據include/unistd.h檔案第133行上的巨集定義,我們把這個巨集展開並替代進上面一行中就可以看出這條語句實際上是intfork()建立程序系統呼叫,見如下所示。
// unistd.h檔案中_syscall0()的定義。即為不帶引數的系統呼叫巨集函式:
type name(void)。
#define _syscall0(type,name)
type name(void)
// 根據上面定義把_syscall0(int, fork)展開代進第02行後我們可以得到如下語句:
static inline int fork(void)
gcc會把上述「函式」體中的語句直接插入到呼叫fork()語句的**處,因此執行fork()不會引起函式呼叫。另外,巨集名稱字串「syscall0」中最後的0表示無引數,1表示帶1個引數。如果系統呼叫帶有1個引數,那麼就應該使用巨集_syscall1()。 Linux系統呼叫
一 實驗目的和要求 1.學習linux核心的配置和編譯 2.深入理解linux系統呼叫 3.理解arm和x86的cpu模式 系統模式 使用者模式 的不同 4.掌握核心模組的編寫方法。二 實驗器材 1.linux實驗板卡一塊 2.5v 1a電源乙個 3.microusb線一根 4.macos一台 5....
Linux系統呼叫
linux系統呼叫 系統呼叫 system call 是使用者空間訪問核心的唯一手段,除異常和陷入外,他們是核心唯一的合法入口。通常情況下應用程式是通過應用程式設計介面api來訪問函式,而不是直接使用系統呼叫來程式設計。作業系統通常是通過中斷從使用者態切換到核心態。中斷就是乙個硬體或軟體請求,要求c...
linux系統呼叫
系統呼叫在使用者空間程序 應用程式 和硬體裝置之間新增了乙個中間層,這樣做的好處在於將使用者空間程序和硬體裝置進行隔離。linux系統有一套標準的系統呼叫介面,然後硬體驅動會去實現這一系列標準的系統呼叫介面,而使用者空間程序只需去呼叫這些標準系統呼叫介面來操作硬體,這樣寫出的應用程式就是易於移植的。...