測試環境
測試用 Linux Kernel 版本 3.14.4 (The Linux Kernel Archives
作業系統版本 Fedora 20(32 位元與 64 位元)
硬體平台 x86-64
本文適用的 Kernel 版本 ≥ 3.4

前言

系統呼叫(system call)是一般應用程式與作業系統核心溝通最重要的管道,而作業系統所制定的 ABI 之中,就包含了應用程式發起系統呼叫時,所應遵循的標準;反過來說,這也指定了系統核心該如何接收應用程式所發起的系統呼叫。在本文所討論的 x86 架構之中,所可能遇到的情況由於 x86-64 支援 x86-32 的關係,變得比較複雜,如下表所列:

名稱 含意
i386 當 Linux 核心使用 32 位元的 x86 系統(也就是常說的 x86-32)時,核心唯一支援的 ABI。
amd64 當 Linux 核心使用 64 位元的 x86 系統(也就是常說的 x86-64)時,核心最主要支援的其中一種 ABI。由於現在的 x86-64 最早是由 AMD 所提出的,Linux 的 ABI 沿用了這個老名字,基本上 AMD64 = Intel64 = x86-64。(注意,這不是 Intel 的 ia64)
ia32 emulation 由於 x86-64 支援 x86-32 的指令集,當 Linux 核心使用 64 位元的 x86 系統時,Linux 核心可以同時支援應用程式以 i386 ABI 來運作,然而,畢竟核心還是以 64 位元指令執行,因此,其實只能算是對應用程式模仿(emulation)出 32 位元的環境罷了,因此本文以「ia32 emulation」指稱此種情況。(不確定正式稱呼為何
x32 這是當 Linux 核心使用 64 位元的 x86 系統時,核心可以支援的其中一種的 ABI。這種 ABI 最大的特色在於 利用 x86-64 的指令進行運算、定址等,可是在程式內的指標與 long 型態大小皆為 32 位元,請注意,這個 ABI 與 i386 ABI 是不同且不相容的,詳細資料可見 Wiki
注意

為了避免混亂,本文內的描述皆採用上方的名稱,讀者可根據頂端右方的按鈕,篩選顯示的內容(然而,由於許多概念息息相關,筆者設定的篩選可能有點「不乾不淨」的現象)。

不過在進行實驗之前,必須認清一件事實,在 x86-64 的核心中,雖然可以同時支援「amd64」、「ia32 emulation」和「x32」三種 ABI,應用程式的成功執行卻是需要核心與系統的其他部份(例如 libc)合作才能達成。然而,在不同的 Linux 發行版(distribution,例如 Fedora、Ubuntu 等)中,後面 2 種 ABI 可能沒有完善的支持,像本文所使用的 64 位元 Fedora 20 中,就不支援 x32 的編譯環境。

此外,本文中以 linux-source 象徵核心原始碼的根目錄,root-directory 象徵系統根目錄,要新增系統呼叫的名字是 project,參數型態為兩個 C 字串指標,回傳值型態為 long(Linux 系統呼叫皆以 long 作為回傳型態,作用統一為描述執行成功與否的代碼)。

環境準備

Linux Kernel 在許多的發行版上有其自己修訂過的版本,以便符合發行版的特殊需求,為了尋求在不同發行版上也能實驗的 Linux Kernel,我們選擇 The Linux Kernel Archives 上所提供的版本。

下載完核心原始碼後,先嘗試編譯看看能否成功運作,通常在 linux-source/README 中會有詳細說明。

在設定核心編譯設定的過程中,要注意各種 ABI 的支援有沒有開啟,在此以筆者針對 make menuconfig 的經驗作說明:

  1. 首頁的「64-bit kernel」選項代表選擇 x86-64 或 x86-32 核心,也代表支援 amd64 或 i386 的 ABI,這個選項與安裝的發行版必須一致,所以不能亂改。
  2. 如果選擇的是 x86-64 的核心,那麼「Executable file formats / Emulation」中的「IA32 Emulation」和「x32 ABI for 64-bit mode」就分別代表對 ia32 emulation 或 x32 的支援,當然,如果安裝的發行版已經使用了這些功能,那就絕對不能關掉它們。
  3. 不過,無論這些選項有沒有開啟,都不影響核心內程式碼的實作,Linux Kernel 原始碼中,會利用核心編譯的設定檔,自動選擇哪些部份的實作要參與編譯。我們所要考慮的,是要不要花心思為各種 ABI 製作實作的版本,來讓使用該 ABI 的應用程式能成功進行系統呼叫。

除此之外,最好也把系統載入程式 GRUB 的開機選單開出來,畢竟人非聖賢,萬一之後實驗失敗,我們才可以還原回舊系統進行補救。

開始修改核心原始碼

  1. 修改 linux-source/arch/x86/syscalls/syscall_*.tbl

    Linux 核心原始碼利用 syscall_*.tbl 來產生供編譯時使用的系統呼叫表(system call table),由於產生方法為字串擷取,修改時要注意一些規則,以避免擷取出錯誤的結果。

    • 行首只要非數字就不被擷取,但我們遵從 shell 腳本的習慣,使用「#」來做為註解的標記。
    • 非註解的資料行,要根據特定的格式修改。
    1. syscall_32.tbl

      此檔案負責 i386 與 ia32 emulation 相關系統呼叫表的產生,完整格式如下:
      <number> <abi> <name> <entry point> <compat entry point>
      而每一項之間由數量不等的空白或 <Tab> 鍵隔開。

      number 代表系統呼叫號碼(system call number,也是應用程式辨識的唯一基準)。
      abi 代表支援的 ABI。(只需要填i386
      name 代表系統呼叫的名字。
      entry point 代表與系統呼叫相對應的實作函式。
      compat entry point 當使用 ia32 emulation 時,用來替換的實作函式。

      i386 相關

      當我們想為 i386 新增系統呼叫的時候,compat entry point是沒有作用的,所以總共就只有 4 項資料要填。由於系統呼叫號碼有特定規範,因此我們應該加在表的尾端,在實驗的環境中,改好的的尾端如下:

                                ...(略)...
                            351    i386    sched_setattr    sys_sched_setattr
                            352    i386    sched_getattr    sys_sched_getattr
                            # Add by Ernie
                            353    i386    project          sys_project
                          

      number 欄位根據原本的最後一個系統呼叫來遞增,至於 entry point,在核心中的慣例是使用「sys_[系統呼叫名稱]」,包括之後會提到的協助進行系統呼叫實作的巨集都採用這種慣例。

      ia32 emulation 相關

      當我們想為 ia32 emulation 的環境新增系統呼叫的時候,必須認知到一個事實,ia32 emulation 原本就是為了讓在 x86-32 系統上的應用程式能跨平台在 x86-64 的平台運作而設立的,所以一旦想要增加 ia32 emulation 上的系統呼叫,必定也要實作在 x86-32 系統上的 i386 ABI。

      然而,相同的系統實作程式碼,在 x86-32 系統上與 x86-64 系統上是有許多地方不同的,例如:指標的大小,這樣的不同,有可能導致實作程式碼的錯亂,為了解決這種問題,才有了 compat entry point 欄位,如果定義了 compat entry point,那麼在 ia32 emulation 的環境下,就會以此函式來處理系統呼叫;如果沒有定義,則沿用 entry point 欄位。

      當我們想為 ia32 emulation 新增系統呼叫的時候,在實驗的環境中,改好的尾端如下:

                                ...(略)...
                            351    i386    sched_setattr    sys_sched_setattr
                            352    i386    sched_getattr    sys_sched_getattr
                            # Add by Ernie
                            353    i386    project          sys_project          compat_sys_project
                          

      number欄位根據原本的最後一個系統呼叫來遞增,至於entry point,在核心中的慣例是使用「sys_[系統呼叫名稱]」,包括之後會提到的協助進行系統呼叫實作的巨集都採用這種慣例。而在 compat entry point 方面,核心中的慣例是使用「compat_sys_[系統呼叫名稱]

      註:這種情況下,i386 和 ia32 emulation 的設定總共只有一行,不需也不可分開設定。

      註:在實作時,新的 system call 應該依據最後一筆系統呼叫的系統呼叫號碼來遞增,這主要由於 User mode 是通過系統呼叫號碼來呼叫 System Call 的,而系統中原本的系統呼叫號碼,在 Linux Kernel 設計時就已經決定用途,依據最後一筆來遞增,可以保證我們使用的系統呼叫號碼不會跟已經訂好的系統呼叫號碼發生衝突。

    2. syscall_64.tbl

      此檔案負責 amd64 與 x32 相關系統呼叫表的產生,完整格式如下:
      <number> <abi> <name> <entry point>
      而每一項之間由數量不等的空白或 <Tab> 鍵隔開。

      number 代表系統呼叫號碼。
      abi 代表支援的 ABI。(只能是 64x32common,分別表示「只支援 amd64」、「只支援 x32」或「都支援」。)
      name 代表系統呼叫的名字。
      entry point 代表與系統呼叫相對應的實作函式。

      amd64 相關

      當我們想為 amd64 新增系統呼叫的時候,要注意到 syscall_64.tbl 的一個地方,那就是系統呼叫號碼,凡是支援 amd64 的系統呼叫,它們的號碼由 0 開始;而支援 x32 的系統呼叫(跟 amd64 共用的那些不算),它們的號碼則由 512 開始。所以加的時候要找一下,最後一筆支援 amd64 的系統呼叫在哪裡,並緊接著新增。在我們的實驗中,改好的的檔案如下:

                                ...(略)...
                            314    common    sched_setattr    sys_sched_setattr
                            315    common    sched_getattr    sys_sched_getattr
                            # Add by Ernie
                            316    64        project          sys_project
      
                            #
                            # x32-specific system call numbers start at 512 to avoid cache impact
                            # for native 64-bit operation.
                            #
                            512    x32       rt_sigaction     compat_sys_rt_sigaction
                            513    x32       rt_sigreturn     stub_x32_rt_sigreturn
                                ...(略)...
                          

      number 欄位根據原本的最後一個支援 amd64 系統呼叫來遞增,至於 entry point,在核心中的慣例是使用「sys_[系統呼叫名稱]」,包括之後會提到的協助進行系統呼叫實作的巨集都採用這種慣例。

      x32 相關

      當我們想為 x32 新增系統呼叫的時候,與 amd64 類似,要注意到 syscall_64.tbl 的一個地方,那就是系統呼叫號碼,凡是支援 amd64 的系統呼叫,它們的號碼由 0 開始;而支援 x32 的系統呼叫(跟 amd64 共用的那些不算),它們的號碼則由 512 開始。

      如果要新增的系統呼叫,可以跟 amd64 相容,那麼就應該新增 abicommon 的記錄,number < 512;如果適用於 x32,那麼就應該新增 abix32 的記錄,number ≥ 512。只不過,相容不相容的標準,筆者還不清楚,因此 x32 在本文不做測試

  2. 在標頭檔中加入函式宣告

    接著,要把我們的函式宣告加到系統呼叫專用的標頭檔中,一般的 .c 實作檔,如果需要呼叫或對函式進行實作,就應該引入這些標頭檔。一般來說,根據不同的情況,我們應該加在不同的標頭檔中:

    系統呼叫的實作函式類型 加入方法
    sys_*
    也就是系統內主要 ABI 的系統呼叫
    如果是通用於所有硬體平台(例如 IA64、PowerPC)的系統呼叫,那麼應該將宣告放於
    linux-source/include/linux/syscalls.h
    如果是只適用於 x86 平台的系統呼叫,那麼應該將宣告放於
    linux-source/arch/x86/include/asm/syscalls.h
    compat_sys_*
    也就是系統內各種因為額外支援的 ABI 而需使用的相容性實作函式。
    應該將宣告放於
    linux-source/include/linux/compat.h

    不過前面有提到,Linux Kernel 編譯時會根據編譯設定來對程式碼做取捨,這件事反映在程式碼中,是利用 C 的前處理指令達成的(通常是用 #ifdef),在加入或修改時,要根據自己想要的情況來使用,下表列了一些跟支援 ABI 相關的定義:

    定義名稱 意義
    CONFIG_X86_32 代表是 x86-32 的核心。
    CONFIG_X86_64 代表是 x86-64 的核心。
    CONFIG_IA32_EMULATION 代表啟用 ia32 emualation。
    CONFIG_X86_X32 代表啟用 x32 ABI。
    CONFIG_COMPAT 代表啟用相容其它 ABI 的模式。(包括 ia32 emulation 跟 x32)

    由於我們並沒有想實作在所有的硬體平台上(其實是還沒研究清楚),所以我們的 sys_project 的宣告,就應該放在 linux-source/arch/x86/include/asm/syscalls.h,而且因為本文希望通用於 i386 與 amd64,所以要避免陷入 CONFIG_X86_32 的區域,所以改完之後的結果如下:

                      ...(略)...
                  /* kernel/tls.c */
                  asmlinkage long sys_set_thread_area(struct user_desc __user *);
                  asmlinkage long sys_get_thread_area(struct user_desc __user *);
    
                  /* Add by Ernie  */
                  asmlinkage long sys_project(char __user *, char __user *);
    
                  /* X86_32 only */
                  #ifdef CONFIG_X86_32
                      ...(略)...
                  #else
                      ...(略)...
                  #endif /* CONFIG_X86_32 */
                  #endif /* _ASM_X86_SYSCALLS_H */
                

    而在函式定義前面必須加上 asmlinkage 以確保編譯時連結的正確性;而在指標類參數加上 __user 是在編譯時提供一些幫助除錯的資料,用意為此指標的內容會指向 userspace 下的位址。

    ia32 emulation 相關

    另一方面,由於我們會藉由 compat_sys_project 來提供 ia32 emulation 的相容性,因此需要在 linux-source/include/linux/compat.h 加入函式宣告,當然,這裡也要小心 #ifdef 構成的「陷阱」。改完後的檔案如下:

                      #ifndef _LINUX_COMPAT_H
                      #define _LINUX_COMPAT_H
                      /*
                       * These are the type definitions for the architecture specific
                       * syscall compatibility layer.
                       */
    
                      #ifdef CONFIG_COMPAT
                          ...(略)...
                      /* Add by Ernie */
                      asmlinkage long compat_sys_project (char __user *, char __user *);
    
                      #else
    
                      #define is_compat_task() (0)
    
                      #endif /* CONFIG_COMPAT */
                      #endif /* _LINUX_COMPAT_H */
                    
  3. 將實作檔加入核心原始碼架構中

    原則上,除了通用的程式碼,不同硬體架構的程式碼,其實作檔應該放到不同硬體架構的目錄下,所以在本實驗中,我們要把實作檔安排在 linux-source/arch/x86/ 之下。然而,應該安排在其下哪個目錄,應該根據程式碼的功能而定,例如記憶體管理相關應放在 mm/,網路相關應放在 net/,筆者所選擇的是 kernel/

    接著,為了以後修改的方便,筆者決定新增名為 my_project/ 的目錄,以方便以後將所有相關的實作檔都收攏在一起。至於所要新增或修改的檔案,在此先條列如下:

    位置 處理方式 描述
    x86/kernel/Makefile 修改 調整編譯設定,以包含我們的實作檔目錄
    x86/kernel/my_project/Makefile 新增 進行編譯設定,指定實作檔
    x86/kernel/my_project/project.c 新增 主要實作檔(供 i386 與 amd64)
    x86/kernel/my_project/compat_project.c 新增 相容性函式的實作檔(供 x32)

    接著,下面開始一一介紹如何處理:

    1. 實作檔的撰寫

      首先,我們必須知道系統呼叫的實作方式有兩種:

      • 直接使用 asmlinkage sys_[系統呼叫名稱] ([一般 C 語法的參數宣告])
      • 使用系統巨集
        SYSCALL_DEFINEx([系統呼叫名稱], [參數一型態], [參數一名稱], [參數二型態], [參數二名稱], ...)
        其中,x 是參數的個數,範圍是 0~6。要特別注意,參數型態和參數名稱以「逗號」隔開。

      第一種方法,是最接近 C 語言原生的實作方法;然而,由於 32 位元平台與 64 位元平台的差異性,Linux 曾出現了一個重大的系統漏洞(此漏洞在 CVE 上的編號是 CVE-2009-0029),為了解決這個漏洞,從 2.6.28.1 開始才提出了第二種方法(最早版本),後來又增加了對 ftrace(一種系統函式的追蹤機制)的支援。也就是說,為了保證和其他系統機制能正確共同運作,應該盡可能使用第二種方法來實作

      接著,對於 project.c 來說,我們的實作必須引入 SYSCALL_DEFINEx 和實作函式宣告的標頭檔,所以應該引入

      • linux/syscalls.h(對應 linux-source/include/linux/syscalls.h
      • asm/syscalls.h(對應 linux-source/arch/x86/include/asm/syscalls.h
      所以,我們的 project.c,會是這樣:

                        #include <linux/printk.h>    // 為了使用 printk 函式
                        #include <linux/syscalls.h>
      
                        #include <asm/syscalls.h>
      
                        SYSCALL_DEFINE2(project, char __user *, str_1, char __user *, str_2)
                        {
                            printk(KERN_INFO"%s %s\n", str_1, str_2);
                            return 0;
                        }
                      
      ia32 emulation 相關

      再來,對於 compat_project.c 來說,我們用它來實作 compat_sys_project,雖然這個實作也可以和 sys_project 放在一起,不過那要使用 #ifdef CONFIG_IA32_EMULATION 包覆來指定使用的時機(關於 CONFIG_* 請參照前面說明);而在本文中,為了演示利用 Makefile 來控制編譯的方法,將其獨立出來。

      在實際實作方面,與前述規則相似,應使用類似的 COMPAT_SYSCALL_DEFINEx 來進行;另一方面,為了引入實作函式宣告的標頭檔,我們應該引入

      • linux/compat.h(對應 linux-source/include/linux/compat.h
      所以,我們的 compat_project.c,會是這樣:

                            #include <linux/printk.h>    // 為了使用 printk 函式
                            #include <linux/compat.h>
      
                            COMPAT_SYSCALL_DEFINE2(project, char __user *, str_1, char __user *, str_2)
                            {
                                printk(KERN_INFO"%s %s\n", "Another", str_2);
                                return 0;
                            }
                          

      註:為了明顯看出此函數的運作,我們讓它與 sys_project 有不一樣的行為;在真正的核心開發中,一致的功能才是正確的設計理念。

    2. Makefile 的部分

      到目前為止,我們已經將需要的實作檔都放進核心的原始碼架構了,接著要做的,就是讓我們的實作檔加入核心原始碼的編譯設定中,我們的實作檔才會隨著核心的編譯加入進去。

      因為我們在 linux-source/arch/x86/kernel/ 下新增了 my_project 資料夾,所以首先要告訴核心的編譯設定,在這個資料夾下,有額外的編譯設定要去讀取,而 Linux Kernel 編譯設定是階層式的架構,也就是說,這項修改應該加在 linux-source/arch/x86/kernel/ 的 Makefile 中。修改時要注意的是

      • 修改的點放的越後面越好,比較不受編譯相依性的影響。
      • 修改時要小心其中的 if ... endif 的區段,避免受到不必要的設定影響。
      於是,linux-source/arch/x86/kernel/Makefile 修改如下:

                            ...(略)...
                        ###
                        # 64 bit specific files
                        ifeq ($(CONFIG_X86_64),y)
                            ...(略)...
                        endif
      
                        # Add by Ernie
                        # Make 'kbuild' to include my directory.
                        obj-y        += my_project/
                      

      要特別注意,obj-y += my_project/ 在最後象徵目錄的「/」一定要加,不然有可能產生誤會;而 += 是指把我們的目錄「附加」進目前為止對 obj-y 的設定。

      緊接著,就是 my_project 目錄中的 Makefile 了,由於核心的編譯預設會把每個 .c 檔編譯成同名的 .o 檔,所以我們應該把目錄中的每個 .c 檔所對應的 .o 檔都加入編譯設定,所以內容如下:

                        obj-y                          := my_project.o
                        obj-$(CONFIG_IA32_EMULATION)   += compat_project.o
                      

      其中第二行的部分 obj-$(CONFIG_IA32_EMULATION) 就表示由 CONFIG_IA32_EMULATION 決定我們用來處理 ia32 emulation 的部分要不要編譯進去。不過,如果沒有實作跟 ia32 emulation 或 x32 相關的系統呼叫,第二行這種形式讀者應該不會用到。

  4. 重新編譯

    到了這一步,新增系統呼叫就可以算是完成了,接著就開始重編核心吧!如果失敗,回頭看看有沒有錯漏的地方,等到能夠成功用新核心開機後,就可以開始進行測試了。

測試系統呼叫

在測試新增的系統呼叫之前,我們要複習一件事:應用程式的成功執行卻是需要核心與系統的其他部份合作才能達成。在 x86-32 的作業系統上,i386 ABI 是一定支援的;而在 x86-64 的作業系統上,amd64 ABI 是一定支援的,可是 i386 ABI(以核心的觀點是 ia32 emulation)和 x32 ABI 卻是不一定支援的。在測試之前先搞清楚自己的作業系統支援那些環境是必要的,這方面筆者不是很了解,不過,雖然不知道,筆者的驗證方法是:先編個測試檔試試再說,以下是使用 gcc 時,指定 ABI 的方法。

ABI 指令格式
i386 gcc -m32 [原始檔] -o [輸出檔]
amd64 gcc -m64 [原始檔] -o [輸出檔]
x32 gcc -mx32 [原始檔] -o [輸出檔]

註:如果沒有 -mXX 選項,預設以當前作業系統的平台為準。此外,有些系統還需要安裝額外的套件才能成功運作,此方面請讀者自行查詢資料安裝。

在筆者針對 ia32 emulation 調整之後(參照網路資料調整),兩套作業系統平台中,總共可以支援 i386、amd64 和 ia32 emulation 三種模式。現在,我們可以開始進行系統呼叫的測試了,在使用 C 語言測試時,有兩種方法可以用來測試:

兩種方法中,前者需要對組合語言與 ABI 有相當程度的認識,相較之下,我們自然是採用後者。而參數是由我們設計出來的,剩下唯一的問題,就是系統呼叫號碼,這個號碼,需要從核心編譯產生的檔案中尋找,我們在以下用 kernel-output 代表編譯時的輸出目錄(如果編譯時沒有使用 O=... 選項來指定,預設與 linux-source 相同)。

i386 相關

對於 32 位元系統唯一支持的 i386 ABI 來說,我們需要從 kernel-output/arch/x86/include/generated/uapi/asm/unistd_32.h 尋找 __NR_[系統呼叫名稱] 所定義的值,理論上會與修改 linux-source/arch/x86/syscalls/syscall_32.tbl 時加入的號碼相同,在本例中,也就是 353,而完成的測試檔如下:

              #include <unistd.h>

              int main(){
                  syscall(353, "Hello", "World");
                  return 0;
              }
            

編譯並執行後,使用 dmesg 指令,就可以發現 printk 所產生的「Hello World」字樣。

ia32 emulation 相關

對於 64 位元系統所支持的 i386 ABI(也就是核心中的 ia32 emulation)來說,我們需要從 kernel-output/arch/x86/include/generated/uapi/asm/unistd_32.h 尋找 __NR_[系統呼叫名稱] 所定義的值,理論上會與修改 linux-source/arch/x86/syscalls/syscall_32.tbl 時加入的號碼相同,在本例中,也就是 353,而完成的測試檔如下:

              #include <unistd.h>

              int main(){
                  syscall(353, "Hello", "World");
                  return 0;
              }
            

編譯並執行後,使用 dmesg 指令,由於我們對於 compat_sys_project 的實作,可以發現 printk 產生了「Another World」字樣。

amd64 相關

對於 64 位元系統主要支援的 amd64 ABI 來說,我們需要從 kernel-output/arch/x86/include/generated/uapi/asm/unistd_64.h 尋找 __NR_[系統呼叫名稱] 所定義的值,理論上會與修改 linux-source/arch/x86/syscalls/syscall_64.tbl 時加入的號碼相同,在本例中,也就是 316,而完成的測試檔如下:

              #include <unistd.h>

              int main(){
                  syscall(316, "Hello", "World");
                  return 0;
              }
            

編譯並執行後,使用 dmesg 指令,就可以看到 printk 所產生的「Hello World」字樣。

x32 相關

對於 64 位元系統所支援的 x32 ABI 來說,由於我們並沒有真的實作,以下的說法均是研究中產生的認知,不一定完整。如果我們實作了這部分,理論上需要從 kernel-output/arch/x86/include/generated/uapi/asm/unistd_x32.h 尋找 __NR_[系統呼叫名稱] 所定義的值,理論上會是修改 linux-source/arch/x86/syscalls/syscall_64.tbl 時加入的號碼再加上特定位移值 __X32_SYSCALL_BIT

經過這些測試,如果系統運作一切正常,系統呼叫的回應也符合預期,認定系統呼叫實作完結之後。為了以後的編譯環境方便起見,我們可以再做一些收尾的動作。

收尾

當確認新增的系統呼叫沒有問題後,我們會發現,每次撰寫程式碼都要記住系統呼叫號碼,而且不同 ABI 使用的號碼也不同,使用起來頗不便利,我們可以開始考慮把有關的設定寫入到系統的引用檔中(通常位於 root-directory/usr/include/),以方便以後的使用。(所以不做也可以)

警告:以下修改比較難以回復!請在確認新增的系統呼叫沒有問題後,再繼續進行!

在 Linux 核心原始碼的 Makefile 中,提供有 make headers-install 來修改系統的引入檔(說明檔位於 linux-source/Documentation/make/headers_install.txt),不過,在安裝時由於它會檢查 root-directory/usr/include/linux/version.h 以避免發生舊版本覆寫新版本的錯誤,有時會產生略過的狀況(畢竟並非正式的核心開發版本)。筆者在此建議的做法是先將原始系統引入檔備份出來(如果出差錯才有回復的機會),接著手動指定 INSTALL_HDR_PATH 選項把核心標頭檔安裝到新資料夾(這樣才保證能通過版本檢查),再把它整個「硬寫」到系統引入檔內,所以整個流程應該如下(當然,這麼做可能導致系統不穩,不過,反正是實驗嘛 :)):

          mkdir ~/origin_include
          cp -R /usr/include ~/origin_include
          sudo make headers_install INSTALL_HDR_PATH=~/new_include
          sudo cp -R ~/new_include/include /usr
        

經過對系統引入檔的修改後,我們在撰寫程式碼時,就可以統一用下面這種方式來進行系統呼叫(適用所有 ABI):

          #include <unistd.h>
          #include <sys/syscall.h> /* 導入 __NR_project */

          int main(){
              syscall(__NR_project, "Hello", "World");
              return 0;
          }
        

其中,__NR_[系統呼叫名稱]就是「Number of [系統呼叫名稱]」的意思。不過,雖然程式碼相同,但是編譯的參數仍然是不同的。接著,就可以用這些程式測試看看有沒有問題。

而在這最後一步完成之後,我們的系統呼叫就大功告成了!