(原)Module_init(x)和module_exit(x)分析

//此文档主要是分析我们在内核驱动中经常打交道的module_init和module_exit函数调用。

//编写人:lihaiping-2013-08-08

驱动编写的时候,我们在很多编写驱动的文件中会看到和用到module_init()和module_exit()函数,

那么经过这两个函数封装的函数以后,它是怎么样被系统调用的呢?

在系统中,我们一起来探索一下内核源码中对这两个函数的定义。

 

在3.0.8kernel中,我自己查询了一下源码,它的整个定义过程如下:

#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_exit函数了

#define module_exit(x) __exitcall(x);

然后

#define __exitcall(fn) \

static exitcall_t __exitcall_##fn __exit_call = fn

然后

#define __exit_call __used __section(.exitcall.exit)

 

所以经过上面两个宏定义,我们可以知道,它是通过这两个函数把我们需要的封装的函数放在

一个属性的段里面去,然后系统会调用这两个段里面的所有函数。

对于module_iniit(x)封装的函数,他的段属性是.initcall6.init的段属性。

那么这个段它是在哪里进行定义的呢?

我们打开链接用的vmlinux.lds.h文件,这个里面定义了一些我们用到的东西。

下面一起来找一下我们需要的相关内容。

在vmlinux.lds.h我们可以看到有这个一个宏定义,它用到了我们上面的.initcall6.init

源码如下:

#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)

然后在他们紧接着的这个宏定义下面,又有一个宏定义

它是INIT_CALLS的宏定义,它又把我们上面包含.initcall6.init这个段的宏INITCALLS

源码如下:

#define INIT_CALLS \

VMLINUX_SYMBOL(__initcall_start) = .; \

INITCALLS \

VMLINUX_SYMBOL(__initcall_end) = .;

从这里我们就可以看出,它的关系是一层紧接着一层的。那么要怎么样才能打开谜底呢?

知道它到底是怎么样被调用的?又是在哪个函数里面被调用的?

这个时候我们很难往下面分析的时候,我们就需要从系统开始的时候进行分析。

首先从start_kernel函数开始,一层一层往下面看,它的整个调用关系如下

贯穿整个内核的调用关系如下:

start_kernel

rest_init

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

kernel_init

do_basic_setup();

driver_init();

do_initcalls();

 

 

在这个调用关系里面,我把do_basic_setup的函数源码贴了出来,如下:

static void __init do_basic_setup(void)

{

cpuset_init_smp();

usermodehelper_init();

init_tmpfs();

driver_init();

init_irq_proc();

do_ctors();

do_initcalls();

}

然后在do_basic_setup函数里面它调用了driver_init函数来进行一些驱动的初始化

void __init driver_init(void)

{

/* These are the core pieces */

devtmpfs_init();

devices_init();

buses_init();

classes_init();

firmware_init();

hypervisor_init();

 

/* These are also core pieces, but must come after the

 * core core pieces.

 */

platform_bus_init();

system_bus_init();

cpu_dev_init();

memory_dev_init();

}

也许有些人会和我一样产生错觉,以为我们所有的写的驱动函数是在driver_init函数里面被调用的。

但是在看到源码以后,你就明白了,它不是我们想的那样,这个函数里面,它只是给我们初始化了一些

我们编写驱动常用的架构相关的东西,例如platform_bus_init的被调用,等等。

那么到底是哪个函数被调用的呢?我翻阅了一下前面的调用函数,从名字来分析,很有可能是这个函数,

do_initcalls,所以我就去分析了一下它的源码,这里我们把它的源码贴出来一下,源码如下:

static void __init do_initcalls(void)

{

initcall_t *fn;

 

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

do_one_initcall(*fn);

}

其中在这个函数的上面,有这样的一条声明语句:

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

也许看了do_initcalls函数的源码,以及结合我们前面在vmlinux.lds.h文件中里面的几个宏定义,你就会明白了,

其实这个函数的作用,它就是把INIT_CALLS宏定义下面的段属性里面的函数,

从__early_initcall_end的位置开始到__initcall_end的位置中间的所有函数,全部调用一次。

所以分析到这里,你应该明白我们编写驱动的时候,驱动推入和推出函数的整个调用过程了吧?

 

 

 

 

posted @ 2013-08-08 15:06  lihaiping  阅读(768)  评论(0)    收藏  举报