linux module解析
来源:互联网 发布:win10 卸载软件 编辑:程序博客网 时间:2024/06/10 07:25
内核模块操作主要在kernel/module.c中,首先我们看下几个主要的宏定义:
module_init,module_exit。
这两个宏定义取决于是否有定义MODULE,没定义的话,表示模块静态编译进内核中。
我们看下宏定义展开后的表示。
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
#define __define_initcall(fn, 6) \
static initcall_t __initcall_fn_6 __used \
__attribute__((__section__(".initcall" "6" ".init"))) = fn
将fn放入.initcall6.init输入段
#define module_exit(x) __exitcall(x);
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define __exit_call __used __section(.exitcall.exit)
上面宏定义中有两个c语言中的符号#和##。
在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号,例如#a 等价于 "a"
而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量;简单讲就是将##两侧的
字串进行连接。
module_init用来将模块放入.initcall6.init section中,这是通过gcc attribute属性执行link时的section。
具体可以查看下kernel编译后输出的lds文件 vi kernel/arch/arm64/kernel/vmlinux.lds,里面有指定了不同段的连接地址:
__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.init call1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(. initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init ) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init ) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
最终在kernel初始化代码中经过层层调用,依次执行了不同模块的init函数,
这种做法好处在于增删模块时不需要修改kernel init调用,只需要定义好init级别,
初始化时就会按顺序自动执行。
start_kernel
|
--> rest_init |
--> kernel_thread |
--> kernel_init |
--> kernel_init_freeable |
--> do_basic_setup |
--> do_initcalls |
--> do_initcall_level(level) |
--> do_one_initcall(initcall_t fn)
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;
strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
//从start地址开始,依次执行level内的所有函数
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
看完了静态编译进内核的模块调用,接下来看下动态加载模块的加载过程。
对于动态加载模块,module_init跟module_exit的定义不同,
#define module_init(initfn) \
static inline initcall_t __inittest(void)\
{ return initfn; }\
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void)\
{ return exitfn; }\
void cleanup_module(void) __attribute__((alias(#exitfn)));
这里面主要用到了gcc的alias属性,这个是用来定义函数别名。
int init_module(void) __attribute__((alias(#initfn))) 这个表示initfn的别名是init_module。
编译成模块动态加载进内核的代码,在编译的过程会自动生成一个*.mod.c文件,
会定义module结构体,用于insmod时使用。
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
insmod最终会调用kernel的init_module系统调用,
这个接口实现过程,首先分配一段内存,将模块读取到内核中,
接着进行elf格式检查,解析module中symbol,找个各个section,
最终如果mod->init不为空,调用mod->init函数。
/* Start the module */
if (mod->init != NULL){
ret = do_one_initcall(mod->init);
}
之后这个模块就加载进内核,可以进行使用了。
module_init,module_exit。
这两个宏定义取决于是否有定义MODULE,没定义的话,表示模块静态编译进内核中。
我们看下宏定义展开后的表示。
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
#define __define_initcall(fn, 6) \
static initcall_t __initcall_fn_6 __used \
__attribute__((__section__(".initcall" "6" ".init"))) = fn
将fn放入.initcall6.init输入段
#define module_exit(x) __exitcall(x);
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define __exit_call __used __section(.exitcall.exit)
上面宏定义中有两个c语言中的符号#和##。
在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号,例如#a 等价于 "a"
而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量;简单讲就是将##两侧的
字串进行连接。
module_init用来将模块放入.initcall6.init section中,这是通过gcc attribute属性执行link时的section。
具体可以查看下kernel编译后输出的lds文件 vi kernel/arch/arm64/kernel/vmlinux.lds,里面有指定了不同段的连接地址:
__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.init call1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(. initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init ) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init ) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
最终在kernel初始化代码中经过层层调用,依次执行了不同模块的init函数,
这种做法好处在于增删模块时不需要修改kernel init调用,只需要定义好init级别,
初始化时就会按顺序自动执行。
start_kernel
|
--> rest_init |
--> kernel_thread |
--> kernel_init |
--> kernel_init_freeable |
--> do_basic_setup |
--> do_initcalls |
--> do_initcall_level(level) |
--> do_one_initcall(initcall_t fn)
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;
strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
//从start地址开始,依次执行level内的所有函数
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
看完了静态编译进内核的模块调用,接下来看下动态加载模块的加载过程。
对于动态加载模块,module_init跟module_exit的定义不同,
#define module_init(initfn) \
static inline initcall_t __inittest(void)\
{ return initfn; }\
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void)\
{ return exitfn; }\
void cleanup_module(void) __attribute__((alias(#exitfn)));
这里面主要用到了gcc的alias属性,这个是用来定义函数别名。
int init_module(void) __attribute__((alias(#initfn))) 这个表示initfn的别名是init_module。
编译成模块动态加载进内核的代码,在编译的过程会自动生成一个*.mod.c文件,
会定义module结构体,用于insmod时使用。
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
insmod最终会调用kernel的init_module系统调用,
这个接口实现过程,首先分配一段内存,将模块读取到内核中,
接着进行elf格式检查,解析module中symbol,找个各个section,
最终如果mod->init不为空,调用mod->init函数。
/* Start the module */
if (mod->init != NULL){
ret = do_one_initcall(mod->init);
}
之后这个模块就加载进内核,可以进行使用了。
0 0
- linux module解析
- linux module
- 全面解析Module模式
- javaScript全面解析Module模式
- YII解析:Module模块使用
- Nodejs源码解析之module
- YII解析:Module模块使用
- Nodejs源码解析之module
- 全面解析JavaScript Module模式
- linux module简介
- Linux kernel & module program
- compile linux 2.6 module
- linux module Makefile
- Linux Module编程小结
- Build linux kernel Module
- 编译linux module方法
- Linux module实现方法
- linux kernel module programming
- php锁定记录,防止多人操作
- 如何获取Client和Server自己的还有对方的IP/port
- 加载更多
- unity3d-配置Android环境,打包发布Apk流程详解
- IT人不要一直做技术
- linux module解析
- 如果你喜欢销售,那么你一定要看!经典之作!
- Android 编程下通过 Theme 和 Style 避免 APP 启动闪黑屏或者白屏,快速启动
- Nginx+tomcat实现集群和负载均衡
- 使用sklearn进行文本TF-IDF处理
- 命令行运行java程序(新手必会)
- 非技术
- 代理模式
- div按钮CSS