(原)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的位置中间的所有函数,全部调用一次。
所以分析到这里,你应该明白我们编写驱动的时候,驱动推入和推出函数的整个调用过程了吧?

浙公网安备 33010602011771号