一.背景

  a) 在进行JZ2440的一个小demo开发的时候,使用自己编译的内核(3.4.2)及lcd模块进行加载时,insmod会提示加载失败因为内核版本不匹配(提示当前内核版本为空),并且显示模块的内核版本为空。

  b) 尝试过修改编译的Makefile文件的内核目录,及重新编译内核及模块并重新烧写,均无效。

  c) 网上方法,使用统一的gcc编译文件系统同样无效,编译较新版本的busybox后命令可以成功使用。

  d) 开始着手分析insmod加载过程,希望发现真正原因

  e) 内核模块编译时尝试绕过insmod的版本检查(尚未实验)

二.概述

  模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中。对于每个模块,系统都要分配一个包含以下数据结构的内存区。

  一个module对象,表示模块名的一个以null结束的字符串,实现模块功能的代码。在2.6内核以前,insmod模块过程主要是通过modutils中的insmod加载,大量工作都是在用户空间完成。但在2.6内核以后,系统使用busybox的insmod指令,把大量工作移到内核代码处理,无论逻辑上还是代码量上都比原来精简了很多,通过busybox的insmod命令与内核进行接入。

三.insmod调用过程分析

  入口函数在busybox的insmod.c文件中

  1. int insmod_main(int argc UNUSED_PARAM, char **argv)
  2. {
  3. char *filename;
  4. int rc;
  5.  
  6. /* Compat note:
  7. * 2.6 style insmod has no options and required filename
  8. * (not module name - .ko can't be omitted).
  9. * 2.4 style insmod can take module name without .o
  10. * and performs module search in default directories
  11. * or in $MODPATH.
  12. */
  13.  
  14. IF_FEATURE_2_4_MODULES(
  15. getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
  16. argv += optind - 1;
  17. );
  18. //去的加载模块的路径名
  19. filename = *++argv;
  20. if (!filename)
  21. bb_show_usage();
  22.  
  23. rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
  24. if (rc)
  25. bb_error_msg("can't insert '%s': %s", filename, moderror(rc));
  26.  
  27. return rc;
  28. }

  

初始化函数bb_init_module中调用的函数parse_cmdline_module_options用来parse传入参数中的模块相关参数(文件为modutils.c)  

  1. char* FAST_FUNC parse_cmdline_module_options(char **argv, int quote_spaces)
  2. {
  3. char *options;
  4. int optlen;
  5.  
  6. options = xzalloc();
  7. optlen = ;
  8. //便利模块名后面的模块参数
  9. while (*++argv) {
  10. const char *fmt;
  11. const char *var;
  12. const char *val;
  13.  
  14. var = *argv;
  15. //为option分配空间
  16. options = xrealloc(options, optlen + + strlen(var) + );
  17. fmt = "%.*s%s ";
  18. val = strchrnul(var, '=');
  19. if (quote_spaces) {
  20. /*
  21. * modprobe (module-init-tools version 3.11.1) compat:
  22. * quote only value:
  23. * var="val with spaces", not "var=val with spaces"
  24. * (note: var *name* is not checked for spaces!)
  25. */
  26. if (*val) { /* has var=val format. skip '=' */
  27. val++;
  28. if (strchr(val, ' '))
  29. fmt = "%.*s\"%s\" ";
  30. }
  31. }
  32. optlen += sprintf(options + optlen, fmt, (int)(val - var), var, val);
  33. }
  34. /* Remove trailing space. Disabled */
  35. /* if (optlen != 0) options[optlen-1] = '\0'; */
  36. return options;
  37. }

  初始化函数bb_init_module会通过系统调用,调用内核的sys_init_module(syscalls.h声明,实现在module.c)

  1. /* Return:
  2. * 0 on success,
  3. * -errno on open/read error,
  4. * errno on init_module() error
  5. */
  6. int FAST_FUNC bb_init_module(const char *filename, const char *options)
  7. {
  8. size_t image_size;
  9. char *image;
  10. int rc;
  11. bool mmaped;
  12.  
  13. if (!options)
  14. options = "";
  15.  
  16. //TODO: audit bb_init_module_24 to match error code convention
  17. #if ENABLE_FEATURE_2_4_MODULES
  18. if (get_linux_version_code() < KERNEL_VERSION(,,))
  19. return bb_init_module_24(filename, options);
  20. #endif
  21.  
  22. image_size = INT_MAX - ;
  23. mmaped = ;
  24. image = try_to_mmap_module(filename, &image_size);
  25. if (image) {
  26. mmaped = ;
  27. } else {
  28. errno = ENOMEM; /* may be changed by e.g. open errors below */
  29. image = xmalloc_open_zipped_read_close(filename, &image_size);
  30. if (!image)
  31. return -errno;
  32. }
  33.  
  34. errno = ;
  35. //调用内核的系统调用
  36. init_module(image, image_size, options);
  37. rc = errno;
  38. if (mmaped)
  39. munmap(image, image_size);
  40. else
  41. free(image);
  42. return rc;
  43. }

  系统调用在内核中的实现(系统调用的调用过程分析以后补上):

  1. SYSCALL_DEFINE3(init_module, void __user *, umod,
  2. unsigned long, len, const char __user *, uargs)
  3. {
  4. int err;
  5. struct load_info info = { };
  6.  
  7. err = may_init_module();
  8. if (err)
  9. return err;
  10.  
  11. pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
  12. umod, len, uargs);
  13.  
  14. err = copy_module_from_user(umod, len, &info);
  15. if (err)
  16. return err;
  17.  
  18. return load_module(&info, uargs, );
  19. }

