Linux内核模块加载过程解析(1)

insmod 入口函数本文用到的 busybox 版本为 1.34.1,linux 内核版本为 4.14.294
insmod_main()函数是 insmod 命令的入口函数,该函数首先通过函数参数获取被加载模块的名字并存入局部指针变量 filename,然后调用bb_init_module()函数进行后续操作。
int insmod_main(int argc unused_param, char **argv){ char *filename; int rc; /* compat note: * 2.6 style insmod has no options and required filename * (not module name - .ko can't be omitted). * 2.4 style insmod can take module name without .o * and performs module search in default directories * or in $modpath. */ if_feature_2_4_modules( getopt32(argv, insmod_opts insmod_args); argv += optind - 1; ); filename = *++argv; if (!filename) bb_show_usage(); rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0)); if (rc) bb_error_msg(can't insert '%s': %s, filename, moderror(rc)); return rc;}模块参数解析函数parse_cmdline_module_options()函数会解析模块加载时传给模块的参数,通过while循环挨个解析模块后面传给模块的参数,并将解析出来的参数值val存入指针变量options指向的内存空间,最后返回该内存空间的首地址。
char* fast_func parse_cmdline_module_options(char **argv, int quote_spaces){ char *options; int optlen; options = xzalloc(1); optlen = 0; while (*++argv) { const char *fmt; const char *var; const char *val; var = *argv; options = xrealloc(options, optlen + 2 + strlen(var) + 2); fmt = %.*s%s ; val = strchrnul(var, '='); if (quote_spaces) { /* * modprobe (module-init-tools version 3.11.1) compat: * quote only value: * var=val with spaces, not var=val with spaces * (note: var *name* is not checked for spaces!) */ if (*val) { /* has var=val format. skip '=' */ val++; if (strchr(val, ' ')) fmt = %.*s\\%s\\ ; } } optlen += sprintf(options + optlen, fmt, (int)(val - var), var, val); } /* remove trailing space. disabled */ /* if (optlen != 0) options[optlen-1] = '\\0'; */ return options;}映射模块文件bb_init_module()函数首先判断模块有没有参数传入,调用try_to_mmap_module()函数完成后续映射工作,该函数接收两个参数:被加载模块的名字(filename),模块的大小(image_size)作为出参参数传入。最后调用init_module()函数,init_module()函数是系统调用函数,对应的内核函数是sys_init_module()函数,进入到内核空间。传入的参数分别是:模块内存空间首地址(image),模块大小(image_size),模块参数内存空间首地址(options)。
int fast_func bb_init_module(const char *filename, const char *options){ size_t image_size; char *image; int rc; bool mmaped; if (!options) options = ;//todo: audit bb_init_module_24 to match error code convention#if enable_feature_2_4_modules if (get_linux_version_code() = 0) { rc = finit_module(fd, options, 0) != 0; close(fd); if (rc == 0) return rc; } }# endif image_size = int_max - 4095; mmaped = 0; image = try_to_mmap_module(filename, &image_size); if (image) { mmaped = 1; } else { errno = enomem; /* may be changed by e.g. open errors below */ image = xmalloc_open_zipped_read_close(filename, &image_size); if (!image) return -errno; } errno = 0; init_module(image, image_size, options); rc = errno; if (mmaped) munmap(image, image_size); else free(image); return rc;}try_to_mmap_module()函数首先打开模块文件获取模块文件描述符fd,然后通过fstat()函数获取模块文件的详细信息,判断模块文件的大小st_size是否超过了设定的文件最大值,调用mmap_read()函数以只读的方式将模块文件的内容映射进内存空间,并返回该内存空间的首地址,通过*(uint32_t*)image != swap_be32(0x7f454c46)检查模块文件是否符号 elf 标准格式,最后将内存空间的首地址image返回。通过try_to_mmap_module()函数我们就获取了模块文件内容在内存空间的地址。
void* fast_func try_to_mmap_module(const char *filename, size_t *image_size_p){ /* we have user reports of failure to load 3mb module * on a 16mb ram machine. apparently even a transient * memory spike to 6mb during module load * is too big for that system. */ void *image; struct stat st; int fd; fd = xopen(filename, o_rdonly); fstat(fd, &st); image = null; /* st.st_size is off_t, we can't just pass it to mmap */ if (st.st_size len赋值为模块大小len,调用__vmalloc()函数在内核空间为模块分配info->len大小的内存空间,并返回内核内存空间的的起始地址info->hdr,最后调用copy_chunked_from_user()函数其实就是copy_from_user()函数将用户空间内存模块文件内容拷贝到info->hdr所指向的内核空间内存地址
static int copy_module_from_user(const void __user *umod, unsigned long len, struct load_info *info){ int err; info- >len = len; if (info- >len hdr))) return -enoexec; err = security_kernel_read_file(null, reading_module); if (err) return err; /* suck in entire file: we'll want most of it. */ info- >hdr = __vmalloc(info- >len, gfp_kernel | __gfp_nowarn, page_kernel); if (!info- >hdr) return -enomem; if (copy_chunked_from_user(info- >hdr, umod, info- >len) != 0) { vfree(info- >hdr); return -efault; } return 0;}至此,模块文件已经从用户空间拷贝到内核空间。
模块加载鉴于模块加载函数load_module()比较复杂,限于篇幅限制,具体的加载过程会在《linux内核模块加载深度剖析(中篇)》一文中分析。
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, flags); 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; } audit_log_kern_module(mod- >name); /* 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; init_param_lock(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 args = strndup_user(uargs, ~0ul > > 1); if (is_err(mod- >args)) { err = ptr_err(mod- >args); goto free_arch_cleanup; } dynamic_debug_setup(mod, 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; err = prepare_coming_module(mod); if (err) goto bug_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, mod, unknown_module_param_cb); if (is_err(after_dashes)) { err = ptr_err(after_dashes); goto coming_cleanup; } else if (after_dashes) { pr_warn(%s: parameters '%s' after `--' ignored\\n, mod- >name, after_dashes); } /* link in to sysfs. */ err = mod_sysfs_setup(mod, info, mod- >kp, mod- >num_kp); if (err < 0) goto coming_cleanup; if (is_livepatch_module(mod)) { err = copy_module_elf(mod, info); if (err state = module_state_going; destroy_params(mod- >kp, mod- >num_kp); blocking_notifier_call_chain(&module_notify_list, module_state_going, mod); klp_module_going(mod); bug_cleanup: mod- >state = module_state_going; /* module_bug_cleanup needs module_mutex protection */ mutex_lock(&module_mutex); module_bug_cleanup(mod); mutex_unlock(&module_mutex); /* we can't deallocate the module until we clear memory protection */ module_disable_ro(mod); module_disable_nx(mod); ddebug_cleanup: dynamic_debug_remove(mod, 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); mod_tree_remove(mod); wake_up_all(&module_wq); /* wait for rcu-sched synchronizing before releasing mod- >list. */ synchronize_sched(); mutex_unlock(&module_mutex); free_module: /* * ftrace needs to clean up what it initialized. * this does nothing if ftrace_module_init() wasn't called, * but it must be called outside of module_mutex. */ ftrace_release_mod(mod); /* free lock-classes; relies on the preceding sync_rcu() */ lockdep_free_key_range(mod- >core_layout.base, mod- >core_layout.size); module_deallocate(mod, info); free_copy: free_copy(info); return err;}未完待续。。。总结上篇主要介绍了模块文件从用户空间拷贝到内核空间的过程,从busybox源码开始一直到linux内核源码。

数字孪生技术在PCBA开发中的优势
数控机床的保养维护要求
浅谈数字隔离器件的选型与应用
新能源汽车市场节点风暴来袭 车和家增程式“解忧”受质疑
远程患者监护系统推动未来医疗保健的创新发展
Linux内核模块加载过程解析(1)
采用新的调制技术和滤波器结构减小D类放大器的EMI
可穿戴设备大举导入MPU 人机界面更吸睛
微电机效率降低的基本原因
Stratix 10 GX 10M FPGA全球密度最高 拥有1020万个逻辑单元
SiC外延片制备技术解析
小小一个电路帮你辨别电池过放电
内存的作用及规格是什么
盐干湿循环腐蚀试验箱的介绍
电子材料疲劳破坏的一般规律分析
AI在面对突发的大火、面对巴黎圣母院的重建又能做什么呢?
电气火灾监控系统在博西华电气二期家电中心库中的应用
Infinera首个供应链人工智能试点项目上线,让物流变得更聪明
中美贸易对决刺激芯片低迷市场 美光下半年复苏恐难实现
5G华为与4G苹果的对比,价格相同该如何选择