# Linux 筆記 3 ###### tags: `Linux 讀書會` ## System Call ### System call簡介 系統呼叫 (system call,簡稱為 syscall),指運行在 **user space 的程式向OS kernel**請求需要更高權限運行的服務。 :::info system call 是 process 和 OS 之間的介面,當使用者程式需要 OS 的服務時,使用者程式便去呼叫 system call 只在user mode就能做完的服務: 像是運算變數行為是透過ALU硬體做運算,如果多個process存取該變數會有race condition,但如果要避免race condition作"上解鎖行為"就算是system call ::: System call 的種類大致分為 * Process Control * 對 process 做控制,如 * process 的啟動、停止 * 給予 process 記憶體空間 * 讀取、設定 process 屬性 * 讓 process 作等待 * e.g. `fork()`, `wait4()` * File Management * 對檔案做控制,如 * 檔案的建立與刪除 * 檔案的開啟與關閉 * 檔案的讀取與寫入 * 讀取或設定檔案的屬性 * e.g. `open()`, `read()`, `write()`, `close()` * Device Management * 電腦系統各項硬體資源與周邊設備均為 device,當 process 在執行時會去要求取得資源或存取資料,如 * Device 的要求與釋放 * Device 資料的存取 * 讀取或設定 device 的屬性 * 掛載或卸載 device * e.g. `ioctl()` * System Information Maintenance * 對系統資料的更新與維護,如 * 設定日期與時間 * 存取系統資料 * 存取 process、file、device 的屬性 * e.g. `getpid()`, `getppid()`, `getpgid()`, `setpgid()` * Communication * 當裝置以連線連通,執行訊息傳遞等 I/O 存取的行為,如 * 建立或中斷連線 * 輸入輸出網路資料 * 狀態資訊的轉換 (Transfer status information) * 使用遠端裝置 * e.g. `pipe()`, `socket()` ### Dual Mode 作業系統存在的意義是提供一個抽象層,讓使用者能夠方便使用或開發應用程式,這個方便的環境就是 user space。 user space 的應用程式若需要與作業系統或硬體相關的功能就要仰賴系統呼叫。而在 Linux 中依照其權限分為兩種模式 - **user mode 和 kernel mode**。目的為**保護系統安全**,一般在 user space 的應用程式不允許直接對 kernel space 的資料做存取 ### 呼叫流程 system call 參數傳遞分為三種方法 * 利用 Registers 儲存參數 * Pros : 快速 * Cons : register 數量有限,參數不能太多 * 將參數存在 Memory Block 後,把該 block 的起始位址存在一個 register 中,之後將此 register 傳遞給 OS * Pros : 可存參數較多 * Cons : 速度較慢 * 利用 system stack 儲存參數,藉由 push 來保存參數,pop 來取出參數 * Pros : 可存參數較多 * Cons : 速度較慢   從application program->glibc wrapper function和Trap handler->system call server routine的過程是透過API 上面這兩張圖可以很明顯的知道執行 system call 所經過的流程 1. 在使用者程式中呼叫 system call 2. 藉由一個軟體中斷 trap (svc #0) 進入 kernel mode,此時系統會將 mode bit 由 user mode 改成 kernel mode (1 -> 0) 3. 查詢 system call table 來找尋對應的 trap service routine 4. 當執行完 trap service routine 後發出中斷通知 OS 已經完成 function call->Interrupt->透過查詢interrupt vector table得知採用哪種interrupt service routine(ISR)->從該ISR得知要查詢system call table->查詢到做哪個system call service routine後完成該system call service routine->呼叫中斷切回user space ### vsyscall 和 vDSO 在system call放置一些常用的核心變數,例如:`gettimeofday`、`time`、`getcpu` 等,這些變數放在 vsyscall、vvar,如果把上述變數先放在system call就不需再切換kernel mode減少模式切換時間。 ## 實作 ### 如何新增一個 System Call 1. 建立一個放自定義 `syscall` 的資料夾 * `$ cd linux && mkdir workspace` 2. 寫一個自定義的 `syscall` * `$ vim workspace/hello_world.c` ```C= # include <linux/kernel.h> asmlinkage long sys_hello_world(void) { printk("Hello World!\n"); return 0; } ``` 3. 再建立一個 `Makefile` * `$ vim workspace/Makefile` ```C= obj-y := hello_world.o ``` 4. 改 kernel `Makefile` 來告訴 compiler 說新的 system call 可以在哪邊找到 * `$ vim Makefile` * 找到 `core-y` 並在最後面添加之前所建立的資料夾,這樣 kernel 編譯時才會找到自定義的目錄 ```SHELL=957 ... ifeq ($(KBUILD_EXTMOD),) core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ workspace/ ... ``` 5. 修改 system call table * `$ vim arch/arm/tools/syscall.tbl` * 在最後一行上添加自定義的 `syscall`,這邊可以先記住 system call number,等一下會用到 ```C=413 ... 397 common statx sys_statx 398 common hello_world sys_hello_world ``` 6. 最後在 system call header file 上添加自定義的 `syscall` 的 prototype * `$ vim include/linux/syscalls.h` ```C=941 ... unsigned mask, struct statx __user *buffer); asmlinkage long sys_hello_world(void); ``` 7. 重新編譯 kernel :::info 在 kernel v4.9 以前,要新增一個 `syscall` 需要修改 `arch/arm/kernel/calls.S`、`asm/unistd.h`、`uapi/asm/unistd.h` 和 `include/linux/syscalls.h` 這些檔案 但是從 kernel v4.10 開始,Russell King 提供了一個自動計算 `__NR_syscalls` 的 shell script,因此只需要更改 `arch/arm/tools/syscall.tbl` 和 `include/linux/syscalls.h` 就可以了 可參閱︰[[PATCH 2/3] ARM: convert to generated system call tables](https://patchwork.kernel.org/patch/9382833/) ::: ### 如何測試一個 System Call #### Method 1 - 透過 Initrd 1. 先寫一個簡單測試 `sys_hello_world` 的程式 * `$ vim hello.c` ```C= #include <linux/kernel.h> #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> int main(void) { long retval = syscall(398); while (1) ; } ``` 2. 使用靜態編譯 * `$ arm-linux-gnueabihf-gcc -static -o hello hello.c` 3. 將 `hello` 製作成 cpio * `$ echo hello | cpio -o --format=newc > rootfs` 4. 使用 QEMU 啟動後即可看見 `sys_hello_world` 所印的結果 * 這邊我們把根目錄直接設成記憶體,啟動用的 `init` 程式直接設成我們之前放進去的 `hello` * ```SHELL $ sudo qemu-system-arm \ -M versatilepb \ -cpu arm1176 \ -m 256 \ -dtb versatile-pb.dtb \ -kernel qemu-kernel-4.14.50 \ -initrd rootfs \ -append "root=/dev/ram rdinit=/hello" \ -nographic ``` > 延伸閱讀︰[什麼是 initrd](https://developer.ibm.com/articles/l-initrd/)、[如何使用 initrd](https://www.kernel.org/doc/html/latest/admin-guide/initrd.html) #### Method 2 - 透過 Raspbian disk image 1. 使用 QEMU 開機後 2. 寫一個簡單測試 `sys_hello_world` 的程式 * `$ vim hello.c` ```C= #include <linux/kernel.h> #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> int main(void) { long retval = syscall(398); return retval; } ``` 3. 編譯並執行 * `$ gcc -o hello hello.c` * `$ ./hello` 4. 查看 printk 印出來的結果,最後一行為 `sys_hello_world` 所印的結果 * `$ dmesg` ``` ... [ 65.952997] Adding 102396k swap on /var/swap. Priority:-2 extents:1 across:102396k [ 78.207853] Hello World! ``` ## 本章節練習與反思 * 在 Linux Kernel 的實作中大量使用了 [asmlinkage](https://elixir.bootlin.com/linux/v4.14.50/source/arch/arm64/kernel/sys.c#L30) 和 [SYSCALL_DEFINEn](https://elixir.bootlin.com/linux/v4.14.50/source/arch/arm64/kernel/sys.c#L40),請問這兩個作用為何?又帶來了什麼好處? asmlinkage與SYSCALL_DEFINEn(巨集)都是讓function call與底層溝通的syscall 差異在asmlinkage偏向於特製化syscall功能,編譯速度快但是開發速度較慢 而SYSCALL_DEFINEn算是提供function call一個泛用的syscall功能,給予該巨集function name跟parameter對該function做類似的system call功能,在開發速度上會比較快但編譯速度比較慢 * 請說明在 Linux Kernel 中的 syscall 是用什麼方法傳遞參數的 在syscall裡面由於user space與kernel space算是不同的process,process兩者的記憶體是無法共用的,所以在傳遞變數時只傳值並無法改變變數的數值,要透過copy_to_user()或是copy_from_user()來傳遞參數,前者是在kernel把位址再傳回user space,後者則是將位址傳遞到kernel。 * 請寫出一個計算階乘 $n!$ 的 syscall `factorial()` > 測試程式位於[此](https://github.com/combo-tw/LinuxBookClub/blob/master/chapter/03-syscall/test/factorial_ut.c) > 測試方法為當 QEMU 啟動後,進入 `/home/pi/test` 資料夾,並執行 `make check` 即可 * 請解釋一般的 function call 與 syscall 的差異與其開銷為何,可分別從名詞解釋、呼叫流程和實驗下手 function call與system call差異: 1. system call需透過中斷切換至kernel mode,從user mode切至kernel mode也有時間開銷,在開銷上system call > function call 2. 但function call的用途只能運算,無法對於硬體實際操作,仍須透過system call進入kernel mode對硬體操作 * 請寫下遇到的困難是怎麼去解決和除錯的 1.一開始在linux路徑先建立system call檔案的資料夾workspace並在內部撰寫system call function,然後在workspace底下創建一個makefile 2.在linux路徑底下修改makefile增加system call路徑告訴compiler新的makefile可在哪裡找到 3. $ vim arch/arm/tools/syscall.tbl 修改system call table 在最後一行添加自定義的system call 4.$ vim include/linux/syscalls.h 在 system call header file 上添加自定義的 syscall 的 prototype最後再重新編譯一次kernel 在整體下來遇到最大的困難是我在kernel算出n!的數值但在user space得知我的答案都是錯的,是因為user space傳遞的參數在kernel space計算完後無法傳遞回去,需要用copy_to_user()來傳遞參數 ## 練習與反思引用資料 [linux核心剖析---Linux系統呼叫詳解(實現機制分析)](https://www.itread01.com/content/1546200738.html) [什麼是asmlinkage](http://www.jollen.org/blog/2006/10/_asmlinkage.html) [Linux系统调用之SYSCALL_DEFINE](https://blog.csdn.net/hxmhyp/article/details/22699669) [系統呼叫和庫函式及API的區別](https://www.itread01.com/content/1544889789.html)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up