在前面helloworld的编写里面,我们使用了两个宏分别是module_init和module_exit,这里分析下为什么使用这两个宏。

在写模块的时候有两个特殊的函数,分别是init_module和cleanup_module,这两个函数分别在insmod的时候和rmmod的时候调用,并且insmod和rmmod只识别这两个特殊的函数,可是我们前面的例子里面并没有这两个函数。怎么会这样呢,那就必须得说说module_init/module_exit了。

一个驱动可以作为一个模块动态的加载到内核里,也可以作为内核的一部分静态的编译进内核,module_init/module_exit也就有了两个含义:

一、动态编译成模块

在内核里有如下定义:

        /* 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有两个含义:

1、验证加载函数的格式

static inline initcall_t __inittest(void) \

{ return initfn; }

这个函数的作用是验证我们穿过来的加载函数格式是否正确,linux内核规定加载函数的的原型是:

typedef int (*initcall_t)(void);

所以我们写加载函数的时候必须是返回值为int参数为void的函数,这个在内核里要求比较严格,所以我们写加载函数的时候必须按照这个约定。

2、定义别名

int init_module(void) __attribute__((alias(#initfn)));

这段代码的作用是给我们的加载函数定义一个别名,别名就是我们前面提到的init_module,这样insmod就能够执行我们的加载函数了。

module_exit的作用和module_init一样,同样也是验证函数格式和定义别名。

二、静态编译

在静态编译的时候module_init的定义如下:
        #define module_init(x) __initcall(x);
        #define __initcall(fn) device_initcall(fn)
        #define device_initcall(fn) __define_initcall("6",fn,6)
        #define __define_initcall(level,fn,id) \
                static initcall_t __initcall_##fn##id __used \
                __attribute__((__section__(".initcall" level ".init"))) = fn

通过这些段代码,我们能够看出最终的结果是将我们的使用module_init修饰的函数指针链接到一个叫.initcall的段里,也就是说最终所以的使用module_init修饰的函数指针都被链接在这个段里,最终内核在启动的时候顺序调用所有链接在这个段里的函数,实现设备的初始化。

module_exit在静态编译的时候没有意义,因为静态编译的驱动无法卸载!

显然 对动态加载的模块是无效的;

Init.h中有相关initcall的启动次序,在system.map中可看出具体的__initcall指针的前后次序

#define pure_initcall(fn) __define_initcall("0",fn,0)

#define core_initcall(fn) __define_initcall("1",fn,1)

#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)

#define postcore_initcall(fn) __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

#define arch_initcall(fn) __define_initcall("3",fn,3)

#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)

#define subsys_initcall(fn) __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#define fs_initcall(fn) __define_initcall("5",fn,5)

#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn) __define_initcall("6",fn,6)

#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

#define late_initcall(fn) __define_initcall("7",fn,7)

#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)

module_init在的启动序号为6,它的展开后就是__define_initcall("6",fn,6)

#definedevice_initcall(fn) __define_initcall("6",fn,6)

#define__initcall(fn) device_initcall(fn)

#definemodule_init(x) __initcall(x);

Kernel通过调用do_initcalls(void)加载模块,具体流程如下图:

static void__init do_initcalls(void)

{

initcall_t*fn;

for (fn =__early_initcall_end; fn < __initcall_end; fn++)

do_one_initcall(*fn);

/* Makesure there is no pending stuff from the initcall sequence */

flush_scheduled_work();

}

因此驱动模块在Kernel启动过程中的启动次序是非常靠后的

具体的每个驱动的启动次序可以从system.map看出,特别对于同一个优先级的各类驱动:

c003288ct __initcall_i2c_init2

c00328b0 t__initcall_video_early_init3

c00328b4 t__initcall_video2_early_init3

c00328b8t __initcall_aml_i2c_init3

c0032c18t __initcall_i2c_dev_init6

c0032c28 t__initcall_videodev_init6

c0032c30t __initcall_v4l2_i2c_drv_init6

c0032c34t __initcall_v4l2_i2c_drv_init6

