内核常见宏定义| module_init 详解必看

前言

做驱动开发前,一定要知道关于Linux的驱动模块化机制(module)运行方式以及加载时机。看到几篇博客写module_init机制挺好借此总结一下分享给大家。

initcall机制的由来

驱动初始化最原始的做法:开发者试图添加一个驱动初始化程序时,在内核启动 init 程序的某个地方直接添加调用自己驱动程序的 xxx_init() 接口函数,在内核启动时就自然会启动这个驱动程序,类似:

void kernel_init()
{
    a_init();
    b_init();
 
    ...
 
    m_init();
}

但是,这种做法在RTOS系统中或许可以,对于 Linux 庞大的系统来说,驱动很多,不可能每添加一个驱动就会改动一下 kernel_init() 代码,这将会是一场灾难。

Linux 内核提供了解决方案:

  • 在编译Linux内核的时候,通过使用告知编译器连接,自定义一个专门用来存放这些初始化函数的地址段,将对应的函数入口统一放在一起;

  • 驱动程序中调用linux 内核提供的专门的 xxx_init() 接口,由编译器来收集这些入口函数,集中存放在一个地方;

  • 内核启动时,统一扫描这段的开始地址,按照顺序执行被添加的驱动初始化程序;

  • init 初始化代码,基本上只会执行一次,因此在这类 xxx_init() 代码所在的特殊段在初始化 完成之后会被内存管理器回收,同时节省了这部分的内存;

本文来探索一下Linux内核源码是如何实现的解决方案,本篇内核代码版本5.10

module_init机制

先看一个module_init机制里最简单的模块例子如下:

#include <linux/module.h>
#include <linux/init.h>
 
static int hello_init(void)
{
    printk(KERN_INFO "Hello World\n");
    return 0;
}
 
static void hello_exit(void)
{
    printk(KERN_INFO "Bye Bye World\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("pan");
MODULE_DESCRIPTION("hello world ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");

模块代码有两种运行方式,一个是静态编译连接进内核,在系统启动过程中进行初始化;另外一个是编译成可动态加载的module,通过insmod动态加载重定位到内核。这两种方式可以在Makefile中通过obj-yobj-m选项进行选择。

那么同样一份C代码如何实现这两种方式的呢?

答案就在于MODULE宏!下面我们一起来分析MODULE宏。(这里所用的Linux内核版本为5.10)定位到Linux内核源码中的 include/linux/module.h,可以看到有如下代码:

#ifndef MODULE

#define module_init(x)  __initcall(x);
#else /* MODULE */
...
#define module_init(initfn)                                     \
         static inline initcall_t __maybe_unused __inittest(void)                \
        { return initfn; }                                      \
         int init_module(void) __copy(initfn)                    \
                 __attribute__((alias(#initfn)));                \
         __CFI_ADDRESSABLE(init_module)

#endif

显然,MODULE宏是由Makefile控制的。上面部分用于将模块静态编译连接进内核,下面部分用于编译可动态加载的模块。本文将分开单独剖析这两种情况下的 initcall 机制。接下来我们对这两种情况进行分析。

静态编译进内核

代码梳理:

include/linux/init.h
/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)		__define_initcall(fn, early)
 
/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#define pure_initcall(fn)		__define_initcall(fn, 0)
 
#define core_initcall(fn)		__define_initcall(fn, 1)
#define core_initcall_sync(fn)		__define_initcall(fn, 1s)
#define postcore_initcall(fn)		__define_initcall(fn, 2)
#define postcore_initcall_sync(fn)	__define_initcall(fn, 2s)
#define arch_initcall(fn)		__define_initcall(fn, 3)
#define arch_initcall_sync(fn)		__define_initcall(fn, 3s)
#define subsys_initcall(fn)		__define_initcall(fn, 4)
#define subsys_initcall_sync(fn)	__define_initcall(fn, 4s)
#def
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值