本文转载自博客http://blog.csdn.net/richard_liujh/article/details/45669207

写过linux驱动的程序猿都知道module_init() 这个函数。那么我们来了解一下module_init这个函数的具体功能和执行过程

在kernel源码目录中找到include\linux\init.h文件

  1. <span style="font-family:SimSun;font-size:14px;">/**
  2. * module_init() - driver initialization entry point
  3. * @x: function to be run at kernel boot time or module insertion
  4. *
  5. * module_init() will either be called during do_initcalls() (if
  6. * builtin) or at module insertion time (if a module).  There can only
  7. * be one per module.
  8. */
  9. #define module_init(x)  __initcall(x);</span>

这里面就有对module_init 的定义,我们发现module_init(x)是一个宏定义,那么_initcall(x)又是什么呢?

  1. #define __initcall(fn) device_initcall(fn)

感觉怪怪的,怎么这么多的宏??再解释这个之前,我们再来看看更多的宏定义吧

完整的宏定义如下:

__define_initcall:

  1. /* initcalls are now grouped by functionality into separate
  2. * subsections. Ordering inside the subsections is determined
  3. * by link order.
  4. * For backwards compatibility, initcall() puts the call in
  5. * the device init subsection.
  6. *
  7. * The `id' arg to __define_initcall() is needed so that multiple initcalls
  8. * can point at the same handler without causing duplicate-symbol build errors.
  9. */
  10. #define __define_initcall(fn, id) \
  11. static initcall_t __initcall_##fn##id __used \
  12. __attribute__((__section__(".initcall" #id ".init"))) = fn

initcalls:

  1. </pre><pre name="code" class="cpp">/
  2. * A "pure" initcall has no dependencies on anything else, and purely
  3. * initializes variables that couldn't be statically initialized.
  4. *
  5. * This only exists for built-in code, not for modules.
  6. * Keep main.c:initcall_level_names[] in sync.
  7. */
  8. #define pure_initcall(fn)       __define_initcall(fn, 0)
  9. #define core_initcall(fn)       __define_initcall(fn, 1)
  10. #define core_initcall_sync(fn)      __define_initcall(fn, 1s)
  11. #define postcore_initcall(fn)       __define_initcall(fn, 2)
  12. #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
  13. #define arch_initcall(fn)       __define_initcall(fn, 3)
  14. #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
  15. #define subsys_initcall(fn)     __define_initcall(fn, 4)
  16. #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
  17. #define fs_initcall(fn)         __define_initcall(fn, 5)
  18. #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
  19. #define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
  20. #define device_initcall(fn)     __define_initcall(fn, 6)
  21. #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
  22. #define late_initcall(fn)       __define_initcall(fn, 7)
  23. #define late_initcall_sync(fn)      __define_initcall(fn, 7s)
  24. #define __initcall(fn) device_initcall(fn)

Note:下面用xxx_initcall来代表pure_initcall,core_initcall、core_initcall_sync … …

我们可以看到非常多的xxx_initcall宏函数定义,他们都是通过__define_initcall 实现的。在__define_initcall里面包含了两个参数,一个是fn,另一个则是id。那么,这么多的宏又有何用?

我们来到init\main.c文件中可以找到函数do_initcalls

  1. static void __init do_initcalls(void)
  2. {
  3. int level;
  4. for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
  5. do_initcall_level(level);
  6. }

很明显do_initcalls中有一个for循环,那么此循环就是按照优先级顺序执行一些函数的。那么问题又来了,执行哪些函数??我们看看do_initcalls这个名字。是不是initcall非常的眼熟?没错就是上面我们提到过的宏定义xxx_initcall里面就有initcall。

所以,我们先来解释一下这些宏有什么用
还是从我们最熟悉的地方module_init(fn)开始说起,其中fn是module_init的参数,fn是一个函数指针(函数名)。

module_init(fn)---> __initcall(fn) ---> device_initcall(fn) ---> __define_initcall(fn, 6)

所以当我们写module_init(fn)最终我们可以简化成以下内容(假设module_init的参数为test_init)

module_init(test_init) ---> __define_initcall(test_init, 6)

  1. #define __define_initcall(fn, id) \
  2. static initcall_t __initcall_##fn##id __used \
  3. __attribute__((__section__(".initcall" #id ".init"))) = fn

简单补充:

符号 作用 举例
##
“##”符号可以
是连接的意思
例如 __initcall_##fn##id 为__initcall_fnid
那么,fn = test_init,id = 6时,__initcall_##fn##id 为 __initcall_test_init6
#
“#”符号可以
是字符串化的意思
例如 #id 为 “id”,id=6 时,#id 为“6”
 
通过上面的定义,我们把module_init(test_init)给替换如下内容
 
static initcall_t __initcall_test6 __used __attribute__((__section__(".initcall""6" ".init"))) =test_init

是不是看起来更加头疼,那么我们说简单一点。通过__attribute__(__section__)设置函数属性,也就是将test_init放在.initcall6.init段中。这个段在哪用?这就要涉及到链接脚本了。

大家可以到kernel目录arch中,根据自己的处理器平台找到对应的链接脚本。例如我现在的平台是君正m200(mips架构),可能大部分是arm架构。

在arch/mips/kernel/vmlinux.lds这个链接脚本里面有如下一段代码

  1. __init_begin = .;
  2. . = ALIGN(4096); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }
  3. .init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_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) }
  4. . = ALIGN(4);

当然,关于链接脚本又有很多多动要讲。所以现在我们不关心里面的具体含义,我们可以观察到上面有这些字符串使我们比较熟悉的:__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)。链接脚本里的东西看似很乱很难,其实是非常有逻辑有规律可循的,我们来简单解释下面一行的代码作用

  1. __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)

其中__initcall6_start是一个符号,链接器用到的。__initcall6_start = .; ,其中的 '.'符号是对当前地址的一个引用,也就说把当前的地址给了符号__initcall6_start, *(.initcall6.init) *(.initcall6s.init) 的意思是所有的.initcall6.init段和.initcall6s.init段的内容从__initcall6_start为起始地址开始链接。

.initcall0.init .initcall0s.init .initcall1.init .initcall1s.init …… .initcall7.init .initcall7s.init

上面的内容都出现在了链接脚本中,而0,0s,1,1s,2,2s …… 6,6s,7,7s 有没有觉得在哪里见过? 我们回顾一下initcalls里面的定义

  1. #define pure_initcall(fn)       __define_initcall(fn, 0)
  2. #define core_initcall(fn)       __define_initcall(fn, 1)
  3. #define core_initcall_sync(fn)      __define_initcall(fn, 1s)
  4. #define postcore_initcall(fn)       __define_initcall(fn, 2)
  5. #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
  6. #define arch_initcall(fn)       __define_initcall(fn, 3)
  7. #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
  8. #define subsys_initcall(fn)     __define_initcall(fn, 4)
  9. #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
  10. #define fs_initcall(fn)         __define_initcall(fn, 5)
  11. #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
  12. #define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
  13. #define device_initcall(fn)     __define_initcall(fn, 6)
  14. #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
  15. #define late_initcall(fn)       __define_initcall(fn, 7)
  16. #define late_initcall_sync(fn)      __define_initcall(fn, 7s)

这里面就有0,0s,1,1s,2,2s …… 6,6s,7,7s,也就是__define_initcall(fn, id)中的第二个参数 id。很显然这个id的值不是我们在调用module_init的时候传过去的。数字id 0~7代表的是不同的优先级(0最高,module_init对应的优先级为6,所以一般我们注册的驱动程序优先级为6),链接脚本里面根据我们注册不同的id,将我们的函数fn放入对应的地址里面。根据上面的分析,test_init放在.initcall6.init段中。在kernel启动过程中,会调用do_initcalls函数一次调用我们通过xxx_initcall注册的各种函数,优先级高的先执行。所以我们通过module_init注册的函数在kernel启动的时候会被顺序执行。

linux内核驱动module_init解析(1)的更多相关文章

  1. linux内核驱动module_init解析(2)

    本文转载自博客http://blog.csdn.net/u013216061/article/details/72511653 如果了解过Linux操作系统启动流程,那么当bootloader加载完k ...

  2. linux 内核驱动--Platform Device和Platform_driver注册过程

    linux 内核驱动--Platform Device和Platform_driver注册过程 从 Linux 2.6 起引入了一套新的驱动管理和注册机制 :Platform_device 和 Pla ...

  3. Unix/Linux环境C编程新手教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建

    1. openSUSE是一款优秀的linux. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXRjYXN0Y3Bw/font/5a6L5L2T/font ...

  4. Unix/Linux环境C编程入门教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建

    1. openSUSE是一款优秀的linux. 2.选择默认虚拟机 3.选择稍后安装操作系统 4.选择linux  opensuse 5. 选择默认虚拟机名称 6.设置处理器为双核. 7.内存设置为2 ...

  5. 【Linux开发】Linux V4L2驱动架构解析与开发导引

    Linux V4L2驱动架构解析与开发导引 Andrew按:众所周知,linux中可以采用灵活的多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动在linux中 ...

  6. Linux内核驱动学习(八)GPIO驱动模拟输出PWM

    文章目录 前言 原理图 IO模拟输出PWM 设备树 驱动端 调试信息 实验结果 附录 前言 上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM.本篇的学习会将写一个驱动操 ...

  7. Linux内核驱动学习(六)GPIO之概览

    文章目录 前言 功能 如何使用 设备树 API 总结 前言 GPIO(General Purpose Input/Output)通用输入/输出接口,是十分灵活软件可编程的接口,功能强大,十分常用,SO ...

  8. linux内核驱动模型

    linux内核驱动模型,以2.6.32内核为例.(一边写一边看的,有点乱.) 1.以内核对象为基础.用kobject表示,相当于其它对象的基类,是构建linux驱动模型的关键.具有相同类型的内核对象构 ...

  9. 【引用】Linux 内核驱动--多点触摸接口

    本文转载自James<Linux 内核驱动--多点触摸接口>   译自:linux-2.6.31.14\Documentation\input\multi-touch-protocol.t ...

随机推荐

  1. 阶段3 3.SpringMVC·_07.SSM整合案例_08.ssm整合之Spring整合MyBatis框架

    service能供成功的调用到service对象就算是整合成功 如果能把生成的代理对象也存大IOC的容器中.那么ServiceImpl就可以拿到这个对象 做一个注入,然后就可以调用代理对象的查询数据库 ...

  2. linux常用命令(7)cp命令

    cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一.一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数.但是如果是 ...

  3. MFC中png格式图片贴图的实现

    MFC中png格式图片贴图的实现(2011-07-14 19:10:29)  ___转载自新浪 初学vc,正在做五子棋,五子棋中的图片格式都是bmp格式的,所以贴图用CBitmap可以很简单的实现.刚 ...

  4. 【VS开发】【电子电路技术】PCI与PCIE主板插卡与插槽识别

    一.PCI PCI接口分为32bit和64bit两种,32bit就是一般台式机使用的普通的pci接口(图一.图三),64bit接口比32bit接口长一些一般只出现在服务器上(图四.图五).32bit和 ...

  5. NIO 编程模型

    NIO 编程模型 Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的 ...

  6. Largest Number At Least Twice of Others

    In a given integer array nums, there is always exactly one largest element. Find whether the largest ...

  7. Exclusive Time of Functions

    On a single threaded CPU, we execute some functions.  Each function has a unique id between 0 and N- ...

  8. cut,sort,awk,sed,tr,find,wc,uniq在Linux中的用法

    cut语法cut [-bn] [file]cut [-c] [file]cut [-df] [file] -b :以字节为单位进行分割.这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志.-c ...

  9. stm32F429启动时钟配置

    STM32f429在启动时会在startup_stm32f429_439xx.s中调用static void SetSysClock(void)函数.默认使用的是25M晶振,把系统时钟设置为180M. ...

  10. Codeforces 1194C. From S To T

    传送门 首先贪心, $S$ 能和 $T$ 匹配就要尽量匹配,剩下的才让 $P$ 来补 在 $S$ 全部匹配上的情况下,看看 $P$ 是否有足够的字符即可 #include<iostream> ...