linux内核中的xx_initcall和module_init实现机制(linux3.1.0)

来源:互联网 发布:雷蒙德钱德勒 知乎 编辑:程序博客网 时间:2024/06/01 09:58

在内核代码中,arch_initcall 、device_initcall、module_init等经常遇到,本文分析其实现机制。

1 include/linux/init.h 下的相关定义

#ifndef MODULE#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()

#define module_init(x)__initcall(x);#define __initcall(fn) device_initcall(fn)
所以module_init(x)和device_initcall(fn)是等价的

 __define_initcall(level,fn,id)
level从0到7表示优先级,也就是pure_initcall() 最先被调用,late_initcall()最后被调用。调用的先后顺序

2 分析 __define_initcall宏定义

我们知道,以上所有的定义最后都转化为了 __define_initcall(level,fn,id),那么就来分析这个宏。

#define __define_initcall(level,fn,id) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(".initcall" level ".init"))) = fntypedef int (*initcall_t)(void);

从一上定义可知:

1 定义一个initcall_t类型函数指针

2 函数指针初值为fn

3  函数指针段属性为(".initcall" level ".init")

以arch_initcall(customize_machine) 为例,可以转化为:

__define_initcall("3",customize_machine,3)static initcall_t __initcall_customize_machine3  __used \__attribute__((__section__(".initcall3.init"))) = customize_machine

1 定义一个函数指针 __initcall_customize_machine3
2 函数指针初始化为customize_machine
3 这个函数指针的属性是.initcall3.init

3 __section__(".initcall" level ".init")段属性定义

/include/asm-generic/vmlinux.lds.h中定义段属性

#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)#define INIT_CALLS\VMLINUX_SYMBOL(__initcall_start) = .;\INITCALLS\VMLINUX_SYMBOL(__initcall_end) = .;

4 调用

其实,在linux内核中,有一个技巧是经常用到的,就是把某些具有类似特性的指针、函数、结构体等

放链接的时候放到同一个段,然后调用的时候到这个段中去遍历,或者去寻找匹配项。这里就是如此。

static void __init do_initcalls(void){initcall_t *fn;for (fn = __early_initcall_end; fn < __initcall_end; fn++)do_one_initcall(*fn);}

__early_initcall_end 在__initcall_start之后,在*(.initcall0.init)之前。

do_initcalls(void)就是到这个段中去遍历fn,然后调用do_one_initcall(*fn),do_one_initcall(*fn)调用fn。

5 调用过程

现在已经知道如何被调用的了,来看看何时被调用。整个调用过程:

/init/main.c start_kernel ->rest_init()->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)
->kernel_init->do_basic_setup()->do_initcalls()->do_one_initcall(initcall_t fn)->fn()
以上所有函数均在/init/main.c中

看到这里应该已经明白了xx_initcall()的实现机制。但是有个前提,是在静态编译内核

的时候是这样的。如果你看的够仔细的话,应该注意到在第一部分include/linux/init.h 下的

相关定义第一行是#ifndef MODULE。

所谓静态编译,就是在make menuconfig时候,选择 *,也就是yes,模块直接编译进了

内核。


动态编译

当你希望这个模块可以用insmod动态加载的时候,可以选择 M ,也就是只编译成模块,

不编译进内核,此时就定义了MODULE。MODULE参数的传递是编译时通过gcc -DMODULE 

传递的。在编译一个自己写的驱动模块的时候,如果不把它融合到内核的目录里,那我们

自己写的Makefile里通常会在make 的后面加上modules,也是达到这个目的。

当动态编译一个模块的时候,定义如下:

#define early_initcall(fn)module_init(fn)#define core_initcall(fn)module_init(fn)#define postcore_initcall(fn)module_init(fn)#define arch_initcall(fn)module_init(fn)#define subsys_initcall(fn)module_init(fn)#define fs_initcall(fn)module_init(fn)#define device_initcall(fn)module_init(fn)#define late_initcall(fn)module_init(fn)#define security_initcall(fn)module_init(fn)
所有这些 device_initcall(fn)等全部等效于module_init(fn)。其实也容易理解,既然你可以不

编译进内核,说明并不是那么的重要,那就不管你说以往是什么头衔,都一视同仁。那就只分析

module_init(fn)就可以了。

#define module_init(initfn)\static inline initcall_t __inittest(void)\{ return initfn; }\int init_module(void) __attribute__((alias(#initfn)));
首先我们可以发现发现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

就能够执行我们的加载函数了。



0 0
原创粉丝点击