module_init的加载和释放

本文详细介绍了Linux内核模块的初始化过程,特别是module_init宏的使用及其内部机制。包括如何利用宏定义实现初始化函数列表,以及如何通过链接器脚本管理这些函数。
像你写C程序需要包含C库的头文件那样, Linux 内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
    其中,init.h 定义了驱动的初始化和退出相关的函数,kernel.h 定义了经常用到的函数原型及宏定义,module.h 定义了内核模块相关的函数、变量及宏。
    几乎每个linux驱动都有个module_init(与module_exit的定义在Init.h (\include\linux) 中)。没错,驱动的加载就靠它。为什么需要这样一个宏?原因是按照一般
的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:

void init(void)

{

    init_a();
    init_b();    

}

    如果再加入一个初始化函数呢,那么在init_b()后面再加一行:init_c(); 这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添
加一个新的函数都要修改init函数。可以采用另一种方式来处理这个问题,只要用一个宏来修饰一下:

void init_a(void)
{

}

__initlist(init_a, 1);

 

它是怎么样通过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:

#define __init __attribute__((unused, __section__(".initlist")))

#define __initlist(fn, lvl) \
static initlist_t __init_##fn __init = { \
  magic:    INIT_MAGIC, \
  callback: fn, \
  level:   lvl }

请注意:__section__(".initlist"),这个属性起什么作用呢?它告诉连接器这个变量存放在.initlist区段,如果所有的初始化函数都是用这个宏,那么每个函数会有
对应的一个initlist_t结构体变量存放在.initlist区段,也就是说我们可以在.initlist区段找到所有初始化函数的指针。怎么找到.initlist区段的地址呢?

extern u32 __initlist_start;
extern u32 __initlist_end;

这两个变量起作用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在哪定义的呢?在一个连接器脚本文件里

 . = ALIGN(4);
 .initlist : {
  __initlist_start = .;
  *(.initlist)
  __initlist_end = .;

}

 这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。为我们做好了。先来分析一下module_init。定义如下:

#define module_init(x)     __initcall(x);              //include\linux\init.h

#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

 

如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成 __initcall_func6 __used加入到内核映像的".initcall"区
。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。

#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)

可以看到,被声明为pure_initcall的最先加载。

    module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码     linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。


    在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化
完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。
    我们看源码,init/main.c中start_kernel是进入kernel()的第一个c函数,在这个函数的最后一行是rest_init();

static void rest_init(void)

{

     .....

     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
     unlock_kernel();
     cpu_idle();

     .....    

}

创建了一个内核线程,主函数kernel_init末尾有个函数: init_post();

这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。

void free_initmem(void)

{

    if (!machine_is_integrator() && !machine_is_cintegrator()) {
    free_area((unsigned long)(&__init_begin),
     (unsigned long)(&__init_end),
     "init"); }

}

 接下来就是kernel内存管理的事了。

 

原文: http://wenku.baidu.com/view/c9a1b2e9172ded630b1cb66d.html

