在linux中系统对驱动程序的加载提供了两种方式静态编译进内核和动态加载,并且驱动开发者通常会提供一个int xxx_init(void)函数,并通过诸如module_init(xxx_init)、early_initcall(xxx_init)等的方式使驱动程序被运行时能第一时间先执行xxx_init函数。那么内核是怎样做到这一点的呢?下面我们从驱动静态编译进内核和动态加载这两种来分析这个过程。
首先在include\linux\init.h代码中,可以看到关于module_init的定义有两种,通过预编译阶段判断MODULE宏是否被定义来确定采用哪种module_init的定义方式,这也就是在编译生成驱动的ko文件时为什么必须定义MODULE宏的原因。
1.驱动静态编译进内核(未定义MODULE宏)

/*下面我们逐步展开module_init*/
/*定义module_init为__initcall*/
#define module_init(x)  __initcall(x);
#define __initcall(fn) device_initcall(fn)   /*定义__initcall为device_initcall*/
/** Early initcalls run before initializing SMP.** Only for built-in code, not modules.*/
#define early_initcall(fn)      __define_initcall(fn, early)/** A "pure" initcall has no dependencies on anything else, and purely* initializes variables that couldn't be statically initialized.** This only exists for built-in code, not for modules.* Keep main.c:initcall_level_names[] in sync.*/
#define pure_initcall(fn)       __define_initcall(fn, 0)#define core_initcall(fn)       __define_initcall(fn, 1)
#define core_initcall_sync(fn)      __define_initcall(fn, 1s)
#define postcore_initcall(fn)       __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
#define device_initcall(fn)     __define_initcall(fn, 6)  /*定义device_initcall为__define_initcall其中6为加载的优先级(后面会用到这个数)*/
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)
#define late_initcall(fn)       __define_initcall(fn, 7)
#define late_initcall_sync(fn)      __define_initcall(fn, 7s)#define __define_initcall(fn, id) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(".initcall" #id ".init"))) = fn; \LTO_REFERENCE_INITCALL(__initcall_##fn##id)
/*通过下面可以看到如果没有定义CONFIG_LTO宏那么LTO_REFERENCE_INITCALL展开后是一个空,所以我们展开__define_initcall宏的时候可以暂时不管它。*/
#ifdef CONFIG_LTO
#define LTO_REFERENCE_INITCALL(x) \; /* yes this is needed */           \static __used __exit void *reference_##x(void) \{                      \return &x;             \}
#else
#define LTO_REFERENCE_INITCALL(x)
#endif/*通过上面的关系可以试着将例如module_init(test_init)的定义展开,结果如下:*/
module_init(test_init)  ->  __define_initcall(test_init, 6)
__define_initcall(test_init, 6)static initcall_t __initcall_test_init6 __used \__attribute__((__section__(".initcall6.init"))) = test_init;
/*最终module_init(test_init)的结果是*/
static initcall_t __initcall_test_init6 __used \__attribute__((__section__(".initcall6.init"))) = test_init;

不难看出module_init(test_init)实际上就是定义了一个类型是initcall_t 初始值为test_init的__initcall_test_init6变量,并且该变量存放在.initcall6.init段中。诸如:early_initcall(fn)、arch_initcall(fn)等的过程和这个类似,只是最后存放的段不一样而已具体是那个段由__define_initcall(fn, id)中的id决定。上面的用到的段都被在arch\arm\kernel\vmlinux.lds中定义。且各段的起始地址如下
__initcall_start = .; *(.initcallearly.init)
__initcall0_start = .; *(.initcall0.init) *(.initcall0s.init)
__initcall1_start = .; *(.initcall1.init) *(.initcall1s.init)
__initcall2_start = .; *(.initcall2.init) *(.initcall2s.init)
__initcall3_start = .; *(.initcall3.init) *(.initcall3s.init)
__initcall4_start = .; *(.initcall4.init) *(.initcall4s.init)
__initcall5_start = .; *(.initcall5.init) *(.initcall5s.init)
__initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init)
__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)
__initcall7_start = .; *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;

vmlinux.lds中如下代码定义各个initcall数据段.init.data : {*(.init.data) *(.meminit.data) *(.init.rodata) *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; *(__clk_of_table) *(__clk_of_table_end) . = ALIGN(8); __reservedmem_of_table = .; *(__reservedmem_of_table) *(__reservedmem_of_table_end) . = ALIGN(8); __clksrc_of_table = .; *(__clksrc_of_table) *(__clksrc_of_table_end) . = ALIGN(8); __cpu_method_of_table = .; *(__cpu_method_of_table) *(__cpu_method_of_table_end) . = ALIGN(8); __cpuidle_method_of_table = .; *(__cpuidle_method_of_table) *(__cpuidle_method_of_table_end) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; *(__irqchip_of_table) *(__irqchip_of_table_end). = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;__con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;__security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;. = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)}

