内核模块(3)模块加载

本文深入探讨Linux内核的x86架构下模块加载的实现,主要关注module.c文件中的相关逻辑,揭示系统调用如何启动内核模块的加载过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*本篇由真胖子同志倾心打造,殊为不易,忘转载时标明出处http://blog.youkuaiyun.com/figtingforlove/article/details/20156159*/
(1)符号的重定位
      想想正在加载的模块有一个“未解决的引用”符号是另一个模块导出的,看看我们上节得到的sym[i].st_value = ksym->value
       虽然“__ksymtab”段被搬移到了最终的内存地址,但是kernel_symbol类型符号sym中的value值依然是内核模块在静态编译时产生的地址(导出符号时EXPORT_SYMBOL,模块编译工具链中的(unsigned long&sym),根本不是这些模块被加载进内核后在内存中的实际地址,这就需要对模块导出的符号进行重定位。
      内核模块有导出符号时,编译工具链会为这个模块的ELF文件生成一个特殊的重定位段“.rel__ksymtab”专门用于对“__ksymtab”段的重定位,这个段有下面的数据结构形成一个数组。
Linux/include/uapi/linux/elf.h
160 typedef struct elf32_rel {
161   Elf32_Addr    r_offset;   //重定位后段距原来段的偏移量
162   Elf32_Word    r_info;
163 } Elf32_Rel;
模块加载时内核关于重定位的代码,其原理就是遍历段头表中的每一项,为有重定位标记SHF_REL的段进行重定位,段头中sh_info成员指明了被重定位的段在段头表中的索引值。
1998 static int apply_relocations(struct module *mod, const struct load_info *info)
1999 {
2000         unsigned int i;
2001         int err = 0;
2002 
2003         /* Now do relocations. */
2004         for (i = 1; i < info->hdr->e_shnum; i++) {
2005                 unsigned int infosec = info->sechdrs[i].sh_info;
2006 
2007                 /* Not a valid relocation section? */
2008                 if (infosec >= info->hdr->e_shnum)
2009                         continue;
2010 
2011                 /* Don't bother with non-allocated sections */
2012                 if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
2013                         continue;
2014                 //是重定位段
2015                 if (info->sechdrs[i].sh_type == SHT_REL)
2016                         err = apply_relocate(info->sechdrs, info->strtab,
2017                                 info->index.sym, i, mod);
2018                 else if (info->sechdrs[i].sh_type == SHT_RELA)
2019                         err = apply_relocate_add(info->sechdrs, info->strtab,
2020                                                  info->index.sym, i, mod);
2021                 if (err < 0)
2022                         break;
2023         }
2024         return err;
2025 }
对于重定位段,调用apply_relocate函数进行重定位,这是一个体系结构相关的函数,我们看看x86_32下的实现。

Linux/arch/x86/kernel/module.c

55 #ifdef CONFIG_X86_32
 56 int apply_relocate(Elf32_Shdr *sechdrs,
 57                    const char *strtab,
 58                    unsigned int symindex,
 59                    unsigned int relsec,
 60                    struct module *me)
 61 {
 62         unsigned int i;
		//当前正在处理的重定位段
 63         Elf32_Rel *rel = (void *)sechdrs[relsec].sh_addr;
 64         Elf32_Sym *sym;
 65         uint32_t *location;
 66 
 67         DEBUGP("Applying relocate section %u to %u\n",
 68                relsec, sechdrs[relsec].sh_info);
		//遍历重定位段中所有需要重定位的符号 
 69         for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
 70                 /* This is where to make the change */
    //符号表段地址 + 重定位偏移 得到 指向符号表中需要修改的地址的指针
 71            location = (void*)sechdrs[sechdrs[relsec].sh_info].sh_addr
 72                         + rel[i].r_offset;
 73                 /* This is the symbol it is referring to.  Note that all  undefined symbols have been resolved.  */
		//符号表地址 + 需要定位的符号在符号表中的偏移量得到符号地址
 75                 sym = (Elf32_Sym *)sechdrs[symindex].sh_addr
 76                         + ELF32_R_SYM(rel[i].r_info);
 77 
 78                 switch (ELF32_R_TYPE(rel[i].r_info)) {
 79                 case R_386_32:
 80                         /* We add the value into the location given */
 81                         *location += sym->st_value;
 82                         break;
 83                 case R_386_PC32:
 84                     /* Add the value, subtract its position */
 85                   *location += sym->st_value - (uint32_t)location;
 86                         break;
 87                 default:
 88                         pr_err("%s: Unknown relocation: %u\n",
 89                     me->name, ELF32_R_TYPE(rel[i].r_info));
 90                         return -ENOEXEC;
 91                 }
 92         }
 93         return 0;
 94 }
