模塊的加載過程一
ps:kernel symbol內(nèi)核符號表,就是在內(nèi)核的內(nèi)部函數(shù)或變量中,可供外部引用的函數(shù)和變量的符號表。. 其實說白了就是一個索引文件,它存在的目的就是讓外部軟件可以知道kernel文件內(nèi)部實際分配的位置。
先來個圖:
在用戶空間,用insmod這樣的命令來向內(nèi)核空間安裝一個內(nèi)核模塊,本節(jié)將詳細(xì)討論模塊加載時的內(nèi)核行為。當(dāng)調(diào)用“insmod demodev.ko”來安裝demodev.ko這樣的內(nèi)核模塊時,insmod會首先利用文件系統(tǒng)的接口將其數(shù)據(jù)讀取到用戶空間的一段內(nèi)存中,然后通過系統(tǒng)調(diào)用sys_init_module讓內(nèi)核去處理模塊加載的整個過程。
sys_init_module
原型:
asmlinkage long sys_init_module(void __user *umod, unsigned long len,
const char __user *uargs);
其中,第一參數(shù)umod是指向用戶空間demodev.ko文件映像數(shù)據(jù)的內(nèi)存地址,第二參數(shù)len是該文件的數(shù)據(jù)大小,第三參數(shù)是傳給模塊的參數(shù)在用戶空間下的內(nèi)存地址。
sys_init_module實現(xiàn)
SYSCALL_DEFINE3宏定義
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS##name)))); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{
int err;
struct load_info info = { };
err = may_init_module();//確定具有加載或去除內(nèi)核模塊的能力 并是否使能modules
if (err)
return err;
pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);
if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS
|MODULE_INIT_IGNORE_VERMAGIC))
return -EINVAL;
err = copy_module_from_fd(fd, &info);//copy_module_from_fd () 將打開的模塊文件讀到申請的內(nèi)核內(nèi)存中,并交由 load_module () 處理。
if (err)
return err;
return load_module(&info, uargs, flags);
}
在sys_init_module函數(shù)中,加載模塊的任務(wù)主要是通過調(diào)load_module函數(shù)來完成的,該函數(shù)的定義為:
/* Allocate and load the module: note that size of section 0 is always
zero, and we rely on this for optional sections. */
//二進制數(shù)據(jù)使用load_module傳輸?shù)絻?nèi)核地址空間中,鎖有需要的重定位都會完成,所有iny都會解決
//在load_module中創(chuàng)建module實例已近添加到全局的modues鏈表后,內(nèi)核內(nèi)需要調(diào)用模塊的初始化函數(shù)并釋放
//初始化數(shù)據(jù)占用的內(nèi)存空間
static int load_module(struct load_info *info, const char __user *uargs,
int flags)
{
struct module *mod;
long err;
char *after_dashes;
err = module_sig_check(info);//module_sig_check () 需要開啟
//CONFIG_MODULE_SIG 才會起作用
if (err)
goto free_copy;
err = elf_header_check(info);//主要是檢查一下 ELF 文件頭信息,別忘了模塊文
//件.ko 是 ELF relocatable 文件,所以需要檢查一下是否是正確的 ELF 文件格
//式。
if (err)
goto free_copy;
/* Figure out module layout, and allocate all the memory. */
mod = layout_and_allocate(info, flags);//獲取模塊的布局,重新申請一次內(nèi)存
//,并對相關(guān)section對內(nèi)容進行修改
if (IS_ERR(mod)) {
err = PTR_ERR(mod);
goto free_copy;
}
/* Reserve our place in the list. */
err = add_unformed_module(mod);//將模塊加入到內(nèi)核的模塊鏈表中。
if (err)
goto free_module;
#ifdef CONFIG_MODULE_SIG
mod->sig_ok = info->sig_ok;
if (!mod->sig_ok) {
pr_notice_once("%s: module verification failed: signature "
"and/or required key missing - tainting "
"kernel\n", mod->name);
add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
}
#endif
/* To avoid stressing percpu allocator, do this once we're unique. */
err = percpu_modalloc(mod, info);//對模塊里面的percpu進行特殊的內(nèi)存申請,
//這個percpu變量比較特殊,在每個CPU上都存在一份,所以用特殊的申請內(nèi)存函數(shù)
//去處理
if (err)
goto unlink_mod;
/* Now module is in final location, initialize linked lists, etc. */
err = module_unload_init(mod);//為后續(xù)執(zhí)行rmmod卸載命令時初始化一些引用變
//量以及鏈表等。
if (err)
goto unlink_mod;
/* Now we've got everything in the final locations, we can
* find optional sections. */
err = find_module_sections(mod, info);//找模塊里面的一些section,
//比如參數(shù)的__param節(jié),內(nèi)核符號表__ksymtab節(jié),GPL范圍協(xié)議的符號
//__ksymtab_gpl節(jié),ftrace增加的_ftrace_events節(jié)等待。
if (err)
goto free_unload;
err = check_module_license_and_versions(mod);
//主要是檢查模塊的license是否存在污染內(nèi)核的可能
if (err)
goto free_unload;
/* Set up MODINFO_ATTR fields */
setup_modinfo(mod, info);//對sys屬性進行一些預(yù)先setup處理。
/* Fix up syms, so that st_value is a pointer to location. */
err = simplify_symbols(mod, info);//修復(fù)加載模塊的符號,使符號指向內(nèi)核正確的運行地址。
if (err < 0)
goto free_modinfo;
err = apply_relocations(mod, info);//是對.rel節(jié)和.rela節(jié)進行重定位的一個過程。
if (err < 0)
goto free_modinfo;
err = post_relocation(mod, info);//對重定位后的percpu變量重新賦值,并將即
//將加載完成的模塊的符號加入到內(nèi)核模塊的符號鏈表中,如果成功加載此模塊且
//內(nèi)核配置了CONFIG_KALLSYMS,那么在/proc/kallsyms下可以看到此模塊的符號
if (err < 0)
goto free_modinfo;
flush_module_icache(mod);//執(zhí)行刷新模塊的init_layout和core_layout的cache。
/* Now copy in args */
mod->args = strndup_user(uargs, ~0UL >> 1);//復(fù)制一下模塊的參數(shù)
if (IS_ERR(mod->args)) {
err = PTR_ERR(mod->args);
goto free_arch_cleanup;
}
dynamic_debug_setup(info->debug, info->num_debug);//需要開啟內(nèi)核CONFIG_DYNAMIC_DEBUG才會啟用
/* Ftrace init must be called in the MODULE_STATE_UNFORMED state */
ftrace_module_init(mod);//需要開啟相關(guān)的ftrace配置
/* Finally it's fully formed, ready to start executing. */
err = complete_formation(mod, info);//對模塊的內(nèi)存屬性進行修改,比如.text的讀+執(zhí)行,.data的讀寫屬性。
if (err)
goto ddebug_cleanup;
/* Module is ready to execute: parsing args may do that. */
after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
-32768, 32767, unknown_module_param_cb);//解析模塊參數(shù)
if (IS_ERR(after_dashes)) {
err = PTR_ERR(after_dashes);
goto bug_cleanup;
} else if (after_dashes) {
pr_warn("%s: parameters '%s' after `--' ignored\n",
mod->name, after_dashes);
}
/* Link in to syfs. */
err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);//對sys進行創(chuàng)建的過程
if (err < 0)
goto bug_cleanup;
/* Get rid of temporary copy. */
free_copy(info);//釋放最初內(nèi)核申請的用于保存模塊原文件信息的內(nèi)存
/* Done! */
trace_module_load(mod);//trace相關(guān)的
return do_init_module(mod);//最后模塊執(zhí)行init函數(shù)的過程
bug_cleanup:
/* module_bug_cleanup needs module_mutex protection */
mutex_lock(&module_mutex);
module_bug_cleanup(mod);
mutex_unlock(&module_mutex);
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_GOING, mod);
/* we can't deallocate the module until we clear memory protection */
unset_module_init_ro_nx(mod);
unset_module_core_ro_nx(mod);
ddebug_cleanup:
dynamic_debug_remove(info->debug);
synchronize_sched();
kfree(mod->args);
free_arch_cleanup:
module_arch_cleanup(mod);
free_modinfo:
free_modinfo(mod);
free_unload:
module_unload_free(mod);
unlink_mod:
mutex_lock(&module_mutex);
/* Unlink carefully: kallsyms could be walking list. */
list_del_rcu(&mod->list);
wake_up_all(&module_wq);
/* Wait for RCU synchronizing before releasing mod->list. */
synchronize_rcu();
mutex_unlock(&module_mutex);
free_module:
/* Free lock-classes; relies on the preceding sync_rcu() */
lockdep_free_key_range(mod->module_core, mod->core_size);
module_deallocate(mod, info);
free_copy:
free_copy(info);
return err;
}
所有參數(shù)同sys_init_module函數(shù)中的完全一樣,實際上在sys_init_module函數(shù)的一開始便會調(diào)用該函數(shù),調(diào)用時傳入的實參完全來自于sys_init_module函數(shù),沒有經(jīng)過任何的處理或者修改。
為了更清楚地解釋模塊加載時的內(nèi)核行為,我們把sys_init_module分為兩個部分:第一部分是調(diào)用load_module,完成模塊加載最核心的任務(wù):第二部分是在模塊被成功加載到系統(tǒng)之后的后續(xù)處理。我們將在討論完load_module部分之后再繼續(xù)討論sys_init_module的第二部分。不過,在繼續(xù)load_module話題之前,先要看一個內(nèi)核中非常重要的數(shù)據(jù)結(jié)構(gòu)—------struct module。
struct module
load module函數(shù)的返回值是一個struct module類型的指針,struct module是內(nèi)核用來管理系統(tǒng)中加載的模塊時使用的一個非常重要的數(shù)據(jù)結(jié)構(gòu),一個struct module對象代表著現(xiàn)實中一個內(nèi)核模塊在Linux系統(tǒng)中的抽象,該結(jié)構(gòu)的定義如下〈刪除了一些trace和unused symbol相關(guān)的部分):
struct module {
enum module_state state;//模塊的狀態(tài)
/* Member of list of modules */
struct list_head list;//用作模塊鏈表的鏈表元素
/* Unique handle for this module */
char name[MODULE_NAME_LEN];//該模塊唯一的句柄,模塊名稱
/* Sysfs stuff. */
//導(dǎo)出符號
struct module_kobject mkobj;
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion;
struct kobject *holders_dir;
/* Exported symbols */
const struct kernel_symbol *syms;
const unsigned long *crcs;//內(nèi)核模塊導(dǎo)出符號的校驗碼所在起始地址。
unsigned int num_syms;
/* Kernel parameters. */
struct kernel_param *kp;//內(nèi)核模塊參數(shù)所在的起始地址。
unsigned int num_kp;
/* GPL-only exported symbols. */
//只適用于GPL的導(dǎo)出符號
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const unsigned long *gpl_crcs;
#ifdef CONFIG_UNUSED_SYMBOLS
/* unused exported symbols. */
const struct kernel_symbol *unused_syms;
const unsigned long *unused_crcs;
unsigned int num_unused_syms;
/* GPL-only, unused exported symbols. */
unsigned int num_unused_gpl_syms;
const struct kernel_symbol *unused_gpl_syms;
const unsigned long *unused_gpl_crcs;
#endif
#ifdef CONFIG_MODULE_SIG
/* Signature was verified. */
bool sig_ok;
#endif
/* symbols that will be GPL-only in the near future. */
const struct kernel_symbol *gpl_future_syms;
const unsigned long *gpl_future_crcs;
unsigned int num_gpl_future_syms;
/* Exception table */
//異常表
unsigned int num_exentries;
struct exception_table_entry *extable;
/* Startup function. */
//初始化函數(shù)
int (*init)(void);//init是一個指針,指向一個在模塊初始化時調(diào)用的函數(shù)
//指向內(nèi)核模塊初始化函數(shù)的指針,在內(nèi)核模塊源碼中由module init宏指定。
/* If this is non-NULL, vfree after init() returns */
void *module_init;
/* Here is the actual code + data, vfree'd on unload. */
void *module_core;
/* Here are the sizes of the init and core sections */
unsigned int init_size, core_size;
/* The size of the executable code in each section. */
unsigned int init_text_size, core_text_size;
/* Size of RO sections of the module (text+rodata) */
unsigned int init_ro_size, core_ro_size;
/* Arch-specific module values */
struct mod_arch_specific arch;
unsigned int taints; /* same bits as kernel:tainted */
#ifdef CONFIG_GENERIC_BUG
/* Support for BUG */
unsigned num_bugs;
struct list_head bug_list;
struct bug_entry *bug_table;
#endif
#ifdef CONFIG_KALLSYMS
/*
* We keep the symbol and string tables for kallsyms.
* The core_* fields below are temporary, loader-only (they
* could really be discarded after module init).
*/
Elf_Sym *symtab, *core_symtab;
unsigned int num_symtab, core_num_syms;
char *strtab, *core_strtab;
/* Section attributes */
struct module_sect_attrs *sect_attrs;
/* Notes attributes */
struct module_notes_attrs *notes_attrs;
#endif
/* The command line arguments (may be mangled). People like
keeping pointers to this stuff */
char *args;
#ifdef CONFIG_SMP
/* Per-cpu data. */
void __percpu *percpu;
unsigned int percpu_size;
#endif
#ifdef CONFIG_TRACEPOINTS
unsigned int num_tracepoints;
struct tracepoint * const *tracepoints_ptrs;
#endif
#ifdef HAVE_JUMP_LABEL
struct jump_entry *jump_entries;
unsigned int num_jump_entries;
#endif
#ifdef CONFIG_TRACING
unsigned int num_trace_bprintk_fmt;
const char **trace_bprintk_fmt_start;
#endif
#ifdef CONFIG_EVENT_TRACING
struct ftrace_event_call **trace_events;
unsigned int num_trace_events;
struct trace_enum_map **trace_enums;
unsigned int num_trace_enums;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
#ifdef CONFIG_LIVEPATCH
bool klp_alive;
#endif
#ifdef CONFIG_MODULE_UNLOAD
//用來在內(nèi)核模塊間建立依賴關(guān)系。
/* What modules depend on me? */
struct list_head source_list;
/* What modules do I depend on? */
struct list_head target_list;
/* Destruction function. */
void (*exit)(void);
atomic_t refcnt;
#endif
#ifdef CONFIG_CONSTRUCTORS
/* Constructor functions. */
ctor_fn_t *ctors;
unsigned int num_ctors;
#endif
};
load_module
作為內(nèi)核模塊加載器中最核心的函數(shù),load_module負(fù)責(zé)最艱苦的模塊加載全過程。我們將仔細(xì)討論該函數(shù),因為除了可以了解內(nèi)核模塊加載的幕后機制之外,還能了解到一些非常有趣的特性,諸如內(nèi)核模塊如何調(diào)用內(nèi)核代碼導(dǎo)出的函數(shù),被加載的模塊如何向系統(tǒng)中其他的模塊導(dǎo)出自己的符號,以及模塊如何接收外部的參數(shù)等。在介紹這部分內(nèi)容時,如果完全按照內(nèi)核代碼的順序依序進行的話,邏輯上可能會顯得比較凌亂。所以此處文字組織的基本思路是:將load_module函數(shù)按照各主要功能分成若干部分,各部分在下文中的出現(xiàn)順序盡可能維持在代碼中的出現(xiàn)順序:如果某些功能之間存在著某種依賴關(guān)系,比如有A和B兩個功能,A功能的敘述需要用到B功能中提供的機制,則先介紹B功能:獨立于功能模塊之外的一些基礎(chǔ)設(shè)施,比如某些功能性函數(shù),則盡量往前放。
模塊ELF靜態(tài)的內(nèi)存視圖
用戶空間程序insmod首先通過文件系統(tǒng)接口讀取內(nèi)核模塊demodev.ko的文件數(shù)據(jù),將其放在一塊用戶空間的存儲區(qū)域中(圖中void *umod所示)。然后通過系統(tǒng)調(diào)用sys_init_module進入到內(nèi)核態(tài),同時將umod指針作為參數(shù)傳遞過去(同時傳入的還有umod所指向的空間大小len和存放有模塊參數(shù)的地址空間指針uargs)。
sys_init_module調(diào)用load_module,后者將在內(nèi)核空間利用vmalloc分配一塊大小同樣為len的地址空間,如圖1·2中Elf_Ehdr *hdr所示。然后通過copy_from_user函數(shù)的調(diào)用將用戶空間的文件數(shù)據(jù)復(fù)制到內(nèi)核空間中,從而在內(nèi)核空間構(gòu)造出demodev.ko的一個ELF靜態(tài)的內(nèi)存視圖。接下來的操作都將以此視圖為基礎(chǔ),為使敘述簡單起見,我們稱該視圖為HDR視圖(圖1·2下方點畫線橢圓部分)。HDR視圖所占用的內(nèi)存空間在load_module結(jié)束時通過vfree予以釋放。
字符串表(string Table)
字符串表是ELF文件中的一個section,用來保存ELF文件中各個section的名稱或符號名,這些名稱以字符串的形式存在。圖1·3給出了一個具體的字符串表實例:
由圖1·3可見,字符串表中各個字符串的構(gòu)成和c語言中的字符串完全一樣,都以’\0’作為一個字符串的結(jié)束標(biāo)記。由index指向的字符串是從字符串表第index個字符開始,直到遇到一個’\0’標(biāo)記,如果index處恰好是,那么index指向的就是個空串(null stnng)。
在驅(qū)動模塊所在的ELF文件中,一般有兩個這樣的字符串表section,一個用來保存各section名稱的字符串,另一個用來保存符號表中每個符號名稱的字符串。雖然同樣都是字符串表section,但是得到這兩個section的基地址的方法并不一樣。
section名稱字符串表的基地址為char *secstrings=(char *)hdr+entry[hdr->e_shstrndx].sh_offset。而獲得符號名稱字符串表的基地址則有點繞:首先要遍歷Section
header table中所有的entry,去找一個entry[i].sh_type=SHT_SYMTAB的entry,SHT_SYMTAB表明這個entry所對應(yīng)的section是一符號表。這種情況下,entry[i].sh_link是符號名稱字符串表section在Section header table中的索引值,換句話說,符號名稱字符串表所在section的基地址為char *strtab=(char *)hdr+entry[entry[i].shlink].sh_offset。
如此,若想獲得某一section的名稱(假設(shè)該section在section header table中的索引值是i),那么用secstrings+entry[i].sh_name即可。
至此,load_module函數(shù)通過以上計算獲得了section名稱字符串表的基地址secstrings和符號名稱字符串表的基地址stb,留作將來使用。
HDR視圖的第一次改寫
在獲得了section名稱字符串表的基地址secstrings和符號名稱字符串表的基地址strtab之后,函數(shù)開始第一次遍歷section header table中的所有entry,將每個entry中的sh_addr改寫為entry[i].sh_addr=(size_t)hdr+entry[i].offset,這樣entry[i].sh_addr將指向該entry所對應(yīng)的section在HDR視圖中的實際存儲地址。
在遍歷過程中,如果發(fā)現(xiàn)CONFIG_MODULE_UNLOAD宏沒有定義,表明系統(tǒng)不支持動態(tài)卸載一個模塊,這樣,對于名稱為".exit”的section,將來就沒有必要把它加載到內(nèi)存中,內(nèi)核代碼于是清除對應(yīng)entry中sh_flags里面的SHF_ALLOC標(biāo)志位。
相對于剛復(fù)制到內(nèi)核空間的HDR視圖,HDR視圖的第一次改寫只是在自身基礎(chǔ)上修改了Section header table中的某些字段,其他方面沒有任何變化。接下來在“HDR視圖的第2次改寫”一節(jié)中將會看到改寫后的HDR視圖會再次被改寫,在那里,HDR視圖中的絕大部分會被搬移到一個新的內(nèi)存空間中,那也是它們最終的內(nèi)存位置。
find_sec函數(shù)
內(nèi)核用find_sec來尋找section再Section header table中的索引值,load_module->find_module_sections->find_sec
static unsigned int find_sec(const struct load_info *info, const char *name)
{
unsigned int i;
for (i = 1; i < info->hdr->e_shnum; i++) {
Elf_Shdr *shdr = &info->sechdrs[i];
/* Alloc bit cleared means "ignore it." */
if ((shdr->sh_flags & SHF_ALLOC)
&& strcmp(info->secstrings + shdr->sh_name, name) == 0)
return i;
}
return 0;
}
函數(shù)返回該的索引值,如果沒有找到對應(yīng)的section,則返回0。該函數(shù)的前兩個參數(shù)分別是ELF文件的ELF header和section header。因為函數(shù)要查找的是某一section的name,所以第三個參數(shù)就是前面提到的secstrings,第四個參數(shù)則是安查找的section的name。函數(shù)的具體實現(xiàn)過程非常簡單:遍歷section header table中所有的entry(忽略沒有SHF_ALLOC標(biāo)志的section,因為這樣的section最終不占有實際內(nèi)存地址),對每一個entry,先找到其所對應(yīng)的section name,然后和第四個參數(shù)進行比較,如果相等,就找到對應(yīng)的section,返回該section在Section header table中的索引值。文章來源:http://www.zghlxwxcb.cn/news/detail-435641.html
在對HDR視圖進行第一次改寫之后,內(nèi)核通過調(diào)用find_sec,分別查找以下名稱的section:".gnu.linkonce.this_module”,“__versions”和“.modinfo,。查找的索引值分別保存在變量modindex、versindex和infoindex中,以備將來使用。文章來源地址http://www.zghlxwxcb.cn/news/detail-435641.html
到了這里,關(guān)于第三十一章 linux-模塊的加載過程一的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!