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);
}
之后这个模块就加载进内核,可以进行使用了。
0 0
原创粉丝点击