四.内核中的相关结构体

  以Linux-3.8.2为例,相关结构定义代码在include/linux/module.h中。

  1. 模块依赖关系
  1. struct module_use {
  2. struct list_head source_list;
  3. struct list_head target_list;
  4. struct module *source, *target;
  5. };

  2.模块状态信息

  1. enum module_state {
  2. MODULE_STATE_LIVE, /* Normal state. */
  3. MODULE_STATE_COMING, /* Full formed, running module_init. */
  4. MODULE_STATE_GOING, /* Going away. */
  5. MODULE_STATE_UNFORMED, /* Still setting it up. */
  6. };

  3.模块计数

  1. /**
  2. * struct module_ref - per cpu module reference counts
  3. * @incs: number of module get on this cpu
  4. * @decs: number of module put on this cpu
  5. */
  6. struct module_ref {
  7. unsigned long incs;
  8. unsigned long decs;
  9. } __attribute((aligned( * sizeof(unsigned long))));

  4.module结构(一个长度可怕的结构)

  module对象描述一个模块。一个双向循环链表存放所有module对象,链表头部存放在modules变量中,而指向相邻单元的指针存放在每个module对象的list字段中。

  1. Struct module
  2. {
  3. enum module_state state; //存放模块当前状态
  4. //装载期间状态为MODULE_STATE_COMING.
  5. //正常运行后,状态变为 MODULE_STATE_LIVE
  6. //正在卸载时,状态为 MODULE_STATE_GOING
  7.  
  8. /* Member of list of modules */
  9. struct list_head list; //模块链表指针,所有加载的模块保存在双向链表中,链表头部为定义的全局变量modules。
  10.  
  11. /* Unique handle for this module */
  12. char name[MODULE_NAME_LEN]; //模块名称
  13.  
  14. /* Sysfs stuff. */
  15. struct module_kobject mkobj;
  16. struct module_attribute *modinfo_attrs;
  17. const char *version;
  18. const char *srcversion;
  19. struct kobject *holders_dir;
  20.  
  21. /* Exported symbols */
  22. /*这三个用于管理模块导出符号,syms是一个数组,有num_syms个数组项,数组项类型为kernel_symbol,负责将标识符(name)分配到内存地址(value)
  23.  struct kernel_symbol 
  24.  { 
  25.      unsigned long value; 
  26.      const char *name; 
  27.  }; 
  28. //crcs也是一个num_syms个数组项的数组,存储了导出符号的校验和,用于实现版本控制
  29. */
  30. const struct kernel_symbol *syms; //指向导出符号数组的指针
  31. const unsigned long *crcs; //指向导出符号CRC值数组的指针
  32. unsigned int num_syms; //导出符号数目
  33.  
  34. /* Kernel parameters. */
  35. struct kernel_param *kp; //内核参数
  36. unsigned int num_kp; //内核参数个数
  37.  
  38. /* GPL-only exported symbols. */
  39.  /*在导出符号时,内核不仅考虑了可以有所有模块(不考虑许可证类型)使用的符号,还要考虑只能由 GPL 兼容模块使用的符号。 第三类的符号当前仍然可以有任意许可证的模块使用,但在不久的将来也会转变为只适用于 GPL 模块。gpl_syms,num_gpl_syms,gpl_crcs 成员用于只提供给 GPL 模块的符号;gpl_future_syms,num_gpl_future_syms,gpl_future_crcs 用于将来只提供给 GPL 模块的符号。unused_gpl_syms 和 unused_syms 以及对应的计数器和校验和成员描述。 这两个数组用于存储(只适用于 GPL)已经导出, 但 in-tree 模块未使用的符号。在out-of-tree 模块使用此类型符号时,内核将输出一个警告消息。 
  40. */ 
  41.  unsigned int num_gpl_syms; //GPL格式导出符号数
  42.  const struct kernel_symbol *gpl_syms; //指向GPL格式导出符号数组的指针
  43.  const unsigned long *gpl_crcs; //指向GPL格式导出符号CRC值数组的指针
  44. #ifdef CONFIG_UNUSED_SYMBOLS
  45. /* unused exported symbols. */
  46. const struct kernel_symbol *unused_syms;
  47. const unsigned long *unused_crcs;
  48. unsigned int num_unused_syms;
  49.  
  50. /* GPL-only, unused exported symbols. */
  51. unsigned int num_unused_gpl_syms;
  52. const struct kernel_symbol *unused_gpl_syms;
  53. const unsigned long *unused_gpl_crcs;
  54. #endif
  55.  
  56. #ifdef CONFIG_MODULE_SIG
  57. /* Signature was verified. */
  58. bool sig_ok;
  59. #endif
  60.  
  61. /* symbols that will be GPL-only in the near future. */
  62. const struct kernel_symbol *gpl_future_syms;
  63. const unsigned long *gpl_future_crcs;
  64. unsigned int num_gpl_future_syms;
  65. /* Exception table */
  66.  /*如果模块定义了新的异常,异常的描述保存在 extable数组中。 num_exentries 指定了数组的长度。 */
  67. unsigned int num_exentries;
  68. struct exception_table_entry *extable;
  69.  
  70.   /*模块的二进制数据分为两个部分;初始化部分和核心部分。 
  71.  前者包含的数据在转载结束后都可以丢弃(例如:初始化函数),后者包含了正常运行期间需要的所有数据。   
  72.  初始化部分的起始地址保存在 module_init,长度为 init_size 字节; 
  73.  核心部分有 module_core 和 core_size 描述。 
  74.  */
  75. /* Startup function. */
  76. int (*init)(void);
  77.  
  78. /* If this is non-NULL, vfree after init() returns */
  79. void *module_init; //用于模块初始化的动态内存区指针
  80.  
  81. /* Here is the actual code + data, vfree'd on unload. */
  82. void *module_core; //用于模块核心函数与数据结构的动态内存区指针
  83.  
  84. /* Here are the sizes of the init and core sections */
  85. unsigned int init_size, core_size;  //用于模块初始化的动态内存区大小和用于模块核心函数与数据结构的动态内存区指针
  86.  
  87. /* The size of the executable code in each section. */
  88.  //模块初始化的可执行代码大小,模块核心可执行代码大小,只当模块链接时使用
  89. unsigned int init_text_size, core_text_size;
  90.  
  91. /* Size of RO sections of the module (text+rodata) */
  92. unsigned int init_ro_size, core_ro_size;
  93.  
  94. /* Arch-specific module values */
  95. struct mod_arch_specific arch; //依赖于体系结构的字段
  96.  /*如果模块会污染内核,则设置 taints.污染意味着内核怀疑该模块做了一个有害的事情,可能妨碍内核的正常运作。 
  97.  如果发生内核恐慌(在发生致命的内部错误,无法恢复正常运作时,将触发内核恐慌),那么错误诊断也会包含为什么内核被污染的有关信息。 
  98.  这有助于开发者区分来自正常运行系统的错误报告和包含某些可疑因素的系统错误。 
  99.  add_taint_module 函数用来设置 struct module 的给定实例的 taints 成员。  
  100. 模块可能因两个原因污染内核: 
  101.  1,如果模块的许可证是专有的,或不兼容 GPL,那么在模块载入内核时,会使用 TAINT_PROPRIETARY_MODULE. 
  102.    由于专有模块的源码可能弄不到,模块在内核中作的任何事情都无法跟踪,因此,bug 很可能是由模块引入的。 
  103.   
  104.    内核提供了函数 license_is_gpl_compatible 来判断给定的许可证是否与 GPL 兼容。 
  105.  2,TAINT_FORCED_MODULE 表示该模块是强制装载的。如果模块中没有提供版本信息,也称为版本魔术(version magic), 
  106.    或模块和内核某些符号的版本不一致,那么可以请求强制装载。  
  107.  */
  108. unsigned int taints; /* same bits as kernel:tainted */
  109.  
  110. #ifdef CONFIG_GENERIC_BUG
  111. /* Support for BUG */
  112. unsigned num_bugs;
  113. struct list_head bug_list;
  114. struct bug_entry *bug_table;
  115. #endif
  116.  
  117. #ifdef CONFIG_KALLSYMS
  118. /*
  119. * We keep the symbol and string tables for kallsyms.
  120. * The core_* fields below are temporary, loader-only (they
  121. * could really be discarded after module init).
  122. */
  123. Elf_Sym *symtab, *core_symtab;
  124. unsigned int num_symtab, core_num_syms;
  125. char *strtab, *core_strtab;
  126.  
  127. /* Section attributes */
  128. struct module_sect_attrs *sect_attrs;
  129.  
  130. /* Notes attributes */
  131. struct module_notes_attrs *notes_attrs;
  132. #endif
  133.  
  134. /* The command line arguments (may be mangled). People like
  135. keeping pointers to this stuff */
  136. char *args;
  137.  
  138. #ifdef CONFIG_SMP
  139. /* Per-cpu data. */
  140. void __percpu *percpu;
  141. unsigned int percpu_size;
  142. #endif
  143.  
  144. #ifdef CONFIG_TRACEPOINTS
  145. unsigned int num_tracepoints;
  146. struct tracepoint * const *tracepoints_ptrs;
  147. #endif
  148. #ifdef HAVE_JUMP_LABEL
  149. struct jump_entry *jump_entries;
  150. unsigned int num_jump_entries;
  151. #endif
  152. #ifdef CONFIG_TRACING
  153. unsigned int num_trace_bprintk_fmt;
  154. const char **trace_bprintk_fmt_start;
  155. #endif
  156. #ifdef CONFIG_EVENT_TRACING
  157. struct ftrace_event_call **trace_events;
  158. unsigned int num_trace_events;
  159. #endif
  160. #ifdef CONFIG_FTRACE_MCOUNT_RECORD
  161. unsigned int num_ftrace_callsites;
  162. unsigned long *ftrace_callsites;
  163. #endif
  164.  
  165. #ifdef CONFIG_MODULE_UNLOAD
  166. /* What modules depend on me? */
  167. struct list_head source_list;
  168. /* What modules do I depend on? */
  169. struct list_head target_list;
  170.  
  171. /* Who is waiting for us to be unloaded */
  172. struct task_struct *waiter; //正卸载模块的进程
  173.  
  174. /* Destruction function. */
  175. void (*exit)(void);
  176.  /*module_ref 用于引用计数。系统中的每个 CPU,都对应到该数组中的数组项。该项指定了系统中有多少地方使用了该模块。 
  177. 内核提供了 try_module_get 和 module_put 函数,用对引用计数器加1或减1,如果调用者确信相关模块当前没有被卸载, 
  178. 也可以使用 __module_get 对引用计数加 1.相反,try_module_get 会确认模块确实已经加载。
  179.  struct module_ref { 
  180.    unsigned int incs; 
  181.    unsigned int decs; 
  182.   } 
  183. */ 
  184. struct module_ref __percpu *refptr; //模块计数器,每个cpu一个
  185. #endif
  186.  
  187. #ifdef CONFIG_CONSTRUCTORS
  188. /* Constructor functions. */
  189. ctor_fn_t *ctors;
  190. unsigned int num_ctors;
  191. #endif
  192. }

