contributed by < foxhoundsk
>
kernel version: 5.1.10
linux2020
insmod
是如何將初始化參數傳入核心模組insmod
使用 finit_module(2)
進行將模組植入核心的動作,而這個系統呼叫的第二個參數(param_values
)即是指向帶入參數的字串的指標。
finit_module
做完權限檢查以及將欲載入的模組預載(這邊使用預載一詞是因為之後會再 kmalloc
一塊實際使用的記憶體)到記憶體後會呼叫 load_module
以繼續進行模組載入。
load_module
在實際配置並載入(memcpy
)模組需要的記憶體後,會呼叫一名為 find_module_sections
的函數,其作用是將模組的 ELF 檔中的各個 section 的位置找到並個別填入模組結構體(struct module
)中對應的 field 內,這邊我們想關注的是結構體中名為 kp
(struct kernel_param
) 的 field,這個指標對應的 ELF section 是 __param
。
先解釋一下 __param
這個 ELF section 是怎麼來的。在 main.c
中,我們使用 module_param
這個巨集來將全域變數指定為 module parameter。以下解析假設代入的變數是 port
。這個巨集展開後如下:
而 module_param_named
展開後如下:
撇開 param_check_##type
這個靜態型別檢查巨集不看,我們看到 module_param_cb
這個巨集,其展開後如下:
而 __module_param_call
展開後如下:
其中第 3 行首先宣告一個字串。替換後第 3 行會是:
接著在第 4 行是宣告一個資料型態為 struct kernel_param
的結構體,其中的 __used
展開後是 GNU extension,其用意為告知編譯器這個結構體雖然在這個 compilation unit 沒有被用到,但它其實時有用到的,請編譯器務必不要優化掉這個結構體。
再往下看,第 6 行旨在告知編譯器這個結構體在這個 compilation unit 本來就沒有用到,請編譯器不要發警告。接著 __section__ ("__param")
是要求這個結構體要放在名為 __param
的 section 中。最後一個參數 aligned(sizeof(void *))
是要求這個結構體的佔用空間要為 sizeof(void *)
的倍數,以利記憶體對齊。
接著看到第 7 行,這邊先列出 struct kernel_param
的所有成員:
這邊我們要關注的是 ops
以及 arg
這兩個成員。賦值給 ops
的是巨集參數 ops
(與結構體成員同名),這個參數替換後是 ¶m_ops_##type
,然後 concatenation 做完後變成 ¶m_ops_ushort
,這邊我們先看到另一份原始碼檔案 kernel/params.c
的一部分:
巨集 STANDARD_PARAM_DEF
是個通用巨集,所有模組參數的資料型態都會用此巨集做個別的宣告(這邊僅貼上 ushort
相關的宣告)。經過一連串 concatenation 後,我們可以拿到名為 param_ops_ushort
的 struct kernel_param
結構體,其中的兩個 function pointer 則是個別指向 ushort
專用(kstrtou16
是 ushort
專用的字串轉數值 method)的 get 以及 set 的 method。
現在我們可以看出 struct kernel_param
中 ops
成員拿到的是 ushort
專用的參數操作 method。
接著我們看到 arg
這個成員,這邊使用 union 的原因是模組參數的資料型態有一般數值(int
, bool
等等)、字串以及陣列,但一個 struct kernel_param
只用到一種資料型態,所以使用 union 可以節省些許記憶體。
巨集參數 arg
(與結構體成員同名) 替換後是 &port
,這個 port
即為我們當初傳入的全域變數,現在他的位置被保存至 arg
(struct kernel_param
的成員) 內。
至此相關巨集已介紹完,現在我們回來看 load_module
。
load_module
再來會呼叫 strndup_user
將 userspace 傳入的字串參數複製到 kernelspace,以利待會做字串解析相關操作。
再往下,load_module
呼叫 parse_args
進行字串解析。parse_args
主要使用 while 迴圈搭配 next_arg
以及 parse_one
這兩個 method 來解析字串參數中所有的參數:
其中 parse_one
會在解析完成後將參數賦值給核心模組中的全域變數,也就是 port
。
以下是 parse_one
主要程式碼:
由於所有的模組參數呼叫了巨集 module_param
,所以他們個別的 struct kernel_param
會坐落於一塊連續的記憶體中,也就是 ELF 檔中的 __param
section,因此我們可以看到第 3 行在比較參數名稱時是使用 params[i]
(params
為一指向 struct kernel_param
的指標) 來參照參數名稱。
最後我們看到第 15 行,這邊呼叫的 set
即是前面提到的用巨集封裝宣告的 method。第一個傳入參數 val
即是準備要賦值給全域變數 port
的字串參數(set
會做字串解析以及賦值)。第二個傳入參數則是 port
對應的 struct kernel_param
結構體。
以上為模組參數從 insmod
以 cmdline 參數傳入至寫入模組全域變數 port
的過程。
epoll 系統呼叫,用於監視單個行程下多個 fd (file descriptor)。比起 pool(2)
以及 select(2)
, epoll 在事件發生(由 epoll_wait(2)
通知)時,行程可直接處理已就緒的若干個 fd,而不需自己 traverse 所有監聽的 fd,因此在 fd 數量大時有較佳的效率。
htstress 的原理
比起每次關閉連線時都將其對應記憶體釋放,將其另外保存至其他 list 會更好,因為配置記憶體是不小的開銷。關於額外的 list,可以用兩串 list 實做,用兩串 list 是為了防止 entry 一新增到 list 就被 sweeper (GC, garbage collection) 回收掉。如此一來,一個被釋放的 entry 在真正被釋放前會被保存在 list 中至少一個 GC cycle。而如果 list 中有 entry 存在,又,我們剛好需要新 entry,這時我們就不用做記憶體配置而是直接從 list 取用即可。
需注意若實做不佳很可能會浪費省下的時間,例如: lock contention。
invbool
核心模組參數的資料型態中比較特別的是 invbool
,這個型態顧名思義,會將輸入反相。也就是說,如果使用者帶入 true
,實際上會被 evaluate 為 false
。
儘管可以使用 aligned
attribute 告知 GCC 欲對齊的大小,但如果 linker 沒有支援到該大小的話,則會被限制至 linker 支援的最大 alignment 大小。