1. 程式人生 > >使用者空間和核心空間通訊之【系統呼叫】

使用者空間和核心空間通訊之【系統呼叫】

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

第一步,編寫核心服務程式利用核心空間提供的許可權和服務來接收、快取和處理資料第二步,編寫使用者程式來和先前的核心服務程式進行互動

具體來說,可以利用使用者程式來配置核心服務程式的引數,獲取核心服務程式提供的資料,也可以向核心服務程式輸入資料。

我們可以看到,使用者程式和核心的資訊交換可以是雙向的,也就是說既可以由使用者主動向核心空間傳送訊息,也可以由核心空間主動向使用者提交資料。當然,使用者程式也可以主動從核心提取資料。

針對上述應用場景,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個,如下所示:

23069658_1347977220xVKv.jpg

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

應用程式呼叫適當的值

填充暫存器,然後呼叫一個特殊的指令,跳轉到核心某一固定的位置,核心根據應用程式所填充的固定值來找到相應的函式,然後開始執行該函式。

注意上述關鍵詞的描述。

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檔案裡)

23069658_1347977262mjce.jpg

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

23069658_1347977367bb0K.jpg

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

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檔案的末尾:

23069658_1347977397lm3m.jpg

然後,在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即可。修改後的結果如下所示:

23069658_1347977435GMJB.jpg

最後,修改linux-2.6.21\arch\i386\kernel\syscall_table.s,增加新系統呼叫的函式入口:

23069658_1347977455t30l.jpg

為了使我們新增的系統呼叫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系統上是跑不起來的,不信你可以試一下。

23069658_13479774845DGW.jpg

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

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

23069658_1347977509hF9g.jpg

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

23069658_1347977532SQ68.jpg

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

1、執行QemuManager.exe,什麼也不用改,都用預設配置即可。

23069658_1347977557Z9fn.jpg

2、建立虛擬系統。

23069658_1347977571swwW.jpg

3、為新系統分配記憶體和硬碟大小。

23069658_134797758775jh.jpg

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

23069658_13479775902BRg.jpg

完成後的效果如下:

23069658_13479775948IQ3.jpg

為了使用網路,我們還需要配置Network Card1選項,VLAN TYPE一定要選擇Tap Netowrking型別,介面卡選擇我們openvp所生成的那個網路連線,我這裡是本地連線5,如下所示:

23069658_1347977604JRHJ.jpg

然後在“Advanced”標籤頁配置核心映象和ramdisk檔案所在的路徑,而且還可以配置核心啟動引數,即載入核心時傳遞什麼樣的引數如給他,目前我們用不到這個。最後的配置結果如下所示:

23069658_134797759871G1.jpg

最後,將openvpn生成的本地連線的IP地址配置成和我們的Mini系統在一個網段,就可以了,預設情況下Mini Linux的網路地址是192.168.1.1。執行我們的系統:

23069658_1347977626Z9hD.jpg

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

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

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script> 閱讀(6040) | 評論(0) | 轉發(13) | 給主人留下些什麼吧!~~ 評論熱議