五.模块链接过程

  用户可以通过执行insmod外部程序把一个模块链接到正在运行的内核中。该过程执行以下操作:
  1.从命令行中读取要链接的模块名
  2.确定模块对象代码所在的文件在系统目录树中的位置。
  3.从磁盘读入存有模块目标代码的文件。
  4.调用init_module()系统调用。函数将模块二进制文件复制到内核,然后由内核完成剩余的任务。
  5.init_module函数通过系统调用层,进入内核到达内核函数 sys_init_module,这是加载模块的主要函数。
  6.结束。

Insmod模块加载过程分析的更多相关文章

  1. seaJS 模块加载过程分析

    先看一个seajs的官方example,  以下以seajs.use('main')为例, 解析加载mod main的过程 //app.html seajs.use("main") ...

  2. insmod模块加载过程代码分析1【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3966290.html 一.概述模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到 ...

  3. 有关Linux ipv6模块加载失败的问题

    有关Linux ipv6模块加载失败的问题 同事一个SUSE11sp3环境配置ipv6地址失败,提示不支持IPv6,请求帮助,第一反应是应该ipv6相关内核模块没有加载.     主要检查内容:   ...

  4. 实现一个类 RequireJS 的模块加载器 (二)

    2017 新年好 ! 新年第一天对我来说真是悲伤 ,早上兴冲冲地爬起来背着书包跑去实验室,结果今天大家都休息 .回宿舍的时候发现书包湿了,原来盒子装的牛奶盖子松了,泼了一书包,电脑风扇口和USB口都进 ...

  5. node模块加载层级优化

    模块加载痛点 大家也或多或少的了解node模块的加载机制,最为粗浅的表述就是依次从当前目录向上级查询node_modules目录,若发现依赖则加载.但是随着应用规模的加大,目录层级越来越深,若是在某个 ...

  6. 使用RequireJS并实现一个自己的模块加载器 (一)

    RequireJS & SeaJS 在 模块化开发 开发以前,都是直接在页面上引入 script 标签来引用脚本的,当项目变得比较复杂,就会带来很多问题. JS项目中的依赖只有通过引入JS的顺 ...

  7. 【模块化编程】理解requireJS-实现一个简单的模块加载器

    在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ..... ...

  8. requirejs:模块加载(require)及定义(define)时的路径小结

    原文地址:http://www.tuicool.com/articles/7JBnmy 接触过requirejs的童鞋可能都知道,无论是通过define来定义模块,还是通过require来加载模块,模 ...

  9. seajs实现JavaScript 的 模块开发及按模块加载

    seajs实现了JavaScript 的 模块开发及按模块加载.用来解决繁琐的js命名冲突,文件依赖等问题,其主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载. 官方文档:http:/ ...