<think>好的,用户想了解Linux内核编程中module_init__init的关系及用法。首先,我需要回忆这两个宏的具体作用它们在内核模块加载过程中的角色。 首先,module_init是一个宏,用于指定模块的初始化函数。当模块被加载时,内核会调用这个函数。而__init是一个属性宏,用于标记函数或变量仅在初始化阶段使用,之后内存会被释放。这可能涉及到内核的内存管理机制,比如将这部分代码或数据放在特定的段中,初始化完成后回收资源。 接下来,我需要确认两者的使用场景。module_init用于定义模块的入口点,无论是编译进内核还是作为可加载模块,都需要用它来声明初始化函数。而__init则用于优化内存使用,标记那些只在初始化阶段需要的函数变量,避免长期占用内存。 用户可能还会混淆这两个宏的具体用法,比如是否必须同时使用。例如,模块的初始化函数是否需要用__init修饰。根据经验,是的,通常module_init声明的函数会同时使用__init,这样在模块内置入内核时,初始化完成后可以释放内存。但如果是可加载模块,可能不需要,因为模块卸载时内存会被释放。不过需要进一步确认内核的处理机制。 另外,用户可能想知道如果不使用__init会有什么后果。可能会导致内存浪费,因为初始化代码在模块加载后不再需要,但未被释放。而module_init如果不使用,模块将无法正确注册初始化函数,导致加载失败。 需要查阅相关文档或代码示例来验证这些信息。例如,Linux内核源码中的模块通常会看到如下结构: static int __init my_module_init(void) { // 初始化代码 } module_init(my_module_init); 这里,__init修饰了初始化函数,而module_init将其注册为入口点。当模块编译进内核时,__init标记的部分会在初始化后被释放;如果是可加载模块,则在卸载时释放。 还要注意,__initdata用于初始化阶段的数据,而__exit__exitdata用于退出时的清理函数数据,类似机制。 可能用户的问题还涉及到模块的生命周期管理,以及内核如何优化资源使用。需要解释清楚这两个宏如何协作,确保模块正确初始化资源释放。 最后,生成相关问题时,需要考虑用户可能进一步想了解的内容,比如其他类似的宏(如__exit),或者模块加载卸载的具体过程,以及如何调试模块初始化的问题等。</think>在Linux内核编程中,`module_init``__init`是密切相关的宏,用于管理内核模块的初始化资源优化。以下是它们的详细关系用法: --- ### 1. `module_init` 的作用 `module_init`宏用于**指定模块的初始化函数**,该函数在模块加载时由内核自动调用。它的主要特点包括: - 当模块编译为可动态加载(`.ko`文件)时,初始化函数在`insmod`或`modprobe`时执行 - 当模块直接编译进内核时,初始化函数在内核启动时执行 - 初始化函数需要返回`int`类型值,成功返回`0`,失败返回负数错误码 示例: ```c static int __init my_module_init(void) { printk(KERN_INFO "Module initialized\n"); return 0; } module_init(my_module_init); // 注册初始化函数 ``` --- ### 2. `__init` 的作用 `__init`宏是**内存优化标记**,用于标识只在初始化阶段使用的函数或数据: - 被`__init`修饰的函数会被放入`.init.text`段 - 被`__init`修饰的变量会被放入`.init.data`段 - 内核在完成初始化后,会释放这些内存区域以节省空间[^1] 示例: ```c static int __initdata init_count = 0; // __initdata用于初始化阶段变量 static int __init my_init(void) { init_count++; return 0; } ``` --- ### 3. 两者的协作关系 | 特性 | `module_init` | `__init` | |---------------------|----------------------------|--------------------------| | 主要目的 | 注册模块入口点 | 标记初始化专用资源 | | 作用阶段 | 模块加载/内核启动时 | 编译阶段划分内存区域 | | 内存管理 | 不直接管理内存 | 优化内核内存使用 | | 典型组合 | 常与`__init`联合使用 | 常修饰`module_init`函数 | **典型代码模式**: ```c static int __init my_module_init(void) { // 用__init修饰初始化函数 /* 初始化逻辑 */ } module_init(my_module_init); // 注册为模块入口 ``` --- ### 4. 关键区别 - **编译处理差异**: - 若模块编译进内核(built-in),`__init`标记的函数/数据会在初始化后**被内存管理器自动释放** - 若模块为动态加载(loadable),`__init`标记的内容会在模块卸载时释放 - **必要性差异**: - `module_init`是**强制要求**的模块入口声明 - `__init`是**可选优化**,但强烈推荐使用 --- ### 5. 扩展应用 - `__exit``module_exit`:与初始化对应的清理机制 ```c static void __exit my_module_exit(void) { printk(KERN_INFO "Module removed\n"); } module_exit(my_module_exit); ``` - `__initdata`:专门用于初始化阶段的数据标记 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值