内核模块(1)模块初探

/*本文由真胖子同志私人定制,烦请转载保留来源http://blog.csdn.net/figtingforlove/article/details/20150957*/

概念:

通过在系统运行期间,加载内核模块来动态的扩展系统该功能而无需重启系统,更无须为这新增添的功能重新编译一个新的系统内核映像。

1.      ELF文件格式解析

内核模块采用ELF(Executableand Linkable Format )格式作为文件的数据组织格式,更具体的讲,内核模块是一种普通的可重定位目标文件。


上图忽略了驱动程序模块中不会用到了program header table,可以看到静态的ELF文件视图总体分为三大部分ELF header,中间的section和尾部的section header table。

ⅰ.ELF 头

32位体系结构中,3.8内核用如下数据结构表示ELF头部。

Linux/include/uapi/linux/elf.h

203 typedef struct elf32_hdr{
204   unsigned char e_ident[EI_NIDENT]; //标识elf文件(可容纳16字节)
205   Elf32_Half    e_type;    //elf文件类型
206   Elf32_Half    e_machine; //elf文件所需的体系结构
207   Elf32_Word    e_version; //elf 版本信息
208   Elf32_Addr    e_entry;   //文件在虚拟内存的入口点(程序加载并映射到内存后,执行开始的位置)
209   Elf32_Off     e_phoff;//程序头表在elf二进制文件中的偏移量
210   Elf32_Off     e_shoff; //段头表在elf二进制文件中的偏移量
211   Elf32_Word    e_flags; //特定于处理器的状态
212   Elf32_Half    e_ehsize;//elf头的长度
213   Elf32_Half    e_phentsize;//程序头表中一项长度
214   Elf32_Half    e_phnum; //程序头表中项的数目
215   Elf32_Half    e_shentsize; //段头表中一项长度
216   Elf32_Half    e_shnum; //段头表中项的数目
217   Elf32_Half    e_shstrndx;//段名称字符串表段在段头表的索引
218 } Elf32_Ehdr;

ⅱ.section部分

    ELF 文件的主体,保存了与文件相关的各种形式的数据(符号表,实际的二进制码,数值常数等),在模块加载时不同的section会根据自身属性被搬移到新的内存区域(init和core)。

ⅲ.section header table部分

    段头表组织为数组,每一项表示一个段的信息,内核定义如下数据结构表示一个sectionheader entry。

299 typedef struct elf32_shdr {
300   Elf32_Word    sh_name; //段名(在段名串表中的索引值)
301   Elf32_Word    sh_type;//段的类型(符号表,重定位,字符串等)
302   Elf32_Word    sh_flags;//段标志(可写,是否分配内存等)
303   Elf32_Addr    sh_addr;//该项对应的段在虚拟内存中的地址(静态文件视图中为0,模块加载时,加载器会用该段在内存中的实际地址改写该值)
304   Elf32_Off     sh_offset;//对应的段在elf文件视图中的偏移量
305   Elf32_Word    sh_size;//段的长度
306   Elf32_Word    sh_link;//根据段的类型有不同的解释
307   Elf32_Word    sh_info; //与sh_link联用
308   Elf32_Word    sh_addralign; //段数据在内存中的对齐方式
309   Elf32_Word    sh_entsize; //表示固定数量entry组成的段(符号表)中entry的长度
310 } Elf32_Shdr;
2. 从驱动开发者的角度看模块的加载

在用户空间,利用“insmod demodev.ko”命令加载模块的时候,内核的操作行为是如何实现的呢?

首先,insmod命令通过init_module系统调用陷入内核,执行系统调用函数sys_init_module,在分析这个函数之前我们先来看看内核是如何表示一个模块的,这是一个非常重要的数据结构(这里重点关注蓝色注释部分)。

Linux/include/linux/module.h