随机推荐

  1. 微信网页开发调用微信jssdk接口遇到的坑以及最终解决方法 (持续更新)

    1.微信网页开发调用jssdk时报permission denied 大致是两个原因 (1)首先注册时未将你所调用的接口名字添加至jsApiList (2)第二个就是你的这个公众号没有权限使用这个ap ...

  2. Caused by: java.lang.ClassNotFoundException: com.mchange.v2.cfg.MConfig

    出错原因:c3p0 为0.9.5.2版本 而使用了 mchange-commons-java 的版本为0.2.3.4,mchange-commons-java 的版本太高了, 将mchange-com ...

  3. 前端工具-让浏览器兼容ES6特性

    babel:将ES6翻译为ES5 问题: 可以处理import和export么? 不能,还是用Rollup或者webpack打包一下吧 可以处理Promise么? 不能,还是使用babel-plugi ...

  4. webbrowser 防止读取 缓存

    http://bbs.csdn.net/topics/240011502 引用 3 楼 kelei0017 的回复: Delphi(Pascal) codeprocedure TInformation ...

  5. T1215:迷宫

    [题目描述] 一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行.同时当Extense处在某 ...

  6. PAT_A1085#Perfect Sequence

    Source: PAT A1085 Perfect Sequence (25 分) Description: Given a sequence of positive integers and ano ...

  7. python读取excel保存到mysql

    首先安装xlrd模块:pip install xlrd ,核心代码网上有很多,这里主要是关于一些个人实际碰到问题细节的处理 1.excel数据不规范导致读取的数据存在空白行和列: 2.参数化执行sql ...

  8. selenium报错TypeError: 'FirefoxWebElement' object is not iterable

    报错原因element少了s定位一组元素的方法与定位单个元素的方法类似,唯一的区别是在单词element后面多了一个s表示复数. 改为 返回结果为

  9. leetcode.双指针.524通过删除字母匹配到字典里最长单词-Java

    1. 具体题目 给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到.如果答案不止一个,返回长度最长且字典顺序最小的字符串.如果答案不存在,则返回空 ...

  10. 54.Counting Bits( 计算1的个数)

    Level:   Medium 题目描述: Given a non negative integer number num. For every numbers i in the range 0 ≤ ...