主要分析do_basic_setup函数里面的do_initcalls()函数,这个函数用来调用所有编译内核的驱动模块中的初始化函数。

static void __init do_initcalls(void)
{
initcall_t *call; for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call); /* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

__early_initcall_end 与 __initcall_end之间有

#define INITCALLS                           \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) = .; \
*(.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)

do_initcalls函数完成对*initcall0-7*的函数的调用工作,遍历所有的模块,当然各个函数的初始化工作有他们自身的函数完成了。

在include/linux/init.h文件中有引用到".initcall" level ".init"的地方

/* 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(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn

然后调用__define_initcall的地方还是在init.h文件中

/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall("early",fn,early) /*
* 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.
*/
#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)

很多子系统比如PCI, USB都会有一个名为subsys_initcall的入口,不过更多的是使用module_init函数。

module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj-y)。

1. 在MODULE没有定义这种情况下,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(x)最终展开为:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:
static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。
这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds及do_initcalls,那么不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。
如果MODULE这个宏没有定义,基本上表明该模块是要编译进内核的(obj-y)。所以module_exit(x)此时最终会被忽略掉,因为编译进入内核的模块是不需要进行清理工作的。

2. 在MODULE被定义的情况下(大部分可动态加载的driver模块都属于此, obj-m)

module_init定义如下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用sys_init_module()去找到init_module函数的入口地址。
如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调用。

linux内核启动分析(3)的更多相关文章

  1. Linux内核启动分析过程-《Linux内核分析》week3作业

    环境搭建 环境的搭建参考课件,主要就是编译内核源码和生成镜像 start_kernel 从start_kernel开始,才真正进入了Linux内核的启动过程.我们可以把start_kernel看做平时 ...

  2. Linux内核启动分析

    张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlo ...

  3. linux内核启动分析(2)

    -----以下内容为从网络上整理所得------ 主要介绍kernel_init线程(函数),这个线程在rest_init函数中被创建,kernel_init函数将完成设备驱动程序的初始化,并调用in ...

  4. Linux内核启动分析笔记

    一.驱动加载 1.驱动加载调用关系 start_kernel //init/main.c rest_init //最后执行它 kernel_init //使用kernel_thread创建一个进程执行 ...

  5. 通过从代码层面分析Linux内核启动来探知操作系统的启动过程

    通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...

  6. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  7. Linux内核及分析 第三周 Linux内核的启动过程

    实验过程: 打开shell终端,执行以下命令: cd LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootf ...

  8. Linux内核启动流程分析(一)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...

  9. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

随机推荐

  1. MATLAN中矩阵表示中有一维是~表示的意思

    [~,m]表示的意思解释. [~,m]=rat(12/34) m = 17 >> [x,m]=rat(12/34) x = 6 m = 17

  2. RT-thread-2.0.1移植(基于STM32F4xx)

    1.将下载的rt-thread-2.0.1解压后,得到如下图所示的文件列表. 在bsp目录下可以找到stm32f40x文件夹,这文件夹里面包括了库函数,其他芯片平台的文件夹统统删掉.在libcpu下, ...

  3. FZU 1492 地震预测(链表)

    实际上把数组排序一遍加入链表中,再记录好数组原来的数在链表中的位置.我们只需要维护链表的删除操作就可以了. # include <cstdio> # include <cstring ...

  4. 【刷题】BZOJ 1717 [Usaco2006 Dec]Milk Patterns 产奶的模式

    Description 农夫John发现他的奶牛产奶的质量一直在变动.经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠.我们称之为一个"模式". ...

  5. [AHOI2009]最小割 最小割可行边&必须边

    ~~~题面~~~ 题解: 做这题的时候才知道有最小割可行边和必须边这种东西..... 1,最小割可行边, 意思就是最小割中可能出现的边. 充要条件: 1,满流 2,在残余网络中找不到x ---> ...

  6. Android 常用控件自定义样式RadioButton、CheckBox、ProgressBar、

    一.RadioButton / CheckBox 系统自带的RadioButton/CheckBox的样式,注定满足不了实际运用中的情况,有时候自定义自己的样式:此次把自己中工作学习过程中所学到的东西 ...

  7. 洛谷3690:【模板】Link Cut Tree——题解

    https://www.luogu.org/problemnew/show/P3690 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...

  8. android eclipse ndk使用记录

    为方便开发jni程序,android提供了ndk包来简化开发过程,避免开发人员下载完整的平台代码,并且可以在windows环境下集成到eclipse里面,大大加快了开发速度.这里记录下一个简单例子. ...

  9. HDOJ(HDU).1015 Safecracker (DFS)

    HDOJ(HDU).1015 Safecracker [从零开始DFS(2)] 从零开始DFS HDOJ.1342 Lotto [从零开始DFS(0)] - DFS思想与框架/双重DFS HDOJ.1 ...

  10. SRM16 B-2(DP)

    老鼠和洞按坐标排序 f[i][j]表示前i个洞进j只老鼠的最短距离 比赛的时候强行分三类去推式子,推是推出来了,也看出来是可以用三个单调队列去优化的,但是太繁琐了,要我敲我真没办法T^T 赛后经 on ...