linux内核中的xx_initcall和module_init实现机制(linux3.1.0)
2014-11-29 17:53
417 查看
在内核代码中,arch_initcall 、device_initcall、module_init等经常遇到,本文分析其实现机制。
1 include/linux/init.h 下的相关定义
2 分析 __define_initcall宏定义
我们知道,以上所有的定义最后都转化为了__define_initcall(level,fn,id),那么就来分析这个宏。
从一上定义可知:
1 定义一个initcall_t类型函数指针
2 函数指针初值为fn
3 函数指针段属性为(".initcall" level ".init")
以arch_initcall(customize_machine) 为例,可以转化为:
1 定义一个函数指针 __initcall_customize_machine3
2 函数指针初始化为customize_machine
3 这个函数指针的属性是.initcall3.init
3 __section__(".initcall" level ".init")段属性定义
/include/asm-generic/vmlinux.lds.h中定义段属性
4 调用
其实,在linux内核中,有一个技巧是经常用到的,就是把某些具有类似特性的指针、函数、结构体等
放链接的时候放到同一个段,然后调用的时候到这个段中去遍历,或者去寻找匹配项。这里就是如此。
__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()的实现机制。但是有个前提,是在静态编译内核
的时候是这样的。如果你看的够仔细的话,应该注意到在第一部分[b]include/linux/init.h 下的[/b]
[b]相关定义第一行是#ifndef MODULE。[/b]
所谓静态编译,就是在make menuconfig时候,选择 *,也就是yes,模块直接编译进了
内核。
动态编译
当你希望这个模块可以用insmod动态加载的时候,可以选择 M ,也就是只编译成模块,
不编译进内核,此时就定义了MODULE。MODULE参数的传递是编译时通过gcc -DMODULE
传递的。在编译一个自己写的驱动模块的时候,如果不把它融合到内核的目录里,那我们
自己写的Makefile里通常会在make 的后面加上modules,也是达到这个目的。
当动态编译一个模块的时候,定义如下:
编译进内核,说明并不是那么的重要,那就不管你说以往是什么头衔,都一视同仁。那就只分析
module_init(fn)就可以了。
1、验证加载函数的格式
typedef int (*initcall_t)(void);
所以我们写加载函数的时候必须是返回值为int参数为void的函数,这个在内核里要求比较严格,所以我们写
加载函数的时候必须按照这个约定。
2、定义别名
就能够执行我们的加载函数了。
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"))) = fn
typedef 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()的实现机制。但是有个前提,是在静态编译内核
的时候是这样的。如果你看的够仔细的话,应该注意到在第一部分[b]include/linux/init.h 下的[/b]
[b]相关定义第一行是#ifndef MODULE。[/b]
所谓静态编译,就是在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
就能够执行我们的加载函数了。
相关文章推荐
- LINUX内核中的xx_initcall初始化标号
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- 转载_LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- 深入分析Linux内核源码-Linux管道的实现机制
- LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- linux的初始化函数(late_initcall和module_init)
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux内核抢占实现机制分析
- LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- Linux内核中的xx_initcall初始化标号
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux内核抢占实现机制分析
- LINUX内核中的xx_initcall初始化标号
- LINUX内核中的xx_initcall初始化标号
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux内核抢占实现机制分析