用戶空間和內核空間通訊之【系統調用】


現在,越來越多的應用程序需要編寫內核和用戶級代碼的程序來一起協作完成具體的任務,而用戶與空間和內核空間的通訊也就是一個不可回避的話題了。針對於需要和內核空間通信的具體應用而言,其開發模式和套路相對來說比較固定,主要概括起來有兩大步驟:

第一步,編寫內核服務程序利用內核空間提供的權限和服務來接收、緩存和處理數據第二步,編寫用戶程序來和先前的內核服務程序進行交互

具體來說,可以利用用戶程序來配置內核服務程序的參數,獲取內核服務程序提供的數據,也可以向內核服務程序輸入數據。

我們可以看到,用戶程序和內核的信息交換可以是雙向的,也就是說既可以由用戶主動向內核空間發送消息,也可以由內核空間主動向用戶提交數據。當然,用戶程序也可以主動從內核提取數據。

針對上述應用場景,Linux提供了幾種用戶和內核空間通訊的手段,在實際環境中可以根據需求自由選取,常見的幾種方式是:系統調用,procfssysfs&ioctlg(s)etsockopt以及netlink等。

今天我們先來談談系統調用。

2.6.21內核提供的系統調用,可以在linux-2.6.21\include\ \unistd.h 頭文件中找到它們,這里的<arch>指的就是CPU體系架構。對於x86而言,就是linux-2.6.21\include\asm-i386\unistd.h,一共有319個,如下所示:

系統調用的實現和工作原理可以概括如下:

應用程序調用適當的值填充寄存器,然后調用一個特殊的指令,跳轉到內核某一固定的位置,內核根據應用程序所填充的固定值來找到相應的函數,然后開始執行該函數。

注意上述關鍵詞的描述。

1、適當的值:unistd.h中我們可以看到,每個系統調用名都對應一個唯一的數字,這個數字我們稱之為系統調用號,在整個系統中,這些系統調用號是唯一的,且由內核開發小組統一維護。例如,我們看到fork系統調用號為2open系統調用號為5等等。

2、特殊的指令:intelCPU中,該指令由中斷指令INT 0x80來實現,也就是說在Linux中,系統調用的接口是一個中斷處理函數的特例。在x86體系中這個處理函數就是system_call()

3、固定的位置:當我們執行一個系統調用時,我們前面提到的系統調用號會作為參數傳遞給system_call(),然后system_call()函數通過查詢中斷向量表找到每個系統調用所對應的實現函數的位置,即內核空間中,哪個內存地址存放哪個系統調用的處理函數是在內核加載時就已經固定了的。

4、相應的函數:系統調用的處理函數都已“sys_”開頭,而所有的系統調用的處理函數最后組成了一張表,叫做系統調用表sys_call_table,位於linux-2.6.21\arch\i386\kernel\syscall_table.s文件中,這是一個匯編文件(注意:如果是在arm系統中,系統調用表的是在內核源碼包的linux-2.6.21\arch\arm\kernel\calls.s文件里)

通過系統調用號可以找到其實現函數的所在位置,也就是說系統調用號和其實現函數的邏輯地址是一一對應的。open的系統調用號為5,那么在執行system_call()時會將“5”傳遞給它,然后system_call()就會去執行sys_open()系統調用了。其整個流程如下所示:

根據上圖所示,如果我們要添加一個新的自定義的系統調用,可分為以下三個步驟:

1、在內核中添加系統調用的實現函數;

2、更新頭文件unistd.h

3、更新syscall_table.s文件。

內核中系統調用的實現函數的分類也相當有講究,例如系統級的函數一般位於kernel/sys.c中;文件操作的系統調用位於linux-2.6.21/fs目錄里,其中每個函數對應一個相應的系統調用的實現文件,如fs/open.cfs/write.c等等;和socket相關的系統調用其實現函數位於linux-2.6.21/net/socket.c中,里面有sys_socketsys_bind等。為了簡單起見,我們新增的系統調用將其放在kernel/sys.c中,當然你也可以仿照內核那樣去組織目錄結構來存放你的系統調用,但這樣就得你自己去寫Makefile了。

我要新增的系統調用是一個用於計算加法的函數,其原型如下:

點擊(此處)折疊或打開

  1. asmlinkage int sys_myadd(int a,int b)
  2. {
  3.     return a+b;
  4. }

比較簡單,其中的asmlinkage表示我們這個函數要從匯編語言中來調用,這也就是我們所看到的為什么所有的系統調用的實現函數腦袋上都頂了一個這玩意兒的原因,使用asmlinkage的另外一個原因是表示我們這個函數使用棧來傳遞參數。

我們新增的系統調用的實現函數sys_myadd()位於kernel/sys.c文件的末尾:

然后,在linux-2.6.21\include\asm-i386\unistd.h中修改如下:

1、新增一行#define __NR_myadd     320

2、並將原來的#define NR_syscalls 320改為#define NR_syscalls 321。

由此可見,x86其實是不鼓勵我們新增自定義的系統調用,新增一個系統調用要改兩個地方,不像arm那樣簡單,只需要在arm架構的unistd.h中按系統調用號順序遞增加1即可。修改后的結果如下所示:

最后,修改linux-2.6.21\arch\i386\kernel\syscall_table.s,增加新系統調用的函數入口:

為了使我們新增的系統調用sys_myadd能生效,接下來我們要重新編譯內核,然后將其加載。用戶空間的調用方式如下:

點擊(此處)折疊或打開

  1. /* my syscall test user-space source file :test.c*/
  2. #include <stdio.h>
  3. #include <linux/unistd.h>

  4. int main(int argc,char** argv)
  5. {
  6.         int result=syscall(320,1,2);
  7.         printf("result=%d\n",result);
  8.         return 0;
  9. }

當然,這可能和我們常見的系統調用比起來有些另類,但是沒關系,你完全可以自定義一個函數,比如add(),然后對其syscall(320,1,2)進行一層封裝,然后就可以像下面這樣子調用了:

點擊(此處)折疊或打開

  1. /* my syscall test user-space source file :test.c*/
  2. #include <stdio.h>
  3. #include <linux/unistd.h>

  4. int add(int a,int b)
  5. {
  6.      return syscall(320,a,b);
  7. }

  8. int main(int argc,char** argv)
  9. {
  10.      int result= add(1,2);
  11.      printf("result=%d\n",result);
  12.      return 0;
  13. }

根據博文“從頭構建自己的linux系統”里我們介紹的方法,重新編譯內核鏡像,然后用如下的命令:

gcc -static -o addtest test.c

來編譯用戶空間的應用程序,然后將其放到initrdbin目錄下。之所以gcc要用static是因為我們的整個系統都是以靜態鏈接的形式存在的,沒有動態依賴庫,所以如果不加static那么編譯出來的可執行程序在我們的Mini系統上是跑不起來的,不信你可以試一下。

VMWare固然強大,但是在我們目前這種學習環境里顯得有些臃腫,每次都要先進到一個標准linux系統,然后將我們編譯生成的initrdbzImage分別拷貝到/boot目錄然,然后重啟系統選擇加載我們自己的Mini系統,不論initrdbzImage任何一個有改變時都需要重復這個繁瑣的過程,今天我們介紹另一款當下比較流程Qemu模擬器,用它來模擬我們加載我們自己定制的Mini

最新的Qemu Manager 7.0已經推出,功能之強大毫無遜色於VMware,但解壓后只有40M多。可以下載最新的綠色版來用。為了支持網絡功能還需要安裝openvpn-2.0.9-install.exe,可以從“ openvpn-2.0.9-install.rar   ”下載。當openvpn-2.0.9-install.exe安裝完成后會生成一個新的本地連接,如下:

QemuManager_7.0.rar解壓到本地目錄,注意路徑中不能包含中文,否則要報錯。我將其放在D:\linux目錄下,最后的截圖如下:

緊接着在D:\linux\qman70里建立system\mylinux目錄,將我們編譯生成的initrdbzImage拷貝到mylinux中,然后開始創建Qemu虛擬機。

1、運行QemuManager.exe,什么也不用改,都用默認配置即可。

2、建立虛擬系統。

3、為新系統分配內存和硬盤大小。

4、顯示方式選擇為Qemu窗口顯示。

完成后的效果如下:

為了使用網絡,我們還需要配置Network Card1選項,VLAN TYPE一定要選擇Tap Netowrking類型,適配器選擇我們openvp所生成的那個網絡連接,我這里是本地連接5,如下所示:

然后在“Advanced”標簽頁配置內核鏡像和ramdisk文件所在的路徑,而且還可以配置內核啟動參數,即加載內核時傳遞什么樣的參數如給他,目前我們用不到這個。最后的配置結果如下所示:

最后,將openvpn生成的本地連接的IP地址配置成和我們的Mini系統在一個網段,就可以了,默認情況下Mini Linux的網絡地址是192.168.1.1。運行我們的系統:

一切OK,我們自己開發的系統調用的應用程序也工作的很愉快。

小結:我們可以看到系統調用這種用戶-內核空間的通信方式確實比較麻煩,所以一般情況下我們都不用這種方法。原因已經不厭其煩的解釋過了,但是通過今天的學習相信大家對Linux系統調用的認識和理解都有了一個全新的認識,同時,也掌握了如何開發系統調用的方法,對自我能力的提升都是很有幫助。


免責聲明!

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



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