通过上面的方式已经将module_init灯修饰的函数地址放在了各自的代码段中统一管理的起来,接下来要做的就是内核启动过程中在这些段中依次把所有的函数地址拿出开,并执行相对应的函数。下面是执行这些段中函数的调用过程:

kernel_init-> kernel_init_freeable-> do_basic_setup-> do_initcalls-> do_initcall_level(level); /**/

do_initcalls和do_initcall_level分析《init\main.c》

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
/*将上面vmlinux.lds中定义的各个initcall段的首地址存放到initcall_levels的数组中,目的在后面将通过遍历数组的方式来达到遍历整个initcall数据段,并且initcall_levels也会被编译器存放在__initdata数据区。内核加载完成后会通过kernel_init->free_initmem调用将整个__initdata区释放。目的应该是为了节省内存空间吧。*/
static initcall_t *initcall_levels[] __initdata = {__initcall0_start,__initcall1_start,__initcall2_start,__initcall3_start,__initcall4_start,__initcall5_start,__initcall6_start,__initcall7_start,__initcall_end,
};/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {"early","core","postcore","arch","subsys","fs","device","late",
};static void __init do_initcall_level(int level)
{initcall_t *fn;strcpy(initcall_command_line, saved_command_line);parse_args(initcall_level_names[level],initcall_command_line, __start___param,__stop___param - __start___param,level, level,&repair_env_string);/*遍历level指定的段区域,并获取到该段区所存放的初始化函数地址,调用do_one_initcall函数执行相应的初始化函数。*/for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)/*调用执行函数fn。*/do_one_initcall(*fn);
}static void __init do_initcalls(void)
{int level;/*循环将initcall_levels数组中存放8个段首地址拿出来,并作为参数传递给do_initcall_level。*/for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level);
}

2.动态加载(定义MODULE宏,将编译成KO模块)

/*动态加载时所有的initcall实际上都是module_init,且并没有加载的优先级区别,具体的加载顺序是由文件系统中insmod的顺序决定的。*/
#define early_initcall(fn)      module_init(fn)
#define core_initcall(fn)       module_init(fn)
#define core_initcall_sync(fn)      module_init(fn)
#define postcore_initcall(fn)       module_init(fn)
#define postcore_initcall_sync(fn)  module_init(fn)
#define arch_initcall(fn)       module_init(fn)
#define subsys_initcall(fn)     module_init(fn)
#define subsys_initcall_sync(fn)    module_init(fn)
#define fs_initcall(fn)         module_init(fn)
#define fs_initcall_sync(fn)        module_init(fn)
#define rootfs_initcall(fn)     module_init(fn)
#define device_initcall(fn)     module_init(fn)
#define device_initcall_sync(fn)    module_init(fn)
#define late_initcall(fn)       module_init(fn)
#define late_initcall_sync(fn)      module_init(fn)#define console_initcall(fn)     module_init(fn)
#define security_initcall(fn)       module_init(fn)/* Each module must use one module_init(). */
#define module_init(initfn)                 \static inline initcall_t __inittest(void)      \{ return initfn; }                 \int init_module(void) __attribute__((alias(#initfn))); /*定义别名*//* This is only required if you want to be unloadable. */
#define module_exit(exitfn)                 \static inline exitcall_t __exittest(void)      \{ return exitfn; }                 \void cleanup_module(void) __attribute__((alias(#exitfn)));

以module_init(test_init)为例参照上面关系进行展开:

#define module_init(test_init)                   \static inline initcall_t __inittest(void)      \{ return test_init; }                  \int init_module(void) __attribute__((alias(test_init)));/*上面最后一句的作用就是为test_init定义一个别名,别名为init_module。*所以最近上面的过程也就是定义一个__inittest函数,函数中返回init_module(别名会替换到原有函数的名称test_init)函数指针。*所有加载KO文件是执行的第一个函数应该是__inittest,然后__inittest会返回驱动初始化的函数,接着在执行这个初始化函数。*/
//最终结果如下:
static inline initcall_t __inittest(void)
{ return init_module; }

对于module_exit的过程实质上和module_init的过程系统。但是对于静态编译进内核的驱动中module_exit在内核启动阶段是不会再kernel_init->free_initmem中释放module_exit所存放的那个段空间的,原因吗就是释放后在关机或重启时需要调用退出函数时就不存在了,会出现错误。
在.init.data段中分布图:

linux设备驱动中的module_init相关推荐

  1. Linux设备驱动中的并发控制总结

    并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资源(硬件资源和软件上的全局.静态变量)的访问则容易导致竞态(race conditions).   SMP是一 ...

  2. linux 两个驱动 竞态,第7章 Linux设备驱动中的并发控制之一(并发与竞态)

    本章导读 Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态(竞争状态). Linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景. 7.1讲解了并 ...

  3. linux 设备驱动阻塞,深入浅出:Linux设备驱动中的阻塞和非阻塞I/O

    今天写的是Linux设备驱动中的阻塞和非阻塞I/0,何谓阻塞与非阻塞I/O?简单来说就是对I/O操作的两种不同的方式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式. 一.基本概念: 阻塞操作 ...

  4. Linux设备驱动开发详解:第7章 Linux设备驱动中的并发控制

    7.1并发与竞态 (1).竞态的发生场景:CPU0的进程与CPU1的进程之间.CPU0的中断与CPU1的进程之间.CPU0的中断与CPU1的中断之间: (2).解决竞态问题的途径是保证对共享资源的互斥 ...

  5. linux write引起进程挂起,Linux设备驱动中的阻塞与非阻塞总结

    Linux设备驱动中的阻塞与非阻塞总结 阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作. 非阻塞操作的进程在不能进行设备操作时,并不挂起.被挂起的进程进入sl ...

  6. Linux设备驱动中的阻塞和非阻塞IO

    这篇文章我们来了解下Linux设备驱动中阻塞和非阻塞. 阻塞:阻塞是指执行设备操作时,如果不能获得设备资源,则挂起进程,是进程进入休眠模式,直到设备资源可以获取. 非阻塞:非阻塞是在不能获取设备资源时 ...

  7. Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O

    在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程--网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: ...

  8. Linux 设备驱动中的 I/O模型(二)—— 异步通知和异步I/O

    阻塞和非阻塞访问.poll() 函数提供了较多地解决设备访问的机制,但是如果有了异步通知整套机制就更加完善了. 异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态 ...

  9. Linux设备驱动中的阻塞与非阻塞I/O

    阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式 本例子讲述了这两者的区别 并实现I/O的等待队列机制, 并进行了用户空间的验证 基本概念: 1> ...

最新文章

  1. cubemx pwm dma_红米K30S至尊纪念版翻车?被曝虽是LDC屏,却是PWM调光
  2. 斯坦福大学报告称中国AI论文引用率首超美国!但李国杰院士也发文灵魂拷问...
  3. 《Oracle高性能SQL引擎剖析:SQL优化与调优机制详解》一2.2 内部函数与操作
  4. 双十一刷名企项目?学霸果然不一样
  5. 计算机中cmos设置程序,电脑主板上有CMOS设置是什么意思
  6. CentOS 6.9通过RPM安装EPEL源(http://dl.fedoraproject.org)
  7. C - Rencontre Gym - 102798C
  8. RxJava学习入门
  9. react native ScrollView
  10. stegsolve---图片隐写查看器
  11. Linux学习笔记二十——系统裁剪之二
  12. Ubutun16.04安装和使用daemontools
  13. 太阳方位角 matlab,太阳天顶角与太阳方位角计算软件V4.1及源码
  14. 2021年总结:厚积薄发,突破自我
  15. ogg在DDL语句同步时出现[Error code [942], ORA-00942的分析
  16. NVIDIA GeForce GTX 1060 驱动安装--------TensorFlow系列学习笔记(一)
  17. Android高级UI系列教程(二)
  18. 父进程与子进程间相互发送信号
  19. 【Excel】使用技巧及总结
  20. 关于wlw(windows live writer):“无法连接到您的日志服务:服务器响应无效”的解决方案

热门文章

  1. Unity 环境搭建
  2. 用牛顿迭代法求方程。
  3. drools的简单入门案例
  4. SpringBoot配置过滤器和拦截器
  5. 高中计算机必修选修知识点,新课标高中数学必修+选修全部知识点精华归纳总结...
  6. RGB图像卷积生成Feature map特征图过程
  7. 杭电OJ4544 湫湫系列故事——消灭兔子(优先队列过)
  8. 安装R语言(Rstudio、R、RTools)
  9. Matlab系列之数组(矩阵)的生成
  10. SpringDataJPA+Hibernate框架源码剖析(六)@PersistenceContext和@Autowired注入EntityManager的区别