基础知识
模块是一种向linux内核添加设备驱动程序,文件系统及其他组件的有效方法,不需要重新编译新内核或重启系统。
模块具有如下优点:
- 通过使用模块,内核发布者能够预先编译大量驱动樨序,而不会致使内核映像的尺寸发膨胀。
- 内核开发者可以试验性的代码打包到模块中,模块可以卸载,修改代码或重新打包后可以点新装载。
添加和删除
从用户的角度看,模块可以通过两个不同的系统程添加到运行的内核中。它们分别是modprobe和insmod。modprobe在识别出目标模块所依赖的模块之后,在内核中也会使用insmod,在用户空间对模块的处理也是基于insmod。
在处理该系统调用时,模块代码首先复制到内核内存中。接下来是重定位工作和解决模块中未定义的引用。因为模块使用了持久编译到内核中的函数,在模块本身编译时无法确定这些函数的地址,所以的要在这里处里未定义的引用。
处理未解决的引用,为与内核的剩余部分协作,模块必须使用内核提供的函数。这些可能是通用的辅助函数,比如几乎内核每一部分都会使的printk或kmalloc。
- insmod只加载一个单一的模块到内核中,而该模块可能只依赖内核中已存在的代码,并不关注所有依赖代码是通过模块动态加载,还是持久编译到内核中。
- modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod。在用户空间对模块的处理基于insmod。
- 在加载模块时所需要的操作,与通过ld和ld.so借助于动态库链接应用程序的操作。从外部来看,模块只是普通的可重定位目标文件。file命令的输出表明模块文件是可重定位的,这是用户空间程序设计中一专业术语。可重位文件的函数都不会引用绝对地址,而只是指向代码中的相对地址,因此可以在内存的任意偏移地址加载,当然在映像加载到内存时,映像中的地址要由动态连接器ld.so进行适当的修改即可。重定位的工作由内核自身执行,而不是动态装载器。
- ramfs模块允许在内存中建立一个文件系统(称为RAM磁盘),因而须调用register_filesystem函数将自身添加到内核中可用文件系统的列表。此模块还使用内核代码中generic_file_read/generic_file_write标准函数,基本大多数内核文件系统都会使用这些普通函数。
很明显这些函定义在内核的基础代码中,因而已经加载到内存。但如何找到与相关函数名称匹配的地址,以便解决这些引用呢?为此,内核提供了一个所有导出函的列表。该列表给出了所有导出函数的内存地址和对应函数名,可以过pro文件系统访问,即文件/pro/kallsyms:
T表示符号位于代码段,而D表示符号位于数据段等。
在向内核添加模块时,需要考虑下列相关问题。
- 内核提供的函数符号表,可以在模块加载时动态扩展其长度。
- 如果模块之间有相互依赖,那么向内核添加模块的顺序很重要。
插入删除模块
用户空间工具和内核的模块实现之间的接口,包括两个系统调用。
init_module:将一个新的模块插入到内核中。用户空间工具只需提供二进制数据。所有其他工作(特别是重定位核解决引用)由内核自身完成。
delete_module:从内核移除一个模块。当然,前提是该模块的代码不再使用,并且其他模块也不再使用该模块导出的函数。
还有一个request_module函数(不是系统调用),用于从内核端加载模块。它不仅用于加载模块,还用于实现热插拔功能。
模块的表示
在详细讲解模块相关函数的实现之前,有必要解释如何在内核中表示模块(及具属性)。首先需要定义一组数据结构。真中,module是最重要的数据结构。内核中驻留的每一个模块,都分配了该结构的一个实例。
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是一个指针,指向一个在模块初始化时调用的函数 /* 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 };
依赖关系和引用
如果模块B使用了模块A供的的数,那么模块A和模块B之间就存在关糸。可以两种不同的方式来这种关系。
- 模块B依赖块A。除非模块A已经驻留在内孩内存,否则模块B无法卸载。
- 模块B引用模块A。换句话说,除非模块B已经移除,否则模块A无法从内核移除。事实上,条件应该是所有引用模块A的模块都已经从内核移除。在内核中,这种关系你之为模块B使用模块A。为正确管理这些依赖关系,内核需要引入另一个数据结构:
/* modules using other modules: kdb wants to see this. */ struct module_use {//为正确管理依赖关系内核inrush此数据结构 struct list_head source_list; struct list_head target_list; struct module *source, *target; };
模块的二进制结构
模块使用ELF二进制恪式,模块中包含了几个额外的段,普通的程序或库中不会出现。除了少量由编译器产生、与我们的讨论不相关的段(主要是重定位段),模块由以下ELF段组成。
生成模块要执行下述3个步骤:
- 首先,模块源码中的所有C文件都编译成普通的.o目标文件。
- 在为所有模块产生目标文件后,内核可以分析它们。找到的附加信息(例如,模块依赖关系)保存在一个独立的文件中,也编译为一个二进制文件。
- 将前述两个步产生的二进制文件链接起来,生成最终的模块。