模块表示:
在详细讲解模块相关函数的实现之前,有必要解释如何在内核中表示模块(及其属性)。
其中,module 是最重要的数据结构。内核中驻留的每一个模块,都分配了该结构的一个实例
struct module { /*state 表示该模块的当前状态。 enum module_state { MODULE_STATE_LIVE, MODULE_STATE_COMING, MODULE_STATE_GOING, }; 在装载期间,状态为 MODULE_STATE_COMING; 正常运行(完成所有初始化任务之后)时,状态为 MODULE_STATE_LIVE; 在模块正在卸载移除时,状态为 MODULE_STATE_GOING. */ enum module_state state; /* list 是一个标准的链表元素,由内核使用,将所有加载模块保存到一个双链表中。 链表的表头是定义在 的全局变量 modules。 */ /* Member of list of modules */ struct list_head list; /*name 指定了模块的名称。该名称必须是唯一地,内核中会使用该名称来引用模块*/ /* Unique handle for this module */ char name[MODULE_NAME_LEN]; /* Sysfs stuff. */ struct module_kobject mkobj; struct module_attribute *modinfo_attrs; const char *version; const char *srcversion; struct kobject *holders_dir; /*syms,num_syms,crcs 用于管理模块导出的符号。syms是一个数组,有 num_syms 个数组项, 数组项类型为 kernel_symbol,负责将标识符(name)分配到内存地址(value): struct kernel_symbol { unsigned long value; const char *name; }; crcs 也是一个 num_syms 个数组项的数组,存储了导出符号的校验和,用于实现版本控制 */ /* Exported symbols */ const struct kernel_symbol *syms; const unsigned long *crcs; unsigned int num_syms; /* Kernel parameters. */ struct kernel_param *kp; unsigned int num_kp; /*在导出符号时,内核不仅考虑了可以有所有模块(不考虑许可证类型)使用的符号,还要考虑只能由 GPL 兼容模块使用的符号。 第三类的符号当前仍然可以有任意许可证的模块使用,但在不久的将来也会转变为只适用于 GPL 模块。 gpl_syms,num_gpl_syms,gpl_crcs 成员用于只提供给 GPL 模块的符号; gpl_future_syms,num_gpl_future_syms,gpl_future_crcs 用于将来只提供给 GPL 模块的符号。 unused_gpl_syms 和 unused_syms 以及对应的计数器和校验和成员描述。 这两个数组用于存储(只适用于 GPL)已经导出, 但 in-tree 模块未使用的符号。在out-of-tree 模块使用此类型符号时,内核将输出一个警告消息。 */ /* GPL-only exported symbols. */ 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 /* 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; /*如果模块定义了新的异常,异常的描述保存在 extable 数组中。 num_exentries 指定了数组的长度。 */ /* Exception table */ unsigned int num_exentries; struct exception_table_entry *extable; /*init 是一个指针,指向一个在模块初始化时调用的函数*/ /* Startup function. */ int (*init)(void); /*模块的二进制数据分为两个部分;初始化部分和核心部分。 前者包含的数据在转载结束后都可以丢弃(例如:初始化函数); 后者包含了正常运行期间需要的所有数据。 初始化部分的起始地址保存在 module_init,长度为 init_size 字节; 核心部分有 module_core 和 core_size 描述。 */ /* 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 是一个特定于处理器的挂钩,取决于特定的体统,其中可能包含了运行模块所需的各种其他数据。 大多数体系结构都不需要任何附加信息,因此将 struct mod_arch_specific 定义为空,编译器在优化期间会移除掉。 */ /* Arch-specific module values */ struct mod_arch_specific arch; /*如果模块会污染内核,则设置 taints.污染意味着内核怀疑该模块做了一个有害的事情,可能妨碍内核的正常运作。 如果发生内核恐慌(在发生致命的内部错误,无法恢复正常运作时,将触发内核恐慌),那么错误诊断也会包含为什么内核被污染的有关信息。 这有助于开发者区分来自正常运行系统的错误报告和包含某些可疑因素的系统错误。 add_taint_module 函数用来设置 struct module 的给定实例的 taints 成员。 模块可能因两个原因污染内核: 1,如果模块的许可证是专有的,或不兼容 GPL,那么在模块载入内核时,会使用 TAINT_PROPRIETARY_MODULE. 由于专有模块的源码可能弄不到,模块在内核中作的任何事情都无法跟踪,因此,bug 很可能是由模块引入的。 内核提供了函数 license_is_gpl_compatible 来判断给定的许可证是否与 GPL 兼容。 2,TAINT_FORCED_MODULE 表示该模块是强制装载的。如果模块中没有提供版本信息,也称为版本魔术(version magic), 或模块和内核某些符号的版本不一致,那么可以请求强制装载。 */ 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 /*symtab,num_symtab,strtab 用于记录该模块所有符号的信息(不仅指显式导出的符号)。 */ /* * 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 /*args 是一个指针,指向装载期间传递给模块的命令行参数*/ /* The command line arguments (may be mangled). People like keeping pointers to this stuff */ char *args; #ifdef CONFIG_SMP /*percpu 指向属于模块的各 CPU 数据。它在模块装载时初始化*/ /* 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; #endif #ifdef CONFIG_FTRACE_MCOUNT_RECORD unsigned int num_ftrace_callsites; unsigned long *ftrace_callsites; #endif #ifdef CONFIG_MODULE_UNLOAD /* What modules depend on me? */ struct list_head source_list; /* What modules do I depend on? */ struct list_head target_list; /*waiter 是一个指针,指向导致模块卸载并且正在等待操作结束的进程的 task_struct 实例 */ /* Who is waiting for us to be unloaded */ struct task_struct *waiter; /*析构函数,exit 与init 是对称的。它是一个指针,指向的函数用于在模块移除时负责特定于模块的清理工作(例如,释放分配的内存区域)。 */ /* Destruction function. */ void (*exit)(void); /*module_ref 用于引用计数。系统中的每个 CPU,都对应到该数组中的数组项。该项指定了系统中有多少地方使用了该模块。 内核提供了 try_module_get 和 module_put 函数,用对引用计数器加1或减1,如果调用者确信相关模块当前没有被卸载, 也可以使用 __module_get 对引用计数加 1.相反,try_module_get 会确认模块确实已经加载。 */ struct module_ref { unsigned int incs; unsigned int decs; } __percpu *refptr; #endif #ifdef CONFIG_CONSTRUCTORS /* Constructor functions. */ ctor_fn_t *ctors; unsigned int num_ctors; #endif };
模块的二进制结构:
http://blog.chinaunix.net/uid-28458801-id-3475954.html
1,module_init()(模块装载)
@a1@
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
init_module 系统调用时用户空间和内核之间用于装载新模块的接口。
该调用需要 3 个参数:
umod : 是一个指针,指向用户地址空间中的区域,模块的二进制代码位于其中。
len : 该区域的长度。
uargs : 是一个指针,指定了模块的参数。

//声明在asmlinkage long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs); //定义 在 /* This is where the real work happens */ SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs) { struct module *mod; int ret = 0; /* Must have permission */ if (!capable(CAP_SYS_MODULE) || modules_disabled) return -EPERM; /* Do all the hard work */ mod = load_module(umod, len, uargs); if (IS_ERR(mod)) return PTR_ERR(mod); blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod); /* Set RO and NX regions for core */ set_section_ro_nx(mod->module_core, mod->core_text_size, mod->core_ro_size, mod->core_size); /* Set RO and NX regions for init */ set_section_ro_nx(mod->module_init, mod->init_text_size, mod->init_ro_size, mod->init_size); do_mod_ctors(mod); /* Start the module */ if (mod->init != NULL) ret = do_one_initcall(mod->init); if (ret < 0) { /* Init routine failed: abort. Try to protect us from buggy refcounters. */ mod->state = MODULE_STATE_GOING; synchronize_sched(); module_put(mod); blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod); free_module(mod); wake_up(&module_wq); return ret; } if (ret > 0) { printk(KERN_WARNING "%s: '%s'->init suspiciously returned %d, it should follow 0/-E convention\n" "%s: loading module anyway...\n", __func__, mod->name, ret, __func__); dump_stack(); } /* Now it's a first class citizen! Wake up anyone waiting for it. */ mod->state = MODULE_STATE_LIVE; wake_up(&module_wq); blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_LIVE, mod); /* We need to finish all async code before the module init sequence is done */ async_synchronize_full(); mutex_lock(&module_mutex); /* Drop initial reference. */ module_put(mod); trim_init_extable(mod); #ifdef CONFIG_KALLSYMS mod->num_symtab = mod->core_num_syms; mod->symtab = mod->core_symtab; mod->strtab = mod->core_strtab; #endif unset_module_init_ro_nx(mod); module_free(mod, mod->module_init); mod->module_init = NULL; mod->init_size = 0; mod->init_ro_size = 0; mod->init_text_size = 0; mutex_unlock(&module_mutex); return 0; }
@b1@ load_module(umod,len,uargs);
1, 二进制数据使用 load_module 传输到内核地址空间中。所有需要的重定位都会完成,所有的引用都会解决。
参数转换为一种易于分析的形式(kernel_param 实例的表),用模块的所有必要信息创建 module 数据结构的一个实例。
2,在 load_module 函数中创建的 module 实例已经添加到全局的 modules 链表后,内核只需要调用模块的初始化函数
并释放初始化数据占用的内存。
@c1@ 从用户空间复制模块数据(和参数)到内核地址空间的临时内存位置。
/* Sets info->hdr and info->len. */
static int copy_and_check(struct load_info *info,
const void __user *umod, unsigned long len,
const char __user *uargs)
{
int err;
Elf_Ehdr *hdr;
if (len < sizeof(*hdr))
return -ENOEXEC;
/* Suck in entire file: we'll want most of it. */
/* vmalloc barfs on "unusual" numbers. Check here */
if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL)
return -ENOMEM;
/*内核把模块的二进制数据载入到内核内存。
此时 hdr 指向模块的ELF头
e_shnum 表示段的数目;
sh_addr 是某个段的地址;
sh_offset 是该段在段表中的 ID
*/
if (copy_from_user(hdr, umod, len) != 0) {
err = -EFAULT;
goto free_hdr;
}
/* Sanity checks against insmoding binaries or wrong arch,
weird elf version */
if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0
|| hdr->e_type != ET_REL
|| !elf_check_arch(hdr)
|| hdr->e_shentsize != sizeof(Elf_Shdr)) {
err = -ENOEXEC;
goto free_hdr;
}
if (len < hdr->e_shoff + hdr->e_shnum * sizeof(Elf_Shdr)) {
err = -ENOEXEC;
goto free_hdr;
}
info->hdr = hdr;
info->len = len;
return 0;
free_hdr:
vfree(hdr);
return err;
}
@c2@ 设置各 ELF 段的地址。查找各个(可选)段的位置
static struct module *layout_and_allocate(struct load_info *info)
{
/* Module within temporary copy. */
struct module *mod;
Elf_Shdr *pcpusec;
int err;
mod = setup_load_info(info);
if (IS_ERR(mod))
return mod;
err = check_modinfo(mod, info);
if (err)
return ERR_PTR(err);
/* Allow arches to frob section contents and sizes. */
err = module_frob_arch_sections(info->hdr, info->sechdrs,
info->secstrings, mod);
if (err < 0)
goto out;
pcpusec = &info->sechdrs[info->index.pcpu];
if (pcpusec->sh_size) {
/* We have a special allocation for this section. */
err = percpu_modalloc(mod,
pcpusec->sh_size, pcpusec->sh_addralign);
if (err)
goto out;
pcpusec->sh_flags &= ~(unsigned long)SHF_ALLOC;
}
/* Determine total sizes, and put offsets in sh_entsize. For now
this is done generically; there doesn't appear to be any
special cases for the architectures. */
layout_sections(mod, info);
info->strmap = kzalloc(BITS_TO_LONGS(info->sechdrs[info->index.str].sh_size)
* sizeof(long), GFP_KERNEL);
if (!info->strmap) {
err = -ENOMEM;
goto free_percpu;
}
layout_symtab(mod, info);
/* Allocate and move to the final place */
err = move_module(mod, info);
if (err)
goto free_strmap;
/* Module has been copied to its final place now: return it. */
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
kmemleak_load_module(mod, info);
return mod;
free_strmap:
kfree(info->strmap);
free_percpu:
percpu_modfree(mod);
out:
return ERR_PTR(err);
}
@d1@ setup_load_info(info);
/*
* Set up our basic convenience variables (pointers to section headers,
* search for module section index etc), and do some basic section
* verification.
*
* Return the temporary module pointer (we'll replace it with the final
* one when we move the module sections around).
*/
static struct module *setup_load_info(struct load_info *info)
{
unsigned int i;
int err;
struct module *mod;
/* Set up the convenience variables */
info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
info->secstrings = (void *)info->hdr
+ info->sechdrs[info->hdr->e_shstrndx].sh_offset;
err = rewrite_section_headers(info);
if (err)
return ERR_PTR(err);
/*遍历所有段永利找到符号表(类型为 SHT_SYMTAB 的唯一段)和相关的符号字符串表的位置,
前者的 sh_link 即为后者的段索引
*/
/* Find internal symbols and strings. */
for (i = 1; i < info->hdr->e_shnum; i++) {
if (info->sechdrs[i].sh_type == SHT_SYMTAB) {
info->index.sym = i;
info->index.str = info->sechdrs[i].sh_link;
info->strtab = (char *)info->hdr
+ info->sechdrs[info->index.str].sh_offset;
break;
}
}
/*在 .gnu.linkonce.this_module 段中,有一个 struct module 的实例(find_sec 是一个辅助函数,根据 ELF 段的名称找到其索引)
*/
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
if (!info->index.mod) {
printk(KERN_WARNING "No module found in object\n");
return ERR_PTR(-ENOEXEC);
}
/*mod 指向 struct module 的实例,该实例中提供了模块的名称和指向初始化以及清理函数的指针,
但其他成员仍然初始化为 NULL 或 0.
*/
/* This is temporary: point mod into copy of data. */
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
if (info->index.sym == 0) {
printk(KERN_WARNING "%s: module has no symbols (stripped?)\n",
mod->name);
return ERR_PTR(-ENOEXEC);
}
info->index.pcpu = find_pcpusec(info);
/* Check module struct version now, before we try to use module. */
if (!check_modstruct_version(info->sechdrs, info->index.vers, mod))
return ERR_PTR(-ENOEXEC);
return mod;
}
sdff
@d2@ module_frob_arch_sections(info->hdr, info->sechdrs, info->secstrings, mod);
某些体系结构使用该函数操作各个段的内容。由于通常不需要该函数,因而通常定义为空。
@d3@ layout_sections(mod, info);
/* Lay out the SHF_ALLOC sections in a way not dissimilar to how ld
might -- code, read-only data, read-write data, small data. Tally
sizes, and place the offsets into sh_entsize fields: high bit means it
belongs in init. */
static void layout_sections(struct module *mod, struct load_info *info)
{
static unsigned long const masks[][2] = {
/* NOTE: all executable code must be the first section
* in this array; otherwise modify the text_size
* finder in the two loops below */
{ SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },
{ SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },
{ SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },
{ ARCH_SHF_SMALL | SHF_ALLOC, 0 }
};
unsigned int m, i;
for (i = 0; i < info->hdr->e_shnum; i++)
info->sechdrs[i].sh_entsize = ~0UL;
DEBUGP("Core section allocation order:\n");
for (m = 0; m < ARRAY_SIZE(masks); ++m) {
for (i = 0; i < info->hdr->e_shnum; ++i) {
Elf_Shdr *s = &info->sechdrs[i];
const char *sname = info->secstrings + s->sh_name;
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
|| s->sh_entsize != ~0UL
|| strstarts(sname, ".init"))
continue;
s->sh_entsize = get_offset(mod, &mod->core_size, s, i);
DEBUGP("\t%s\n", name);
}
switch (m) {
case 0: /* executable */
mod->core_size = debug_align(mod->core_size);
mod->core_text_size = mod->core_size;
break;
case 1: /* RO: text and ro-data */
mod->core_size = debug_align(mod->core_size);
mod->core_ro_size = mod->core_size;
break;
case 3: /* whole core */
mod->core_size = debug_align(mod->core_size);
break;
}
}
DEBUGP("Init section allocation order:\n");
for (m = 0; m < ARRAY_SIZE(masks); ++m) {
for (i = 0; i < info->hdr->e_shnum; ++i) {
Elf_Shdr *s = &info->sechdrs[i];
const char *sname = info->secstrings + s->sh_name;
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
|| s->sh_entsize != ~0UL
|| !strstarts(sname, ".init"))
continue;
s->sh_entsize = (get_offset(mod, &mod->init_size, s, i)
| INIT_OFFSET_MASK);
DEBUGP("\t%s\n", sname);
}
switch (m) {
case 0: /* executable */
mod->init_size = debug_align(mod->init_size);
mod->init_text_size = mod->init_size;
break;
case 1: /* RO: text and ro-data */
mod->init_size = debug_align(mod->init_size);
mod->init_ro_size = mod->init_size;
break;
case 3: /* whole init */
mod->init_size = debug_align(mod->init_size);
break;
}
}
}
layout_sectinos 用于判断模块的哪些段装载到了内存的哪些位置,或哪些段必须从其临时地址复制到其他位置。
各个段分为两类:核心和初始化。
前一部分包括了模块的整个运行期间都需要的所有代码段,内核将所有初始化数据和函数放置到一个单独的部分,
在装载完成后移除。
除非段的头部设置了 SHF_ALLOC 标志,否则段不会转移到最终内存位置上。
例如:对调试信息段(使用 gcc选项 -g 生成)不会设置该标志,因为这些数据不必移植处于内存中,需要时从
二进制文件读取即可。
layout_sections 会检测段的名称是否包含 .init 字符串。这使得内核能够区分初始化代码和普通代码。
相应的,从段的起始位置就能判断出是核心段还是初始化段。
layout_sections 的结果使用以下数据元素表示:
1,每个段对应于一个 ELF 段数据结构实例,该实例的 sh_entsize 表示段在核心段或初始化段中的相对位置。
如果某个段不会被装载,则该只设置为 ~out.
为了区分 初始化段和核心段,前者在 sh_entsize中置位了 INIT_OFFSET_MASK 标志位(定义为
(1UL<<(BITS_PER_LONG-1)))。所有初始化段的相对位置都存储在各自的 sh_entsize 成员中。
2,core_size 用于表示在内核中持久驻留的代码的总长度(至少在模块卸载前)。
init_size 是模块初始化所需的所有段的总长度
@d4@ layout_symtab(mod, info);
将存在的各个段分配到其在内存中的最终位置
@d DONE@
@c3@ find_module_sections(mod, &info);
确保内核和模块中版本控制字符串和 struct module 的定义匹配
@c4@ check_module_license_and_versions(mod);
检测模块符号的版本控制信息。
@c5@ simplify_symbols(mod, &info);
重定位符号并解决引用。
/* Change all symbols so that st_value encodes the pointer directly. */
static int simplify_symbols(struct module *mod, const struct load_info *info)
{
Elf_Shdr *symsec = &info->sechdrs[info->index.sym];
Elf_Sym *sym = (void *)symsec->sh_addr;
unsigned long secbase;
unsigned int i;
int ret = 0;
const struct kernel_symbol *ksym;
for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {
const char *name = info->strtab + sym[i].st_name;
switch (sym[i].st_shndx) {
case SHN_COMMON:
/* We compiled with -fno-common. These are not
supposed to happen. */
DEBUGP("Common symbol: %s\n", name);
printk("%s: please compile with -fno-common\n",
mod->name);
ret = -ENOEXEC;
break;
/*不同符号类型必须进行不同的处理。完全定义的符号是最容易的,因为什么也不需要做。*/
case SHN_ABS:
/* Don't need to do anything */
DEBUGP("Absolute symbol: 0x%08lx\n",
(long)sym[i].st_value);
break;
case SHN_UNDEF:
ksym = resolve_symbol_wait(mod, info, name);
/*如果符号已经解决,则没有问题*/
/* Ok if resolved. */
if (ksym && !IS_ERR(ksym)) {
sym[i].st_value = ksym->value;
break;
}
/*如果符号定义为弱的,也没有问题*/
/* Ok if weak. */
if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
break;
printk(KERN_WARNING "%s: Unknown symbol %s (err %li)\n",
mod->name, name, PTR_ERR(ksym));
ret = PTR_ERR(ksym) ?: -ENOENT;
break;
/*解决所有其他符号时,都是通过在模块符号表中查找其值*/
default:
/* Divert to percpu allocation if a percpu var. */
if (sym[i].st_shndx == info->index.pcpu)
secbase = (unsigned long)mod_percpu(mod);
else
secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
sym[i].st_value += secbase;
break;
}
}
return ret;
}
static const struct kernel_symbol *
resolve_symbol_wait(struct module *mod,
const struct load_info *info,
const char *name)
{
...
!IS_ERR(ksym = resolve_symbol(mod, info, name, owner))
...
return ksym;
}
sdfsda
resolve_symbol 用于解决未定义的符号引用。它就是一个包装器函数,
如果符号因为没有匹配的定义可用而无法解决,则 resolve_symbol 返回 0.
如果该符号定义为弱的,则没有问题;否则该模块无法插入,因为它引用了不存在的符号。
解决符号的实际工作在 __find_symbol 中进行。内核首先遍历持久编译到内核中的所有符号;
如果 __find_symbol 成功,内核首先使用 check_version 确定校验和是否匹配。
如果使用的符号源自另一个模块,则通过 use_module 函数建立两个模块之间的依赖关系。
@c6@ 处理模块参数
@c7@ 连接到 sysfs 中
@c DONE@
@b2@ 设置模块初始化域和核心域
@b3@ 启动模块
@b DONE@
@a DONE@
2,module_exit()(模块卸载)
模块加载流程

1,系统调用通过名称来识别模块,因此必须通过参数传递模块的名称
(
除了名称之外,还可以传递两个标志:
O_TRUNC : 表示模块可以从内核“强制”移除(例如:既是引用计数器为正值);
O_NONBLOCK : 指定该操作必须是非阻塞的。
)。
2,必须确保其他模块都不需要使用该模块(即要确保 struct module 实例中的 source_list 成员为空)。
3,在确认引用计数器为 0之后,调用特定于模块的清理函数,而模块数据占用的内存空间通过 free_module 释放。
@a DONE@