到此,模块加载最艰巨的部分算是结束了~~~~~接下来,继续看看其余的一些处理。接下来的处理就比较简单了,就是调用模块的初始化函数,释放HDR视图和INIT section占用的内存空间等善后工作。
有了上面的基础,下面我们主要分析内核模块加载器函数的具体实现,忽略部分细节,主要理解函数的调用流程和完成的功能。 

(1)进入系统调用函数

/*模块加载过程中传递的信息*/
173 struct load_info {
174         Elf_Ehdr *hdr;
175         unsigned long len;
176         Elf_Shdr *sechdrs;
177         char *secstrings, *strtab;
178         unsigned long symoffs, stroffs;
179         struct _ddebug *debug;
180         unsigned int num_debug;
181         bool sig_ok;
182         struct {
183                 unsigned int sym, str, mod, vers, info, pcpu;
184         } index;
185 };

umod表示用户空间模块视图地址,len代表长度,uargs是用户空间传入的模块参数
3301 SYSCALL_DEFINE3(init_module, void __user *, umod,
3302               unsigned long, len, const char __user *, uargs)
3303 {
3304         int err;
3305         struct load_info info = { };
3313 
3314         err = copy_module_from_user(umod, len, &info);
3315         if (err)
3316                 return err;
3317 
3318         return load_module(&info, uargs, 0);
3319 }
从用户空间拷贝模块ELF文件视图
/* Sets info->hdr and info->len. */
2499 static int copy_module_from_user(const void __user *umod, unsigned long len,
2500                                   struct load_info *info)
2501 {
2502         int err;
2503 
2504         info->len = len;
2505         if (info->len < sizeof(*(info->hdr)))
2506                 return -ENOEXEC;
2507         /*安全性检查*/
2508         err = security_kernel_module_from_file(NULL);
2509         if (err)
2510                 return err;
2511 
2512         /* 分配内存 */
2513         info->hdr = vmalloc(info->len);
2514         if (!info->hdr)
2515                 return -ENOMEM;
2516         /*从用户空间拷贝*/
2517         if (copy_from_user(info->hdr, umod, info->len) != 0) {
2518                 vfree(info->hdr);
2519                 return -EFAULT;
2520         }
2521 
2522         return 0;
2523 }
(2)    加载函数load_module
3146 static int load_module(struct load_info *info, const char __user *uargs, int flags)
3148 {
3149         struct module *mod, *old;
3151     /*一些检查性工作,如检查ELF头*/
/* 前面讲的段搬移,即ELF视图的修改*/
3161         mod = layout_and_allocate(info, flags);
             /*模块仍然处于启动状态*/
3172         mod->state = MODULE_STATE_UNFORMED;
3173 again:
3174         mutex_lock(&module_mutex);
              /*如果系统中已经有一个该模块正在加载,我们不能加载两次*/
3175         if ((old = find_module_all(mod->name, true)) != NULL) {
3176                 if (old->state == MODULE_STATE_COMING
3177                     || old->state == MODULE_STATE_UNFORMED) {
3178                         /* Wait in case it fails to load. */
3179                         mutex_unlock(&module_mutex);
3180                  err = wait_event_interruptible(module_wq,                                             finished_loading(mod->name));
3182                         if (err)
3183                                 goto free_module;
3184                         goto again;
3185                 }
3186                 err = -EEXIST;
3187                 mutex_unlock(&module_mutex);
3188                 goto free_module;
3189         }
             /*添加到内核模块全局链表*/
3190         list_add_rcu(&mod->list, &modules);
3191         mutex_unlock(&module_mutex);
3199         /*初始化模块unload section相关的成员*/
3200         err = module_unload_init(mod);

3204         /* Now we've got everything in the final locations, we can  find optional sections. */
              /*初始化模块mod中各个指向各段的指针*/
3206         find_module_sections(mod, info);
3207         /*版本和许可证检查*/
              /*前面介绍的对“未解决的引用”符号的处理*/
3216         err = simplify_symbols(mod, info);
		/*加载过程中的导出符号的重定位*/
3220         err = apply_relocations(mod, info);
/*...*/
3230         /* Now copy in args */
3231         mod->args = strndup_user(uargs, ~0UL >> 1);
/*…*/
3239         mutex_lock(&module_mutex);
3240         /*检查模块的导出符号表 */
3241         err = verify_export_symbols(mod);
3248   /* Mark state as coming so strong_try_module_get() ignores us, but kallsyms etc. can see us. */
3250         mod->state = MODULE_STATE_COMING;
3252         mutex_unlock(&module_mutex);
3253 
3254         /* Module is ready to execute: parsing args may do that. */
		/*模块参数的处理,将mod->args的值复制到“__param”段*/
3255         err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
3256                          -32768, 32767, &ddebug_dyndbg_module_param_cb);
/*…*/
3260         /* Link in to syfs. */
3261         err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
3265         /* Get rid of temporary copy.vfree(HDR) */
3266         free_copy(info);
3267        /*现在模块加载的艰巨工作基本做完了,下一步就是执行模块的init函数,并做一些后续处理*/
3268         /* Done! */
3269         trace_module_load(mod);
3270 
3271         return do_init_module(mod);
3272         /*前面一些检查失败的处理操作*/
3299 }
ELF各段的重新布局
2929 static struct module *layout_and_allocate(struct load_info *info, int flags)
2930 {
2931         /* Module within temporary copy. */
2932         struct module *mod;
2935        /*主要是第一次HDR视图的改写*/
2936         mod = setup_load_info(info, flags);
2949         /*一些检查工作和pcpu段的操作*/
		/*前面讲的段划分:INIT和CORE*/
2963         layout_sections(mod, info);	
              /*前面讲的符号表划分:CORE*/
2964         layout_symtab(mod, info);
2965 
2966         /*INIT和CORE内存分配和段搬移 to the final place */
2967         err = move_module(mod, info);
/* Module has been copied to its final place now: return it. */
		/*前面讲的修改mod指针指向最终位置*/
2972     mod = (void *)info->sechdrs[info->index.mod].sh_addr;
2973         kmemleak_load_module(mod, info);
2974         return mod;
2980 }
初始化load_info的一些成员信息,并完后HDR视图的第一次改写
2634 static struct module *setup_load_info(struct load_info *info, int flags)
2635 {
2640         /* Set up the convenience variables */
		/*段头表和段名称字符串表指针*/
2641         info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
2642         info->secstrings = (void *)info->hdr
2643         + info->sechdrs[info->hdr->e_shstrndx].sh_offset;
2644         /*HDR视图的第一次改写,主要是前面讲的shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;的修改*/
2645         err = rewrite_section_headers(info, flags);
2648         
2649         /* 模块内部符号表的处理. */
2659         /*mod段的索引*/
2660         info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
		/*前面讲的mod变量的赋值*/
        mod = (void *)info->sechdrs[info->index.mod].sh_addr;
2667         /*其他一些段的相关处理*/
2680         return mod;
2681 }

