linux内核驱动module_init解析(1)
本文转载自博客http://blog.csdn.net/richard_liujh/article/details/45669207
写过linux驱动的程序猿都知道module_init() 这个函数。那么我们来了解一下module_init这个函数的具体功能和执行过程
在kernel源码目录中找到include\linux\init.h文件
- <span style="font-family:SimSun;font-size:14px;">/**
- * module_init() - driver initialization entry point
- * @x: function to be run at kernel boot time or module insertion
- *
- * module_init() will either be called during do_initcalls() (if
- * builtin) or at module insertion time (if a module). There can only
- * be one per module.
- */
- #define module_init(x) __initcall(x);</span>
这里面就有对module_init 的定义,我们发现module_init(x)是一个宏定义,那么_initcall(x)又是什么呢?
- #define __initcall(fn) device_initcall(fn)
感觉怪怪的,怎么这么多的宏??再解释这个之前,我们再来看看更多的宏定义吧
完整的宏定义如下:
__define_initcall:
- /* initcalls are now grouped by functionality into separate
- * subsections. Ordering inside the subsections is determined
- * by link order.
- * For backwards compatibility, initcall() puts the call in
- * the device init subsection.
- *
- * The `id' arg to __define_initcall() is needed so that multiple initcalls
- * can point at the same handler without causing duplicate-symbol build errors.
- */
- #define __define_initcall(fn, id) \
- static initcall_t __initcall_##fn##id __used \
- __attribute__((__section__(".initcall" #id ".init"))) = fn
initcalls:
- </pre><pre name="code" class="cpp">/
- * 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)
- #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 __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
- static void __init do_initcalls(void)
- {
- int level;
- for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
- do_initcall_level(level);
- }
很明显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)
- #define __define_initcall(fn, id) \
- static initcall_t __initcall_##fn##id __used \
- __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”
|
是不是看起来更加头疼,那么我们说简单一点。通过__attribute__(__section__)设置函数属性,也就是将test_init放在.initcall6.init段中。这个段在哪用?这就要涉及到链接脚本了。
大家可以到kernel目录arch中,根据自己的处理器平台找到对应的链接脚本。例如我现在的平台是君正m200(mips架构),可能大部分是arm架构。
在arch/mips/kernel/vmlinux.lds这个链接脚本里面有如下一段代码
- __init_begin = .;
- . = ALIGN(4096); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }
- .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) }
- . = ALIGN(4);
当然,关于链接脚本又有很多多动要讲。所以现在我们不关心里面的具体含义,我们可以观察到上面有这些字符串使我们比较熟悉的:__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)。链接脚本里的东西看似很乱很难,其实是非常有逻辑有规律可循的,我们来简单解释下面一行的代码作用
- __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里面的定义
- #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)
- #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)
这里面就有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)的更多相关文章
- linux内核驱动module_init解析(2)
本文转载自博客http://blog.csdn.net/u013216061/article/details/72511653 如果了解过Linux操作系统启动流程,那么当bootloader加载完k ...
- linux 内核驱动--Platform Device和Platform_driver注册过程
linux 内核驱动--Platform Device和Platform_driver注册过程 从 Linux 2.6 起引入了一套新的驱动管理和注册机制 :Platform_device 和 Pla ...
- Unix/Linux环境C编程新手教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建
1. openSUSE是一款优秀的linux. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXRjYXN0Y3Bw/font/5a6L5L2T/font ...
- Unix/Linux环境C编程入门教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建
1. openSUSE是一款优秀的linux. 2.选择默认虚拟机 3.选择稍后安装操作系统 4.选择linux opensuse 5. 选择默认虚拟机名称 6.设置处理器为双核. 7.内存设置为2 ...
- 【Linux开发】Linux V4L2驱动架构解析与开发导引
Linux V4L2驱动架构解析与开发导引 Andrew按:众所周知,linux中可以采用灵活的多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动在linux中 ...
- Linux内核驱动学习(八)GPIO驱动模拟输出PWM
文章目录 前言 原理图 IO模拟输出PWM 设备树 驱动端 调试信息 实验结果 附录 前言 上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM.本篇的学习会将写一个驱动操 ...
- Linux内核驱动学习(六)GPIO之概览
文章目录 前言 功能 如何使用 设备树 API 总结 前言 GPIO(General Purpose Input/Output)通用输入/输出接口,是十分灵活软件可编程的接口,功能强大,十分常用,SO ...
- linux内核驱动模型
linux内核驱动模型,以2.6.32内核为例.(一边写一边看的,有点乱.) 1.以内核对象为基础.用kobject表示,相当于其它对象的基类,是构建linux驱动模型的关键.具有相同类型的内核对象构 ...
- 【引用】Linux 内核驱动--多点触摸接口
本文转载自James<Linux 内核驱动--多点触摸接口> 译自:linux-2.6.31.14\Documentation\input\multi-touch-protocol.t ...
随机推荐
- 阶段3 3.SpringMVC·_03.SpringMVC常用注解_3 PathVariable注解
请求地址都一样,根据不同的请求方式,最终让不同的方法去执行.这就是restfull的风格 如果有两个查询都是get的.那么当前访问 满足条件的就是两个. 用一个占位符 {id} 演示PathVaria ...
- Java 操作Word表格
本文将对如何在Java程序中操作Word表格作进一步介绍.操作要点包括 如何在Word中创建嵌套表格. 对已有表格添加行或者列 复制已有表格中的指定行或者列 对跨页的表格可设置是否禁止跨页断行 创建表 ...
- Python爬虫学习==>第二章:MongoDB环境配置
学习目的: MongoDB的安装 正式步骤 (VMWare 虚拟机上无法安装这个MongoDB的自启动服务,如果你能办到,请多赐教) Step1:MongoDB的简介 MongoDB是一个基于分布式文 ...
- 小程序请求豆瓣API报403解决方法
微信小程序使用wx.request API请求豆瓣公开api的时候,会报一个403(Forbidden)的错误.这是为什么呢?是由于来自小程序的调用过多,豆瓣来自于小程序的调用被禁止.这里收集以下三种 ...
- Interceptors - 拦截器
1.概述 Flume有能力在运行阶段修改/删除Event,这是通过拦截器(Interceptors)来实现的. 拦截器需要实现org.apache.flume.interceptor.Intercep ...
- 统计学习方法 | 第3章 k邻近法
第3章 k近邻法 1.近邻法是基本且简单的分类与回归方法.近邻法的基本做法是:对给定的训练实例点和输入实例点,首先确定输入实例点的个最近邻训练实例点,然后利用这个训练实例点的类的多数来预测输入实例 ...
- git clone 报错 fatal: protocol 'https' is not supported 解决办法
版本:git 2.22.0 系统:win7旗舰版 先把https去掉 再把https加上 神奇的事情出现了,这样就可以了. 很多人都说这样解决了,原因不知道. Administrator@BWE8QX ...
- 平衡树(Splay、fhq Treap)
Splay Splay(伸展树)是一种二叉搜索树. 其复杂度为均摊\(O(n\log n)\),所以并不可以可持久化. Splay的核心操作有两个:rotate和splay. pushup: 上传信息 ...
- js、jQuery各种高度
height.js ```$(document).height(); //整个网页的高度 $(window).height(); //浏览器可视窗口的高度 $(window).scrollTop(); ...
- WPF最小化窗体后激活函数显示不了窗体
WPF最小化窗体后激活函数显示不了窗体 今天测试小哥给我提了一些问题,其中一个问题是这样的,点击web端的一个链接,是能启动本地的一个应用程序的,如果本地应用程序已启动(通过tcp进程间通信),那么应 ...