第三十一章 linux-模块的加载过程
文章目录
- 第三十一章 linux-模块的加载过程
- sys_init_module
- struct module
- load_module
在用户空间,用insmod这样的命令来向内核空间安装一个内核模块,本节将详细讨论模块加载时的内核行为。当调用“insmod demodev.ko”来安装demodev.ko这样的内核模块时,insmod会首先利用文件系统的接口将其数据读取到用户空间的一段内存中,然后通过系统调用sys_init_module让内核去处理模块加载的整个过程。
sys_init_module
原型:
asmlinkage long sys_init_module(void __user *umod, unsigned long len,
const char __user *uargs);
其中,第一参数umod是指向用户空间demodev.ko文件映像数据的内存地址,第二参数len是该文件的数据大小,第三参数是传给模块的参数在用户空间下的内存地址。
sys_init_module实现
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(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);
}
在sys_init_module函数中,加载模块的任务主要是通过调load_module函数来完成的,该函数的定义为:
/* Allocate and load the module: note that size of section 0 is always
zero, and we rely on this for optional sections. */
//二进制数据使用load_module传输到内核地址空间中,所有需要的重定位都会完成,所有iny都会解决
//在load_module中创建module实例已近添加到全局的modues链表后,内核内需要调用模块的初始化函数并释放
//初始化数据占用的内存空间
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);
if (err)
goto free_copy;
err = elf_header_check(info);
if (err)
goto free_copy;
/* Figure out module layout, and allocate all the memory. */
mod = layout_and_allocate(info, flags);
if (IS_ERR(mod)) {
err = PTR_ERR(mod);
goto free_copy;
}
/* Reserve our place in the list. */
err = add_unformed_module(mod);
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);
if (err)
goto unlink_mod;
/* Now module is in final location, initialize linked lists, etc. */
err = module_unload_init(mod);
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);
if (err)
goto free_unload;
err = check_module_license_and_versions(mod);
if (err)
goto free_unload;
/* Set up MODINFO_ATTR fields */
setup_modinfo(mod, info);
/* Fix up syms, so that st_value is a pointer to location. */
err = simplify_symbols(mod, info);
if (err < 0)
goto free_modinfo;
err = apply_relocations(mod, info);
if (err < 0)
goto free_modinfo;
err = post_relocation(mod, info);
if (err < 0)
goto free_modinfo;
flush_module_icache(mod);
/* Now copy in args */
mod->args = strndup_user(uargs, ~0UL >> 1);
if (IS_ERR(mod->args)) {
err = PTR_ERR(mod->args);
goto free_arch_cleanup;
}
dynamic_debug_setup(info->debug, info->num_debug);
/* Ftrace init must be called in the MODULE_STATE_UNFORMED state */
ftrace_module_init(mod);
/* Finally it's fully formed, ready to start executing. */
err = complete_formation(mod, info);
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);
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);
if (err < 0)
goto bug_cleanup;
/* Get rid of temporary copy. */
free_copy(info);
/* Done! */
trace_module_load(mod);
return do_init_module(mod);
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;
}
所有参数同sys_init_module函数中的完全一样,实际上在sys_init_module函数的一开始便会调用该函数,调用时传入的实参完全来自于sys_init_module函数,没有经过任何的处理或者修改。
为了更清楚地解释模块加载时的内核行为,我们把sys_init_module分为两个部分:第一部分是调用load_module,完成模块加载最核心的任务:第二部分是在模块被成功加载到系统之后的后续处理。我们将在讨论完load_module部分之后再继续讨论sys_init_module的第二部分。不过,在继续load_module话题之前,先要看一个内核中非常重要的数据结构—------struct module。
struct module
load module函数的返回值是一个struct module类型的指针,struct module是内核用来管理系统中加载的模块时使用的一个非常重要的数据结构,一个struct module对象代表着现实中一个内核模块在Linux系统中的抽象,该结构的定义如下〈删除了一些trace和unused symbol相关的部分):
struct module {
enum module_state state;//模块的状态
/* Member of list of modules */
struct list_head list;//用作模块链表的链表元素
/* 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;
/* 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-only exported symbols. */
//只适用于GPL的导出符号
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. */
//初始化函数
int (*init)(void);//init是一个指针,指向一个在模块初始化时调用的函数
//指向内核模块初始化函数的指针,在内核模块源码中由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
//用来在内核模块间建立依赖关系。
/* 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
作为内核模块加载器中最核心的函数,load_module负责最艰苦的模块加载全过程。我们将仔细讨论该函数,因为除了可以了解内核模块加载的幕后机制之外,还能了解到一些非常有趣的特性,诸如内核模块如何调用内核代码导出的函数,被加载的模块如何向系统中其他的模块导出自己的符号,以及模块如何接收外部的参数等。在介绍这部分内容时,如果完全按照内核代码的顺序依序进行的话,逻辑上可能会显得比较凌乱。所以此处文字组织的基本思路是:将load_module函数按照各主要功能分成若干部分,各部分在下文中的出现顺序尽可能维持在代码中的出现顺序:如果某些功能之间存在着某种依赖关系,比如有A和B两个功能,A功能的叙述需要用到B功能中提供的机制,则先介绍B功能:独立于功能模块之外的一些基础设施,比如某些功能性函数,则尽量往前放。
- 模块ELF静态的内存视图
用户空间程序insmod首先通过文件系统接口读取内核模块demodev.ko的文件数据,将其放在一块用户空间的存储区域中(图中void *umod所示)。然后通过系统调用sys_init_module进入到内核态,同时将umod指针作为参数传递过去(同时传入的还有umod所指向的空间大小len和存放有模块参数的地址空间指针uargs)。
sys_init_module调用load_module,后者将在内核空间利用vmalloc分配一块大小同样为len的地址空间,如图1·2中Elf_Ehdr *hdr所示。然后通过copy_from_user函数的调用将用户空间的文件数据复制到内核空间中,从而在内核空间构造出demodev.ko的一个ELF静态的内存视图。接下来的操作都将以此视图为基础,为使叙述简单起见,我们称该视图为HDR视图(图1·2下方点画线椭圆部分)。HDR视图所占用的内存空间在load_module结束时通过vfree予以释放。
- 字符串表(string Table)
字符串表是ELF文件中的一个section,用来保存ELF文件中各个section的名称或符号名,这些名称以字符串的形式存在。图1·3给出了一个具体的字符串表实例:
由图1·3可见,字符串表中各个字符串的构成和c语言中的字符串完全一样,都以’\0’作为一个字符串的结束标记。由index指向的字符串是从字符串表第index个字符开始,直到遇到一个’\0’标记,如果index处恰好是,那么index指向的就是个空串(null stnng)。
在驱动模块所在的ELF文件中,一般有两个这样的字符串表section,一个用来保存各section名称的字符串,另一个用来保存符号表中每个符号名称的字符串。虽然同样都是字符串表section,但是得到这两个section的基地址的方法并不一样。