229 struct module
230 {
231         enum module_state state; //模块在加载过程中的状态
232 
233         /* Member of list of modules */
234         struct list_head list;//组织在内核维护的模块链表中
235 
236         /* Unique handle for this module */
237         char name[MODULE_NAME_LEN];//模块名
238 
239         /* Sysfs stuff. */
240         struct module_kobject mkobj;//模块的内核对象
241         struct module_attribute *modinfo_attrs;//模块属性
242         const char *version;
243         const char *srcversion;
244         struct kobject *holders_dir;
245 
246         /* Exported symbols */    //导出符号
247         const struct kernel_symbol *syms;  
248         const unsigned long *crcs;
249         unsigned int num_syms;
250 
251         /* Kernel parameters. */   //模块参数
252         struct kernel_param *kp;
253         unsigned int num_kp;
254 
255         /* GPL-only exported symbols. */
256         unsigned int num_gpl_syms;
257         const struct kernel_symbol *gpl_syms;
258         const unsigned long *gpl_crcs;
259 
                     …
277         /* symbols that will be GPL-only in the near future. */
278         const struct kernel_symbol *gpl_future_syms;
279         const unsigned long *gpl_future_crcs;
280         unsigned int num_gpl_future_syms;
281 
282         /* Exception table */   //模块异常表
283         unsigned int num_exentries;
284         struct exception_table_entry *extable;
285 
286         /* Startup function. */
287         int (*init)(void); //内核模块初始化函数(最终为module__init宏指定的函数)
288 
289         /* If this is non-NULL, vfree after init() returns */
290         void *module_init;   //elf中init段在内核内存的地址
291 
292         /* Here is the actual code + data, vfree'd on unload. */
293         void *module_core;  //elf中core段在内核内存的地址
294 
295         /* Here are the sizes of the init and core sections */
296         unsigned int init_size, core_size; //记录当前init和core	
                                                          空间的大小
297 
298         /* The size of the executable code in each section.  */
299         unsigned int init_text_size, core_text_size;
300 
301         /* Size of RO sections of the module (text+rodata) */
302         unsigned int init_ro_size, core_ro_size;
303 
304         /* Arch-specific module values */
305         struct mod_arch_specific arch;
306 
307         unsigned int taints;    /* same bits as kernel:tainted */        //模块污染内核标记
                …………
316 #ifdef CONFIG_KALLSYMS  //模块的所有符号(包括常量,不仅是导出符号)
317         /*
318          * We keep the symbol and string tables for kallsyms.
319          * The core_* fields below are temporary, loader-only (they
320          * could really be discarded after module init).
321          */
322         Elf_Sym *symtab, *core_symtab;  //符号表
323         unsigned int num_symtab, core_num_syms;
324         char *strtab, *core_strtab;
325 
326         /* Section attributes */
327         struct module_sect_attrs *sect_attrs;
328 
329         /* Notes attributes */
330         struct module_notes_attrs *notes_attrs;
331 #endif
332 
333         /* The command line arguments (may be mangled).  People like
334            keeping pointers to this stuff */
335         char *args; //装载器件传递给模块的命令行参数
		…
363 
364 #ifdef CONFIG_MODULE_UNLOAD   //模块可卸载配置
365         /* What modules depend on me? */  //模块间依赖关系
366         struct list_head source_list;
367         /* What modules do I depend on? */
368         struct list_head target_list;
369 
370         /* Destruction function. */
371         void (*exit)(void); //模块卸载函数
372 
373         struct module_ref __percpu *refptr;//模块引用计数
		...
381 };


了解了模块的内核表示之后,接着来分析这个函数。首先,我们来看一下sys_init_moduele的代码流程图,看看它主要完成什么功能。

Load_module作为内核模块加载器的核心函数,“几乎完成了所有艰苦的工作”,先来介绍这个函数的工作原理,最后我们再分析linxu内核源代码。

(1)从用户空间复制模块数据到内核空间的一个临时内存(vmalloc分配),从而在内核空间构造demodev.ko的ELF静态内存视图。

(2)计算段名称字符串表和符号名称字符串表的基址

       定义entry为段头表首址 entry = (void*)hdr + hdr->e_shoff;

段名称字符串表:

Char *sectrings = (char*)hdr + entry[hdr->e_shstrndx].sh_offset;

hdr->e_shstrndx是段名表的索引,entry表示段头表的起始,offset表示段名表的偏移量

符号表:

遍历段头表,找到entry[i].e_type = SHT_SYMTAB的项,Entry[i].e_link是符号名称字符串表的索引

Char *strtab = (char*)hdr + entry[entry[i].e_link].sh_offset;

(3)HDR视图的第一次改写

       在上述的HDR视图中,段头表中各项的sh_addr仍然为0,现在应该将该值指向每一项所对应的段在HDR视图中的实际地址了

       遍历段头表的每一项修改entry[i]. sh_addr = (size_t)hdr + entry[i].sh_offset;

(4)struct module类型变量mod的初始化

       首先,编译模块文件的时候,模块编译工具链会在ELF文件中生成这样一个段section".gnu.linkonce.this_module”,打开编译模块后生的目录,会发现扩展名为“.mod.c”的文件,其中有如下定义

struct module __this_module
8    __attribute__((section(".gnu.linkonce.this_module"))) = {
9    .name = KBUILD_MODNAME,
10    .init = init_module,
11    #ifdef CONFIG_MODULE_UNLOAD
12    .exit = cleanup_module,
13    #endif
14    };

可见定义了一个变量__this_module,并将该数据放到ELF文件的gnu.linkonce.this_module段保存。这里小小说明下,上述变量里模块初始化函数和析构函数的初始化有些另类,原因何在?在我们编写的模块中,使用module_init(my_init)和module_exit(my_exit)宏定义,将我们模块程序中的初始化函数my_init和析构函数my_exit函数变为init_module和cleanup_module的别名,这样就可以执行我们定义的函数了。

    那么现在要做的就是利用该段初始化mod变量了。内核调用find_sec(…,name)函数遍历段头表的所有项找到名称相符的段,返回该段的段头表索引值modindex

    Mod = (void*)entry[modindex].sh_addr

(5)HDR视图的第二次改写

       临时内存映像中的段会根据各自flag搬移到新的内存中,内核调用layout_section函数遍历HDR视图中的每个段,对每个段的SHF_ALLOC标记划分为两大类:init段和core段。由于ELF文件中的符号表所在的段没有SHF_ALLOC标记,所以还要有一个对符号表的处理,很据内核配置选项CONFIG_KALLSYMS通过调用layout_symtab函数把符号表搬移到CORE section段。

    随后内核调用vmalloc分配init和core对应的内存,mod变量中的module_init和module_core记录这两块的基地址,然后包对应的数据搬到新的内存空间,遍历段头表中各项修改sh_addr指向新的最终内存地址。注意,这里".gnu.linkonce.this_module”段也搬到了core section内存空间,所以要更新mod变量的值。   


这样模块的数据就算进入了内核内存,下节会着重介绍模块的导出符号和加载过程中对“未解决引用”符号的处理”。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值