contributed by < steven523
>
檢查 Linux 核心版本是否大於等於 5.15.0
確認 linux-headers 套件已正確安裝於開發環境
insmod
後,Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API)、MODULE_LICENSE
巨集指定的授權條款又對核心有什麼影響 (GPL 與否對於可用的符號列表有關),以及藉由 strace 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統?insmod
insmod 是一個用於將模組掛載到 Linux 核心中的命令。Linux 有許多功能是通過模組的方式,在需要時才載入核心。這樣可以使核心整體較精簡,從而提高效率。
練習第一個 Linux 核心模組的撰寫、掛載及卸載
建立 hello 目錄並在其中建立 Makefile 及 hello.c 後,使用以下命令編譯核心模組:
掛載核心模組並查看核心訊息:
卸載核心模組並查看核心訊息:
fibdrv
: 可輸出 Fibonacci 數列的 Linux 核心模組
通過查看 /sys/class/fibonacci/fibonacci/dev
和 /dev/fibonacci
來瞭解裝置的主次編號,而這兩個數字是由系統在註冊裝置時動態分配的,並且可能會因系統而異。
在 fibdrv.c
中,主編號是在呼叫 register_chrdev
函式時指定的。如果 register_chrdev()
的第一個參數為 0,系統會自動分配一個可用的主編號。如果是一個正整數,則系統會嘗試使用這個指定的主編號。
以下是 fibdrv.c
中的 init_fib_dev()
:
register_chrdev
函式被用來註冊一個新的 Character Device,並將傳回的主編號指派給 major
變數。這就是為什麼看到的主編號可能是 511 或 236,兩個數字都是系統自動分配的,具體的值取決於系統在註冊裝置時可用的主編號。
不過 register_chrdev()
這個 function 已經算是較舊版的 api,他存在著以下缺點:
所以對於撰寫 Character Device,建議使用像 ksort/sort_mod.c 裡的 alloc_chrdev_region()
和 register_chrdev_region()
這兩個新版的 api,較具靈活性。
Linux 核心模組掛載機制
從 kernel/module/main.c 中可以看到 load_module
,它是負責載入模組的主要函式,它會呼叫 simplify_symbols
來解析模組中的符號。
在 simplify_symbols
會根據符號的類型來進行不同的處理,對於未定義的符號(SHN_UNDEF
)會呼叫 resolve_symbol_wait
,然後再呼叫 resolve_symbol
。
最後在 resolve_symbol
呼叫 find_symbol
並使用 List API 的 list_for_each_entry_rcu
函式來逐一走訪核心的符號表,尋找與模組中未定義的符號名稱相匹配的符號,如果找到則將該符號的地址填充到模組的符號表中,這樣模組就可以在運行時訪問到這些符號。
掛載核心模組時需要 MODULE_LICENSE()
巨集,用於指定模組的授權條款,它告訴使用者和開發者該模組的授權方式,以及他們可以如何使用和修改這個模組。
以下表格是 Linux kernel licensing rules 裡提供的 MODULE_LICENSE()
valid license strings
fibdrv.c
中使用到的 MODULE_LICENSE("Dual MIT/GPL")
,表達這個模組採用雙重許可,能在 GPL v2 變體或 MIT 許可證中進行選擇。EXPORT_SYMBOL_GPL()
巨集導出的服務或函式,因為 EXPORT_SYMBOL_GPL()
只向 GPL 相容的模組顯示符號。MODULE_LICENSE("GPL")
,那這個核心將被視為 "tainted" (受污染)。意思是核心已經載入了非自由軟體的模組,可能無法得到維護在以上結果中可以看到:
execve
:用於執行 insmod
指令openat
、read
、close
、finit_module
,用於開啟、讀取、關閉模組檔案以及載入模組mmap
、munmap
,用於記憶體映射和解除記憶體映射brk
、arch_prctl
、set_tid_address
、exit_group
,用於管理程式的堆疊空間、設定執行緒的狀態和 ID 位址、退出程式prlimit64
用於設定行程的資源量getrandom
系統呼叫用來獲取隨機數透過不同的系統呼叫來執行不同的功能,就能夠構建 Linux 核心的相關操作。
至於 finit_module(3, "", 0) = -1 EEXIST (File exists)
,代表先前已經有掛載過該模組,所以掛載失敗,如果是第一次掛載的話結果會顯示 0
。
The alias attribute causes the declaration to be emitted as an alias for another symbol, which must have been previously declared with the same type, and for variables, also the same size and alignment. Declaring an alias with a different type than the target is undefined and may be diagnosed.
意思是說,透過 alias
這個屬性將宣告作為另一個符號的別名發出,該符號之前必須使用相同的類型進行宣告,對於變量也必須具有相同的大小和對齊方式。
最後一行可以看到 gcc 會在編譯過後將 initfn
設為 int init_module(void)
的別名,以下範例:
可以看到最後一行會利用 module_init
巨集將 hello
函式指定為模組初始化函式。
所以當我們掛載這個模組時,就會呼叫 hello
這個函式來執行模組的初始化,並輸出 "Hello, world!"。