(3)加载模块的后续执行(模块真实的工作)

3039 /* This is where the real work happens */
3040 static int do_init_module(struct module *mod)
3041 {
/*...*/    
        /*呼叫内核模块通知链,该模块正在加载,随后介绍该机制*/
   blocking_notifier_call_chain(&module_notify_list,
3051                         MODULE_STATE_COMING, mod);
3052 
3053         /* Set RO and NX regions for core */
3054         set_section_ro_nx(mod->module_core,
3055                                 mod->core_text_size,
3056                                 mod->core_ro_size,
3057                                 mod->core_size);
3058 
3059         /* Set RO and NX regions for init */
3060         set_section_ro_nx(mod->module_init,
3061                                 mod->init_text_size,
3062                                 mod->init_ro_size,
3063                                 mod->init_size);
3064 
3065         do_mod_ctors(mod);
3066/* Start the module,执行模块定义的初始化函数代码 */
3067         if (mod->init != NULL)
3068                 ret = do_one_initcall(mod->init);
3069         if (ret < 0) {
3070                 /* Init routine failed: abort.  Try to protect us from
3071                    buggy refcounters. */
3072                 mod->state = MODULE_STATE_GOING;
3073                 synchronize_sched();
3074                 module_put(mod);
3075                 blocking_notifier_call_chain(&module_notify_list,
3076                                              MODULE_STATE_GOING, mod);
3077                 free_module(mod);
3078                 wake_up_all(&module_wq);
3079                 return ret;
3080         }
/*...*/
3089        /*模块已经加载进系统*/
3090         /* Now it's a first class citizen! */
3091         mod->state = MODULE_STATE_LIVE;
        blocking_notifier_call_chain(&module_notify_list,
3093                                      MODULE_STATE_LIVE, mod);
3094 
/*...*/
3116      /* Drop initial reference.释放init section占据的内存 */
3117         module_put(mod);
3118         trim_init_extable(mod);
3119 #ifdef CONFIG_KALLSYMS
3120         mod->num_symtab = mod->core_num_syms;
3121         mod->symtab = mod->core_symtab;
3122         mod->strtab = mod->core_strtab;
3123 #endif
3124         unset_module_init_ro_nx(mod);
3125         module_free(mod, mod->module_init);
3126         mod->module_init = NULL;
3127         mod->init_size = 0;
3128         mod->init_ro_size = 0;
3129         mod->init_text_size = 0;
3130         mutex_unlock(&module_mutex);
		/*唤醒等待该模块加载完成的那些等待链表上的节点*/
3131         wake_up_all(&module_wq);
3132 
3133         return 0;
3134 }


至此,模块的加载告一段落了!!!!!~~~~~~~确实是个明智而艰巨的工作,下一节介绍内核模块的依赖关系和模块的卸载。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值