module_init这个函数对做驱动的人来说肯定很熟悉,这篇文章用来跟一下这个函数的实现。

在include/linux/init.h里面有module_init的定义,自然,因为一个module可以在内核启动时自动加载进内核,也可以由我们手动在需要时加载进内核,基于这种场景,内核使用了MODULE这个宏,见代码:

#ifndef MODULE

#ifndef __ASSEMBLY__

...

#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn #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) #define __initcall(fn) device_initcall(fn) #define module_init(x) __initcall(x); #else /* MODULE */ ... #define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
...

当我们使用make menuconfig来配置内核时,将某个module配置为m时,MODULE这个宏就被定义了,而当配置为y时,则没有定义,具体的实现在kernel的根Makefile(-DMODULE)里。

现在我们先看下第一种情况,即把module配置为m的情况,即else分支的代码。

先看下initcall_t的定义:

typedef int (*initcall_t)(void);

它是一个接收参数为void, 返回值为int类型的函数指针。这样就明白了,其实前两句话只是做了一个检测,当你传进来的函数指针的参数和返回值与initcall_t不一致时,就会有告警。
重点在第三句,是使用alias将initfn变名为init_module,我们知道,kernel 2.4版本之前都是用init_module来加载模块的。这样做应该是为了不用修改load module的那块代码吧。

当我们调用insmod将module加载进内核时,会去找init_module作为入口地址,即是我们的initfn, 这样module就被加载了。

取nvme.ko为例,我们可以通过objdump -t nvme.ko 查看该模块的符号表,发现init_module和nvme_init指向同一个偏移量。如下:

现在看第二种情况,即我们选择将模块编进内核,让它随内核启动而加载。

这种情况下module_init最终会调用__define_initcall宏,这个宏的作用就是将我们的初始化函数放在".initcall" level ".init"中。

在这里是.initcall6.init, 它的位置可以在Vmlinux.lds.h里面找到:

#define INITCALLS                            \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)

而INITCALL可以在vmlinux.lds.S里面找到:

.init.text : AT(ADDR(.init.text) - LOAD_OFFSET) {
__init_begin = .;
_sinittext = .;
*(.init.text)
_einittext = .;
}
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { *(.init.data) }
. = ALIGN();
.init.setup : AT(ADDR(.init.setup) - LOAD_OFFSET) {
__setup_start = .;
*(.init.setup)
__setup_end = .;
}
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end = .;
}
.con_initcall.init : AT(ADDR(.con_initcall.init) - LOAD_OFFSET) {
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
}

上面贴出来的代码是系统启动时存放初始化数据的地方,执行完成后不再需要,会被释放掉。根据上面的内存布局,可以列出初始化宏和内存的对应关系:

_init_begin              -------------------

                        |  .init.text       | ---- __init

                        |-------------------|

                        |  .init.data       | ---- __initdata

_setup_start       |-------------------|

                        |  .init.setup      | ---- __setup_param

__initcall_start   |-------------------|

                        |  .initcall1.init  | ---- core_initcall

                        |-------------------|

                        |  .initcall2.init  | ---- postcore_initcall

                        |-------------------|

                        |  .initcall3.init  | ---- arch_initcall

                        |-------------------|

                        |  .initcall4.init  | ---- subsys_initcall

                        |-------------------|

                        |  .initcall5.init  | ---- fs_initcall

                        |-------------------|

                        |  .initcall6.init  | ---- device_initcall

                        |-------------------|

                        |  .initcall7.init  | ---- late_initcall

__initcall_end    |-------------------|

                        |                   |

                        |    ... ... ...    |

                        |                   |

__init_end              -------------------

而各个initcall被调用的地方在kernel_init-》do_basic_setup-》do_initcalls里面:

static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count(); for (call = __initcall_start; call < __initcall_end; call++) {
ktime_t t0, t1, delta;
char *msg = NULL;
char msgbuf[];
int result; if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
t0 = ktime_get();
} result = (*call)();
...
}

