本文转载自博客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. EF Code First 学习笔记:约定配置 Data Annotations+Fluent API

    要更改EF中的默认配置有两个方法,一个是用Data Annotations(在命名空间System.ComponentModel.DataAnnotations;),直接作用于类的属性上面;还有一个就 ...

  2. C#字符串内插-$

    1.字符串内插 $特殊字符将字符串文本标识未内插字符串,可能包含内插表达式的字符串文本. 将内插字符串解析为结果字符串,带有内插表达式的项会替换为表达式结果的字符串表示形式. 在C#6级更高版本语言中 ...

  3. list 属性字段直接转成字符串数组

    List<Car> cars = //whatever; string concat = String.Join(",", cars.Select(c => c. ...

  4. 利用fiddler+nginx模拟流量识别与转发

    最近看到一些关于全链路压测的文章,全链路压测主要处理以下问题: 数据清洗压测流量标记,识别 压测流量标记的传递测试数据与线上数据隔离等等... 要实现全链路压测,必然要对原有的业务系统进行升级,要怎么 ...

  5. (转)arcengine+c# 修改存储在文件地理数据库中的ITable类型的表格中的某一列数据,逐行修改。更新属性表、修改属性表某列的值。

    作为一只菜鸟,研究了一个上午+一个下午,才把属性表的更新修改搞了出来,记录一下: 我的需求是: 已经在文件地理数据库中存放了一个ITable类型的表(不是要素类FeatureClass),注意不是要素 ...

  6. yum安装telnet如何开启telnet服务

    1.# yum  install  -y  telnet  telnet-server  xinetd   (其中telnet-server是由xinetd管理的,所以得下载xinetd服务) 2.修 ...

  7. 关联SecureCRT

    建议安装SecureCRT 8.1及其以上版本,打开http://EVE-NG地址/files/windows.zip下载eve-ng的Windows关联文件,然后使用文本编辑工具编辑win7_64b ...

  8. redhat 5 中文乱码

    安装 1.fonts-chinese-3.02-9.6.el5.noarch.rpm. 如果无法安装,则加个--force 2.fonts-ISO8859-2-75dpi-1.0-17.1.noarc ...

  9. Unity打包出来的App 跳转其他 App

    void Start()//在android测试成功 { bool fail = false; string bundleId = "跳转的包名";//包名称 AndroidJav ...

  10. 区间最值的优秀数据结构---ST表

    ST表,听起来高大上,实际上限制非常多,仅仅可以求最值问题: 为什么?先从原理看起: st表运用了倍增的思想:st[i][j] = min(st[i][j - 1],st[i + 2^(j - 1)) ...