前言
做驱动开发前,一定要知道关于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-y或obj-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