module_init解析及内核initcall的初始化顺序的更多相关文章

  1. Linux内核很吊之 module_init解析 (下)【转】

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

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

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

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

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

  4. Java中类的初始化顺序

    一.一个类的初始化顺序(没继承情况)  规则: 1.静态变量>普通变量>构造方法   2.变量定义的顺序决定初始化的顺序 3.静态变量和静态块是一样的,普通变量和非静态块是一样的,即能够把 ...

  5. 【转】两道面试题,带你解析Java类加载机制(类初始化方法 和 对象初始化方法)

    本文转自 https://www.cnblogs.com/chanshuyi/p/the_java_class_load_mechamism.html 关键语句 我们只知道有一个构造方法,但实际上Ja ...

  6. C++从静态对象的初始化顺序理解static关键字

    问题 首先考虑一个全局变量的初始化顺序问题 在头文件1中: extern int b; ; 在头文件2中: extern int a; ; 源文件中包含了头文件1和头文件2,这种情况下a和b可能的值是 ...

  7. Java类继承关系中的初始化顺序

    Java类初始化的顺序经常让人犯迷糊,现在本文尝试着从JVM的角度,对Java非继承和继承关系中类的初始化顺序进行试验,尝试给出JVM角度的解释. 非继承关系中的初始化顺序 对于非继承关系,主类Ini ...

  8. Java杂谈3——类加载机制与初始化顺序

    Java语言的哲学:一切都是对象.对于Java虚拟机而言,一个普通的Java类同样是一个对象,那如果是对象,必然有它的初始化过程.一个类在JVM中被实例化成一个对象,需要经历三个过程:加载.链接和初始 ...

  9. spring初始化顺序

    首先,Spring bean的默认加载顺序是怎么控制的 工程中有2个bean,A和B,其中必须先初始化A再初始化B,但是没有depend-on或者Order等方式去保证,只不过恰好刚好这么运行着没出事 ...

随机推荐

  1. HMC5883L地磁传感器驱动

    霍尼韦尔 HMC5883L 是一种表面贴装的高集成模块,并带有数字接口的弱磁传感器芯片,应用于低成本罗盘和磁场检测领域.HMC5883L 包括最先进的高分辨率 HMC118X 系列磁阻传感器,并附带霍 ...

  2. 一个php开发的用于路由器的小功能

    最近接到一个需求,假设有A.B.C 三台主机.现A主机要访问C主机上的一个脚本,并且根据A传递的参数给C主机,同时接受C主机返回来的数据.但是现在A主机不能直接通过url.IP访问C主机,需要借由主机 ...

  3. 安装pip工具

    Python 2.7.9+ and 3.4+ Good news! Python 3.4 (released March 2014) and Python 2.7.9 (released Decemb ...

  4. UVa 750 - 8 Queens Chess Problem

    题目大意:八皇后问题,在一个8*8的棋盘上,放置8个皇后,使得任意两个皇后不在同一行上.不在同一列上.不在同一条对角线上,不过这道题预先给定了一个位置放置一个皇后,让你输出所有可能的答案. 经典的回溯 ...

  5. Mysql 多列形成主键(复合主键 )

    什么是数据表的复合主键 所谓的复合主键 就是指你表的主键含有一个以上的字段组成 比如 create table test (    name varchar(19),    id number,    ...

  6. 包含中文字符的NSString 转换为NSURL

    转包含中文字符的NSString 转换为NSURL NSString中如果包括中文字符的话转换为NSURL得到的值为nil,在网上搜了下,用stringByAddingPercentEscapesUs ...

  7. DevExpress控件学习总结2(转)

    1.TextEditor(barEditItem)取文本string editValue = barEditItem1.EditValue.ToString(); //错误,返回null string ...

  8. IE6-能让png图片有透明效果的js代码

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  9. 冒泡排序法-java案例详解

    /** * 功能:冒泡排序法 * 思想:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的排序码, * ,若发现逆序这交换,使得排序码较小的元素逐渐从后部移向前部(从下标较大的单元移 ...

  10. [QT]简单介绍一下 *.pro、*.pri、*.prf、*.prl等四种文件

    转自:http://blog.csdn.net/dbzhang800/article/details/6348432 简单介绍一下 *.pro.*.pri.*.prf.*.prl等四种文件:干嘛用的, ...