c0032d24 t__initcall_video_init6

c0032d28 t__initcall_video2_init6

对于同一级别的 __initcall的次序 主要由MakeFile中.o文件的链接次序决定,具体看Kernel下的主Makefile ---- Build vmlinux

以及kernel/driver 下的obj-y

/* end */

⭐驱动之module_init/module_exit与系统启动关系的更多相关文章

  1. 驱动之module_init/module_exit

    在前面helloworld的编写里面,我们使用了两个宏分别是module_init和module_exit,这里分析下为什么使用这两个宏. 在写模块的时候有两个特殊的函数,分别是init_module ...

  2. linux驱动 之 module_init解析 (上)【转】

    转自:https://blog.csdn.net/Richard_LiuJH/article/details/45669207 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...

  3. module_init module_exit

    像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:#include <linux/init.h>#inc ...

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

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

  5. linux总线、设备和设备驱动的关系

    之一:bus_type 总线是处理器和一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部的虚拟"platform"总线.可以通过ls -l /sys/bu ...

  6. linux驱动工程面试必问知识点

    linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...

  7. platform总线驱动代码分析

    /************************************************************************/ Linux内核版本:2.6.35.7 运行平台:三 ...

  8. 【Linux开发】【DSP开发】Linux设备驱动之——PCI 总线

    PCI总线概述  随着通用处理器和嵌入式技术的迅猛发展,越来越多的电子设备需要由处理器控制.目前大多数CPU和外部设备都会提供PCI总线的接口,PCI总线已成为计算机系统中一种应用广泛.通用的总线标准 ...

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

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

随机推荐

  1. Maven常用参数及其说明【转:http://blog.csdn.net/wangjunjun2008/article/details/18982089】

    Maven常用参数及其说明 -h,--help                              Display help information-am,--also-make         ...

  2. SQL Server中的@@ROWCOUNT

    SQL Server中@@ROWCOUNT返回受上一语句影响的行数,返回值类型为 int 整型. 如果行数大于 20 亿,则需要使用 ROWCOUNT_BIG. @@ROWCOUNT和@@ERROR变 ...

  3. ElasticSearch分词器

    什么是分词器? 分词器,是将用户输入的一段文本,分析成符合逻辑的一种工具.到目前为止呢,分词器没有办法做到完全的符合人们的要求.和我们有关的分词器有英文的和中文的.英文的分词器过程:输入文本-关键词切 ...

  4. echarts3样例

    <script type="text/javascript" src="echarts.min.js"></script> <di ...

  5. Fabrice Bellard其人 ---- FFMPEG及其他……

    有些计算机科学家的名字耳熟能详:阿兰·图灵(Alan Turing).高纳德(Donald Knuth).艾兹赫尔·戴克斯特拉(Edsger Dijkstra),这些人的名气甚至大于他们突破性的成就. ...

  6. Python入门--12--函数与变量

    python只有函数没有过程 函数有运行完了之后会有返回值.过程没有 def back(): return 1,'gg',2 #会有返回值(1,'gg',2) #注意默认返回元祖 一.全局和局部变量 ...

  7. Python 基础教程之包和类的用法

    Python 基础教程之包和类的用法 建立一个文件夹filePackage 在filePackage 文件夹内创建 __init__.py 有了 __init__.py ,filePackage才算是 ...

  8. [React] Persist Form Data in React and Formik with formik-persist

    It can be incredibly frustrating to spend a few minutes filling out a form only to accidentally lose ...

  9. python 学习笔记 13 -- 经常使用的时间模块之time

    Python 没有包括相应日期和时间的内置类型.只是提供了3个相应的模块,能够採用多种表示管理日期和时间值: *    time 模块由底层C库提供与时间相关的函数.它包括一些函数用于获取时钟时间和处 ...

  10. 封装算法: 模板方法(Template Method)模式

    template method(模板方法)模式是一种行为型设计模式.它在一个方法中定义了算法的骨架(这种方法被称为template method.模板方法),并将算法的详细步骤放到子类中去实现.tem ...