linux module_init

Linux内核编程中,`module_init`用于驱动的加载初始化,它将初始化函数添加到`.initcall`区段,按优先级加载。本文详细解析`module_init`的工作原理,包括`__init`宏如何将函数放入.initlist区段,以及内核如何在启动后期释放不再使用的内存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

就像你写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末尾有个函数:

 /*
  * Ok, we have completed the initial bootup, and
  * we're essentially up and running. Get rid of the
  * initmem segments and start the user-mode stuff..
  */
 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://blog.csdn.net/citytramper/archive/2006/02/16/600708.aspx

http://blog.csdn.net/citytramper/archive/2006/04/15/664930.aspx

### 后期初始化函数 `later_init` 和模块初始化函数 `module_init` 在 Linux 内核开发中,`later_init` 并不是一个标准的术语或宏定义,但它可能指的是某些特定场景下的后期初始化逻辑。而 `module_init` 是一个广泛使用的宏,用于指定模块加载时执行的入口函数。 #### 1. **`module_init` 宏** `module_init` 是一种机制,允许开发者为内核模块指定一个初始化函数,在模块被加载到内核时自动调用该函数。其核心实现依赖于 GCC 的属性扩展功能 (`__attribute__((constructor))`) 或类似的链接器脚本技术[^4]。 以下是 `module_init` 的基本工作原理: - 当编译带有 `module_init(init_function)` 的代码时,`init_function` 被标记为需要优先运行的函数之一。 - 在模块加载过程中,内核会扫描这些特殊标记的函数并依次调用它们。 示例代码如下: ```c #include <linux/module.h> #include <linux/kernel.h> static int __init my_module_init(void) { printk(KERN_INFO "Module initialized\n"); return 0; } static void __exit my_module_exit(void) { printk(KERN_INFO "Module exited\n"); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); ``` 上述代码中的 `my_module_init` 函数会在模块加载时被执行。 --- #### 2. **假设的 `later_init` 场景** 虽然没有官方文档提及名为 `later_init` 的具体概念,但在实际项目中可能存在类似的功能需求。例如,有些驱动程序或子系统希望延迟部分初始化操作直到更晚的时间点完成(比如等待其他组件准备好)。这种情况下可以手动设计类似于 `later_init` 的机制。 常见的做法包括但不限于以下几种方式: - 使用定时器接口(如 `timer_setup()`),安排某个延后的回调函数; - 利用工作队列(workqueue)提交异步任务; - 借助设备模型框架的通知链表监听事件触发条件满足后再继续处理剩余步骤; 下面是一个简单的例子展示如何通过 workqueue 实现所谓的 “late init” 功能: ```c #include <linux/workqueue.h> #include <linux/init.h> #include <linux/module.h> struct delayed_work my_late_init_work; void late_init_handler(struct work_struct *work) { pr_info("Late initialization completed.\n"); } DECLARE_DELAYED_WORK(my_late_init_work, late_init_handler); static int __init early_init(void){ queue_delayed_work(system_wq,&my_late_init_work,msecs_to_jiffies(500)); pr_info("Early initialization done, scheduling late init...\n"); return 0; } static void __exit cleanup_mod(void){ cancel_delayed_work_sync(&my_late_init_work); pr_info("Cleanup complete.\n"); } module_init(early_init); module_exit(cleanup_mod); MODULE_LICENSE("Dual BSD/GPL"); ``` 在这个案例里,“早起”的初始化由 `early_init` 执行完毕后立即调度了一个延迟作业去稍候再做额外的工作——即所谓“晚期”。 --- #### 3. **两者的对比** | 特性 | `module_init` | 自定义 `later_init` | |--------------------------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| | 初始化时机 | 模块载入瞬间 | 可设定任意时间间隔 | | 是否内置支持 | 是 | 需要自行构建 | | 应用范围 | 主要用作模块启动阶段的核心配置 | 更适合那些需分多阶段逐步完善的复杂对象 | 需要注意的是,如果确实存在某种形式的标准 API 名叫 `later_init`,那么它应该属于某特定领域或者第三方补丁集的一部分而不是原生Linux Kernel所提供. --- ### 结论 综上所述,尽管两者都涉及到了系统的初始化过程,但是他们针对的应用场合以及具体的运作模式存在着明显的差异。对于常规意义上的插件化管理来说推荐采用成熟的 `module_init`;而对于更加精细控制的需求则考虑引入自定义方案诸如基于WorkQueue等手段达成目的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值