# 新增自定義system call ###### tags: `OS` `kernel` 如題,本次作業要將自定義的system call加到kernel中,其步驟參考為[1][5] - system call需求:將學號輸出至kernel log中,其優先級為*KERN_EMERG* - 環境:kernel v4.4.1 > [kernel安裝](https://hackmd.io/yHcqeiGnR8KhS6PMJWtq3A?view) ## 步驟 1. system call source code實作 2. 增加system call header 3. Makefile擴充 3.1 增加剛實作的source code的依賴規則 3.2 在kernel Makefile新增檢索路徑 4. 修改system call table (handler table) 5. 重新編譯安裝 6. 測試 --- 7. 修改成從user level傳遞參數 --- ### 1. system call實作 - 以額外擴展的思想去實作system call(也就是新增一個module的概念),盡量不改動kernel source code以利方便分割自訂義的code,所以另建新檔案。 - 實作的目標檔案為: hello.c ``` $cd /usr/src/kernel-4.4.1 $mkdir my && cd my $vi hello.c ``` ![](https://hackmd.io/_uploads/rJWWZfml6.png) - **#include<linux/kernel.h>** 該標頭檔包含了kernel所需的各種macro和function declaration,因為我們需要使用printk,所以需要引用該檔。 - **asmlinkage** 這個keyword的使用是依照於架構的abi,也就是說我在x86的架構下適用stack傳遞參數[2][3][4]。查看kernel source code的其他架構會發現有一些架構如alpha也是用這個keyword,其他架構則無。 - **long** 主要還是因為在kernel中system call實作習慣用long作為回傳型態[6],更詳細可參考[7] - **sys_hello** 自訂義funtion name。 - **printk** 由kernel.h所提供的函數,用於輸出訊息到kernel log file[8][9]。 - **KERN_EMERG** 設定優先級的參數,作為最高級(最優先)[8],也是本次作業要求。 ### 2. 增加system call header 在*include/linux/syscalls.h*下新增header宣告,如892行 ![](https://hackmd.io/_uploads/ry7Tu7ml6.png) ### 3. Makefile擴充 一個新增,一個修改。 #### 3.1 增加剛實作的source code的依賴規則 為遵守額外擴展的想法,我們一樣在my資料夾下另建一個Makefile去增加依賴規則,在下一個步驟3.2將這個規則納入kernel的主要makefile中。 ``` $vi Makefile ``` ![](https://hackmd.io/_uploads/rJzN9mXe6.png) - obj-y 將module編進kernel中[10] - := 即時賦值(會取代原本的),所以若其他地方也有用到obj-y:=時,要確保編譯順序。 #### 3.2 在kernel Makefile新增檢索路徑 在是否要編譯額外module的賦值那邊加入我們剛剛自訂義的資料夾"*my/*"。可以觀察到它還有我們之前上一個作業設定menuconfig時所見到的選項,如fs, ipc, security, crypto,也就是我們先前所選擇要額外增加的modules在這邊一併包進去kernel了。 ![](https://hackmd.io/_uploads/Hk1lpQQep.png) ### 4. 修改system call table (handler table) system call處理時都需要一個table,也就是我們要修改的這個檔案 在各自架構的資料夾下分別可以找到對應的檔案 如x86_64是在*arch/x86/entry/syscalls/syscall_64.tbl* ![](https://hackmd.io/_uploads/HJZ90XXxp.png) 在編號的最後尾端增加我們的編號326 system call 其中64位元下的format是<number> <abi> <name> <entry point>[11] - <number> 該system call編號 - <abi> 這邊common指通用在64-bit底下兼容32-bit - <name> 自訂義的名稱 - <entry point> 實際上所使用的函數 那至於為什麼要在新增在編號尾端?因為算是約定俗成的,除了方便和兼容性考量以外,有些syscall是直接呼叫編號,所以任意更動編號可能會造成不可預期的錯誤[11][12]。 ### 5. 重新編譯安裝 ``` $sudo make bzImage -j6 $sudo make modules -j6 $sudo make modules_install $sudo make install $sudo update-grub $sudo reboot ``` ### 6. 測試 寫一個測試程式去呼叫執行該system call看是否成功運作。 ``` $vi test.c ... $gcc test.c $./a.out ``` ![](https://hackmd.io/_uploads/HyCkXE7gT.png) ``` $dmesg ``` ![](https://hackmd.io/_uploads/SkW2BN7gp.png) 可以看到最後一行成功了。 --- ### 7. 修改成從user level傳遞參數 原本是直接print輸出指定的訊息,現在改成可以從user level傳遞輸出字串的參數。 先嘗試修改宣告跟定義那邊 ``` asmlinkage long sys_hello(char* msg) ``` ``` asmlikage long sys_hello(char* msg) { printk(KERN_EMERG "Hi, %s\n",msg); return 0; } ``` 重新編譯安裝並執行測試檔再用*dmesg*觀察。 會看到在kernel log檔的部分無限loop輸出我們給定的字串,例如*Hi, xxx......* (無圖) 之後我們觀察*syscall.h*其他函數是如何運作傳遞參數的 ![](https://hackmd.io/_uploads/Bk_g3H4e6.png) 看到第884行有看到string的傳遞通常都會加個__user作為代表從user-space傳遞來的標誌,而後又在[1]看到*copy-from-user*這個字眼,去找資料後大概有了概念,kernel中都會有特定的操作不同於user-space,接著修改成以下的程式碼。 ![](https://hackmd.io/_uploads/S1tP8Y4xp.png) 892行已修改成增加const和__user ![](https://hackmd.io/_uploads/BygHIYEep.png) - **#include <linux/slab.h>** 包含kmalloc和kfree的標頭檔。 - **#include <linux/uaccess.h>** 包含copy_from_user的標頭檔。 - **strlen_user** 在kernel-space的strlen()[14]; - **kmalloc(len, GFP_KERNEL)** kernel-space的mallo,memory的一般正常分配,可能會進入sleep[13]。 - **-EINVAL** 作為linux的錯誤回報標誌,此為無效參數[15]。 - **-ENOMEM** 作為linux的錯誤回報標誌,此為記憶體分配不足或無法分配[15]。 - **copy_from_user** kernel-space的strncpy[16],不過為什麼要這樣copy而不直接使用傳入的*msg*,是因為不同space要存取各自的memory section,如果從kernel去存取user-space的data可能會發生非預期的錯誤,所以才另外做copy把value變成在kernel-space使用的。 - **kfree** kernel-spcae的free()。 - **-EFAULT** 作為linux的錯誤回報標誌,此為錯誤的address[15]。 ![](https://hackmd.io/_uploads/Hyf6UFExa.png) 測試用,直接呼叫我們定義的syscall。 ![](https://hackmd.io/_uploads/r1iL2YNxp.png) 成功傳遞參數! --- ## 心得   本次作業其實若是只要實作出來的話難度並不高,但可以從中學習到一般的system call是如何在kernel中起作用的,一開始我選用的方式較為簡單一點並且沒有透過user level去傳遞參數,是直接輸出的方式。 稍微比較有挑戰性的是去觀察它的coding style,實作上也可以在source code裡面去用*SYSCALL_DEFINE*這個macro定義新的syscall[5],其中還要分類新的syscall是屬於什麼類型的並找到相關syscall定義的地方增加進去,另外在做傳遞參數時可以藉此機會多看看linux menu,可以更了解linux kernel的coding style,而且因為機制不同,或者是防止不同space之間存取到錯誤的資訊的潛在危險,所以在kernel-space要用不同於user-space的方式去做類似的事情。 另外在新增syscall的過程中有一些錯誤它是不會像一般compiler那樣幫你報錯的,還是要靠自己肉眼去觀察那裡有沒有出錯,過程中有一些小出錯導致debug了一下下。 整體來說較新一點的版本相對於2.x版已經很平易近人了,添加syscall的過程很有脈絡。 --- linux kernel source code細節補充_參考[17] --- ## Refernece 1. https://hackmd.io/@combo-tw/Linux-%E8%AE%80%E6%9B%B8%E6%9C%83/%2F%40combo-tw%2FBJPoAcqQS#%E5%AF%A6%E4%BD%9C 2. https://www.cs.uic.edu/~tkhatiwa/kernel/kernel_gotchas.html 3. https://kernelnewbies.org/FAQ/asmlinkage 4. https://www.jollen.org/blog/2006/10/_asmlinkage.html 5. https://www.kernel.org/doc/html/v4.10/process/adding-syscalls.html 6. http://books.gigatux.nl/mirror/kerneldevelopment/0672327201/ch05lev1sec2.html#ch05fn04 7. https://stackoverflow.com/questions/20940212/why-is-linux-syscall-return-type-long 8. https://www.kernel.org/doc/html/next/core-api/printk-basics.html 9. https://docs.nxlog.co/glossary/kernel-log.html 10. https://meetonfriday.com/posts/5523c739/ 11. https://member.adl.tw/ernieshu/syscall_3_14_4.html 12. https://www.win.tue.nl/~aeb/linux/lk/lk-4.html 13. https://www.kernel.org/doc/html/next/core-api/memory-allocation.html 14. https://www.cs.bham.ac.uk/~exr/lectures/opsys/12_13/docs/kernelAPI/x3674.html 15. https://man7.org/linux/man-pages/man3/errno.3.html 16. https://www.cs.bham.ac.uk/~exr/lectures/opsys/12_13/docs/kernelAPI/r4037.html 17. https://github.com/MintCN/linux-insides-zh/blob/master/SysCall/linux-syscall-2.md