Linux设备驱动-模块加载过程
開篇
本文引用的內核代碼參考來自版本 linux-5.15.4 。
在用戶空間,用指令 insmod 來向內核空間安裝一個內核模塊,其使用方法如下:
insmod xx.ko /* 向內核空間安裝模塊 xx */注意,加載內核模塊需要具有 root 權限,否則會加載失敗。
當調用 “insmod xx.ko” 來安裝 “xx.ko” 內核模塊時,insmod 會首先利用文件系統的接口,將模塊文件的數據讀取到用戶空間的一段內存中,然后通過系統調用 sys_init_module 讓內核去處理模塊加載的整個過程。
系統調用 sys_init_module
sys_init_module() 函數的原型為:
long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs);參數 umod,是指向用戶空間內核模塊文件映像數據的內存地址。參數 len,是該文件的數據大小。第三個參數 uargs,是傳給模塊的參數在用戶空間下的內存地址。
函數的具體代碼如下(已經將函數名稱替換為實際展開后的形式):
/* <kernel/module.c> */ ? long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs); {int err;struct load_info info = { }; ?err = may_init_module(); /* 判斷是否有加載模塊的權限 */if (err)return err; ?pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n", umod, len, uargs); ?/* 將模塊文件數據復制到內核空間 */err = copy_module_from_user(umod, len, &info);if (err)return err; ?/* 加載模塊 */return load_module(&info, uargs, 0); }由以上代碼可知,加載模塊的工作主要是通過 load_module 函數完成的。
該函數完成模塊加載的全部任務,原型為:
static int load_module(struct load_info *info, const char __user *uargs, int flags)參數 info 為結構指針,指向存儲模塊文件數據的結構。參數 uargs,與函數sys_init_module 的參數 uargs 相同。參數 flags 為加載標志。
函數的主要功能為:分配模塊需要的內存資源,然后將模塊加載到內核中。
模塊加載成功,返回值為 0。加載失敗,則返回錯誤碼(負值)。
關鍵數據結構
結構體 struct load_info
在加載過程中會用到一個類型為 load_info 的結構體變量 info,此變量在模塊加載過程中臨時記錄一些參數。結構體 load_info 的定義如下:
/* <kernel/module-internal.h> */ ? struct load_info {const char *name;/* pointer to module in temporary copy, freed at end of load_module() */struct module *mod;Elf_Ehdr *hdr; ? ? /* 模塊文件內容指針 */unsigned long len; /* 模塊文件大小(字節數) */Elf_Shdr *sechdrs;char *secstrings, *strtab;unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;struct _ddebug *debug;unsigned int num_debug;bool sig_ok; #ifdef CONFIG_KALLSYMSunsigned long mod_kallsyms_init_off; #endifstruct {unsigned int sym, str, mod, vers, info, pcpu;} index; };結構體 struct module
此結構體用來管理系統中加載的模塊,是一個非常重要的數據結構。
一個 struct module 對象代表著一個內核模塊在 Linux 系統的抽象。由于該結構成員變量特別多,只列出了關鍵的幾個成員變量,并做了注釋說明,如下:
/* <include/linux/module.h> */ ? struct module {enum module_state state; /* 記錄模塊加載過程中的不同階段狀態 */struct list_head list; ? /* 用來將模塊鏈接到系統維護的內核模塊鏈表中 */char name[MODULE_NAME_LEN]; /* 模塊名稱 */const struct kernel_symbol *syms; /* 內核模塊導出的符號所在起始地址 */const s32 *crcs; /* 內核模塊導出符號的校驗碼存放地址 */struct kernel_param *kp; /* 內核模塊參數所在地址 */int (*init)(void); /* 內核模塊初始化函數的指針 */struct list_head source_list; /* 用來在內核模塊之間建立依賴關系 */struct list_head target_list;void (*exit)(void); /* 內核模塊退出函數指針 */ }; 模塊加載過程不同階段的狀態,module_state 定義如下:enum module_state {MODULE_STATE_LIVE, /* 模塊成功加載進系統時的狀態 */MODULE_STATE_COMING, /* 配置完成,開始加載模塊 */MODULE_STATE_GOING, /* 加載過程出錯,退出加載 */MODULE_STATE_UNFORMED, /* 正在建立加載配置 */ };加載函數 load_module
此函數主要分兩部分功能:一部分完成模塊加載最核心的任務;第二部分是,模塊被加載到系統的后續處理。
load_module 第一部分
-
構造模塊 ELF 的內存視圖
通過調用 copy_module_from_user()函數,將用戶空間的模塊文件數據復制到內核空間中,從而在內核空間構造出模塊的一個 ELF 靜態內存視圖。也就是 HDR 視圖,加載完成后會將其釋放。
-
創建字符串表
字符串表是 ELF 文件中的一個 section,用來保存 ELF 文件中各個 section 的名稱或符號。通過調用 setup_load_info(info, flags); 會創建這個字符串表,并得到 section 名稱字符串表的基地址 secstrings。
-
find_sec 函數
通過調用此函數,內核尋找某一個 section 在 section header table 中的索引值。分別查找以下 section:“.modinfo”、“__versions”、“.gnu.linkonce.this_module”,保存查到的索引值,以備將來使用。
-
HDR 視圖第一次改寫
第一次遍歷 section header table 中的所有 entry,修改 entry 中的 sh_addr ,計算語句如下:
shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;這樣每個 entry 中的 sh_addr 指向該 entry 所對應的 section 在 HDR 視圖中的實際存儲地址。
-
struct module 類型變量 mod 初始化
結構體 struct module 是一個非常重要的數據結構,內核用來表示一個模塊。在 load_module 函數中定義了一個 struct module 類型的變量 mod。調用 mod = layout_and_allocate(info, flags); 分配需要的內存,并初始化。
-
HDR 視圖的第二次改寫
這次改寫中,HDR 視圖中絕大多數的 section 會被搬移到新的內存空間中,使得其中 section header table 中各個 entry 的 sh_addr 指向最終的內存地址。
-
模塊導出的符號
模塊可以向外部導出自己的符號。如果一個內核向外界導出了自己的符號,那么模塊編譯工具鏈負責生成這些導出的符號 section。而這些 section 都帶有 SHF_ALLOC 標志,模塊在加載過程中會被搬移到 CORE section 區域中。
-
find_symbol 函數
第一部分,在內核導出的符號表中查找指定的符號。第二部分,在系統已經加載的模塊導出的符號表中查找符號。
-
對“未解決的引用”符號的處理
“未解決的引用符號“,就是模塊編譯鏈接生產 .ko 文件時,對于模塊中調用的一些函數,鏈接工具無法在所有的目標文件中找到某個函數的指令碼,鏈接工具會將這個符號標記為”未解決的引用符號“。模塊被加載時,內核會解決這些符號。
-
重定位
主要用來解決靜態鏈接時的符號引用,與動態加載時的實際符號地址不一致的問題。
-
模塊參數
在用 insmod 加載模塊時,有時需要向模塊傳遞一些參數,內核模塊本身在源代碼中必須用宏 module_param 聲明模塊可以接收的參數。內核加載器可以得到從命令行傳過來的實際參數。
-
處理模塊間的依賴關系
內核能跟蹤模塊間的依賴關系,在模塊加載過程中,建立模塊之間的依賴關系。
-
模塊的版本控制
版本控制主要用來解決內核模塊和內核之間的接口一致性問題。避免模塊使用內核已經改變或廢棄的接口,而導致加載失敗或者存在風險的問題。
-
模塊的信息
模塊最終的 ELF 文件中都會有一個名為 ”.modinfo“ 的 section,以文本形式保留著模塊的一些信息。加載過程中,內核需要獲得 ”.modinfo“ section 中的相關信息,以便進一步處理。包括:模塊的 license、模塊的 vermagic。
load_module 第二部分
第二部分通過調用 do_init_module()函數完成。
在這個函數中,首先調用模塊的構造函數 do_mod_ctors()。然后調用模塊的初始化函數,也就是 mod 中 init 指針指向的函數,初始化函數通過 do_one_initcall()完成調用。
模塊完成加載之后,HDR 視圖和 INIT section 所占的內存空間不再使用,需要釋放它們。
HDR 視圖在調用 do_init_module()之前完成釋放(調用 free_copy(info))。INIT section 在 do_init_module()結尾完成釋放。
模塊加載進系統之后,鏈接到內核維護的模塊鏈表 modules 中,該鏈表記錄著系統中所有已加載的模塊。
公眾號【一起學嵌入式】,獲取更多精彩內容。
?
總結
以上是生活随笔為你收集整理的Linux设备驱动-模块加载过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第十四届恩智浦智能汽车大赛车队规划概要
- 下一篇: 【笔记】人工智能 一种现代方法 人工智能