写过Linux驱动的人都知道module_init宏,因为它声明了一个驱动的入口函数。
我们都知道,linux对驱动程序提供静态编译进内核和动态加载两种方式,当我们试图将一个驱动程序编译进内核时,开发者通常提供一个xxx_init()函数接口以启动这个驱动程序同时提供某些服务。
那么,根据常识来说,这个xxx_init()函数肯定是要在系统启动的某个时候被调用,才能启动这个驱动程序。
最简单直观地做法就是:开发者试图添加一个驱动初始化程序时,在内核启动init程序的某个地方直接添加调用自己驱动程序的xxx_init()函数,在内核启动时自然会调用到这个程序。
类似这样子:
void init(void)
{
a_init();
b_init();
// ...
z_init();
}
但是,回头一想,这种做法在单人开发的小系统中或许可以,但是在linux中,如果驱动程序是这样通过手动添加的话,那就是一场灾难。
不难想到另一种方式,就是集中提供一个地方,如果你要添加你的驱动初始化程序,你就将你的初始化函数在这个地方进行添加,在内核启动的时候统一扫描这个地方,再执行这一部分的所有被添加的驱动程序。
比如,直接在C文件中作一个列表,在里面添加初始化函数:
#include
void a_init(void) {
printf("%s\n", __func__);
}
void b_init(void) {
printf("%s\n", __func__);
}
void(*fun_list[]) (void)={
a_init,
b_init,
};
void init(void)
{
int i;
void(*pfun) (void);
for (i = 0; i < sizeof(fun_list)/sizeof(fun_list[0]); ++i) {
printf("%d\n", i);
fun_list[i]();
}
}
但这个方法也不够完美。
Linux内核是如何解决的?
Linux源码在编译的时候,通过使用告知编译器链接,自定义一个专门用来存放这些初始化函数的地址段,将对应的函数入口统一放在一起;
虽然我们没有手动将函数添加到函数队列中,但实际上我们使用内核提供的xxx_init()以后,编译器就可以替我们将对应的函数入口集中存在一个地方。
等到在内核启动的时候统一扫描这个段的开始,按照顺序,就可以执行这一部分的所有被添加的驱动程序。
对上层而言,linux内核提供xxx_init(init_func)宏定义接口,驱动开发者只需要将驱动程序的init_func使用来修饰,这个函数就被自动添加到了上述的段中,开发者完全不需要关心实现细节。
对于各种各样的驱动而言,可能存在一定的依赖关系,需要遵循先后顺序来进行初始化,考虑到这个,linux也对这一部分做了分级处理。
而且,对于 init 初始化代码而言,基本上也就只需要执行一次,因此这类 xxx_init() 代码所在的特殊段将在初始化完成之后被内存管理器回收,同时也节省了内存。
定位到Linux内核源码中的 include/linux/init.h
,可以看到有如下代码:
#ifndef MODULE
// 静态加载
// ...
#else
// 动态加载
// ...
#endif
显然,MODULE是可配置的。上面部分用于将模块静态编译连接进内核,下面部分用于编译可动态加载的模块。
接下来我们对这两种情况进行分析。
#ifndef MODULE
// 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)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
xxx_init_call(fn)
的原型其实是__define_initcall(fn, n)
。
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
需要注意的是,根据官方注释可以看到early_initcall(fn)只针对内置的核心代码,不能描述模块。
继续跟踪代码,看看__define_initcall(fn,n):
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
我们看看如何解析这个宏__define_initcall:
__attribute__()
是gnu C中的扩展语法,它可以用来实现很多灵活的定义行为,这里不细究。__attribute__((__section__(".initcall" #id ".init")))
表示编译时将目标符号放置在括号指定的段中。#define __used __attribute__((__used__))
用于作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。#define __used __attribute__((__used__))
为了更方便地理解,我们拿举个例子来说明,开发者声明了这样一个函数:module_init(hello_init);,那么有:
__define_initcall(hello_init, 6)
static initcall_t __initcall_hello_init6 = test_init
;这就是定义了函数指针变量。__initcall_hello_init6
这个变量即使没被引用也保留符号,且将其放置在内核镜像的.initcall6.init
段处。GNU编译工具链支持用户自定义section,所以我们阅读一些GUN项目的时候,会发现类似用法:
__attribute__((__section__("section-name")))
__attribute__
用来指定变量或结构位域的特殊属性,其后的双括弧中的内容是属性说明,它的语法格式为:__attribute__ ((attribute-list))
。它有位置的约束,通常放于声明的尾部且“ ;” 之前。此时attribute-list为
__section__(“.initcall6.init”)
。通常,编译器将生成的代码存放在.text段中。但有时可能需要其他的段,或者需要将某些函数、变量存放在特殊的段中,section属性就是用来指定将一个函数、变量存放在特定的段中。
即, module_init(hello_init)
展开为:
static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall6.init"))) = hello_init
这里的 initcall_t
是函数指针类型,如
typedef int (*initcall_t)(void);
.initcall
既然我们知道了xxx_initcall是怎么定义而且目标函数的放置位置,那么使用xxx_initcall()修饰的函数是怎么被调用的呢?
我们就从内核C函数起始部分也就是start_kernel开始往下挖,这里的调用顺序为:
start_kernel
-> rest_init();
-> kernel_thread(kernel_init, NULL, CLONE_FS);
-> kernel_init()
-> kernel_init_freeable();
-> do_basic_setup();
-> do_initcalls();
这个do_initcalls()就是我们需要寻找的函数了,在这个函数中执行所有使用xxx_initcall()声明的函数,接下来我们再来看看它是怎么执行的:
// init/main.c
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_initcall_level(int level)
{
initcall_t *fn;
// ...
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
在上述代码中,定义了一个静态的initcall_levels数组,这是一个指针数组,数组的每个元素都是一个指针.
do_initcalls()
循环调用do_initcall_level(level)
,level就是initcall的优先级数字,由for循环的终止条件ARRAY_SIZE(initcall_levels) - 1
可知,总共会调用do_initcall_level(0)~do_initcall_level(7),一共八次。
而do_initcall_level(level)
中则会遍历initcall_levels[level]
中的每个函数指针,initcall_levels[level]
实际上是对应的__initcall##level##_start
指针变量,然后依次取出__initcall##level##_start
指向地址存储的每个函数指针,并调用do_one_initcall(*fn)
,实际上就是执行当前函数。
可以猜到的是,这个__initcall##level##*start
所存储的函数指针就是开发者用xxx_initcall()
宏添加的函数,对应".initcall*##level##.init"
段。
// init/main.c
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
// ...
return ret;
}
static int __init_or_module do_one_initcall_debug(initcall_t fn)
{
ktime_t calltime, delta, rettime;
unsigned long long duration;
int ret;
pr_debug("calling %pF @ %i\n", fn, task_pid_nr(current));
calltime = ktime_get();
ret = fn();
rettime = ktime_get();
delta = ktime_sub(rettime, calltime);
duration = (unsigned long long) ktime_to_ns(delta) >> 10;
pr_debug("initcall %pF returned %d after %lld usecs\n",
fn, ret, duration);
return ret;
}
do_one_initcall(*fn)
的执行:判断initcall_debug的值,如果为真,则调用do_one_initcall_debug(fn)
;如果为假,则直接调用fn。
事实上,调用do_one_initcall_debug(fn)只是在调用fn的基础上添加一些额外的打印信息,可以直接看成是调用fn。
那么,在initcall源码部分有提到,在开发者添加xxx_initcall(fn)时,事实上是将fn放置到了".initcall##level##.init"
的段中。
但是在do_initcall()的源码部分,却是从initcall_levels
(__initcall##level##_start
指针)取出,initcall_levels[level]
是怎么关联到".initcall##level##.init"
段的呢?
答案在vmlinux.lds.h中:
// include/asm-generic/vmlinux.lds.h
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
VMLINUX_SYMBOL(__initcall_end) = .;
在这里首先定义了__initcall_start
,将其关联到".initcallearly.init"
段。
然后对每个level定义了INIT_CALLS_LEVEL(level)
,将INIT_CALLS_LEVEL(level)
展开之后的结果是定义__initcall##level##_start
,并将
__initcall##level##_start
关联到".initcall##level##.init
"段和".initcall##level##s.init
"段。
__initcall_start = .; \
*(.initcallearly.init) \
__initcall0_start = .; \
*(.initcall0.init) \
*(.initcall0s.init) \
// 省略1、2、3、4、5
__initcallrootfs_start = .; \
*(.initcallrootfs.init) \
*(.initcallrootfss.init) \
__initcall6_start = .; \
*(.initcall6.init) \
*(.initcall6s.init) \
__initcall7_start = .; \
*(.initcall7.init) \
*(.initcall7s.init) \
__initcall_end = .;
上面这些代码段最终在kernel.img中按先后顺序组织,也就决定了位于其中的一些函数的执行先后顺序(__initcall_hello_init6 位于 .initcall6.init 段中)。.init 或者 .initcalls 段的特点就是,当内核启动完毕后,这个段中的内存会被释放掉。这一点从内核启动信息可以看到
Freeing unused kernel memory: 124K (80312000 - 80331000)
到这里,__initcall##level##_start
和".initcall##level##.init
"段的对应就比较清晰了,所以,从initcall_levels[level]
部分一个个取出函数指针并执行函数就是执行xxx_init_call()
定义的函数。
那么存放于.initcall6.init
段中的__initcall_hello_init6
是怎么样被调用的呢?我们看文件 init/main.c,代码梳理如下:
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)
kernel_init 这个函数是作为一个内核线程被调用的(该线程最后会启动第一个用户进程init)。
我们着重关注 do_initcalls
函数,如下:
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
函数 do_initcall_level 如下:
static void __init do_initcall_level(int level)
{
// 省略
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
函数 do_one_initcall 如下:
int __init_or_module do_one_initcall(initcall_t fn)
{
int ret;
// 省略
ret = fn();
return ret;
}
initcall_levels 的定义如下:
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
initcall_levels[]
中的成员来自于 INIT_CALLS 的展开,如“__initcall0_start = .;
”,这里的__initcall0_start
是一个变量,它跟代码里面定义的变量的作用是一样的,所以代码里面能够使用__initcall0_start
。
因此在 init/main.c
中可以通过 extern 的方法将这些变量引入,如下:
extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
到这里基本上就明白了,在 do_initcalls
函数中会遍历 initcalls
段中的每一个函数指针,然后执行这个函数指针。因为编译器根据链接脚本的要求将各个函数指针链接到了指定的位置,所以可以放心地用do_one_initcall(*fn)
来执行相关初始化函数。
我们例子中的 module_init(hello_init)
是 level6 的 initcalls 段,比较靠后调用,很多外设驱动都调用 module_init
宏,如果是静态编译连接进内核,则这些函数指针会按照编译先后顺序插入到 initcall6.init
段中,然后等待 do_initcalls
函数调用。
便于理解,我们需要一个示例来梳理整个流程,假设我是一个驱动开发者,开发一个名为beagle的驱动,在系统启动时需要调用beagle_init()函数来启动启动服务。
我需要先将其添加到系统中:
core_initcall(beagle_init)
core_initcall(beagle_init)
宏展开为__define_initcall(beagle_init, 1)
,所以beagle_init()
这个函数被放置在".initcall1.init
"段处。
在内核启动时,系统会调用到do_initcall()函数。
根据指针数组initcall_levels[1]
找到__initcall1_start
指针,在vmlinux.lds.h
可以查到:__initcall1_start
对应".initcall1.init
"段的起始地址,依次取出段中的每个函数指针,并执行函数。
添加的服务就实现了启动。
可能有些C语言基础不太好的朋友不太理解do_initcall_level()函数中依次取出地址并执行的函数执行逻辑:
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
fn为函数指针,fn++相当于函数指针+1,相当于:内存地址+sizeof(fn)
而initcall_levels[level]
指向当前".initcall##level##s.init
"段,initcall_levels[level+1]
指向".initcall##(level+1)##s.init
"段,两个段之间的内存就是存放所有添加的函数指针。
也就是从".initcall##level##s.init
"段开始,每次取一个函数出来执行,并累加指针,直到取完。
#else
模块代码有两种运行方式:
obj-y
或obj-m
选项进行选择。而一旦可动态加载的模块目标代码(.ko)被加载重定位到内核,其作用域和静态链接的代码是完全等价的。所以这种运行方式的优点显而易见:
因为这样的优点,在进行设备驱动开发时,基本上都是将其编译成可动态加载的模块。但是需要注意,有些模块必须要编译到内核,随内核一起运行,从不卸载,如 vfs、platform_bus等。
实际上,上述的机制就是通过module_init实现的。
// include/linux/init.h
/* Don't use these in loadable modules, but some people do... */
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define rootfs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
#define console_initcall(fn) module_init(fn)
#define security_initcall(fn) module_init(fn)
__inittest
仅仅是为了检测定义的函数是否符合 initcall_t
类型,如果不是 __inittest
类型在编译时将会报错。所以真正的宏定义是:
/* Each module must use one module_init(). */
#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)));
因此,用动态加载方式时,可以不使用module_init
和 module_exit
宏,而直接定义 init_module
和 cleanup_module
函数,效果是一样的。
alias
属性是 gcc
的特有属性,将定义init_module
为函数initfn
的别名。
所以 module_init(hello_init)
的作用就是定义一个变量名 init_module
,其地址和 hello_init
是一样的。
// filename: HelloWorld.c
#include
#include
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Bye Bye World\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
上述例子编译可动态加载模块过程中,会自动产生 HelloWorld.mod.c
文件,内容如下:
#include
#include
#include
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,
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
可知,其定义了一个类型为 module 的全局变量__this_module
,成员 init
为 init_module(即 hello_init),且该变量链接到 .gnu.linkonce.this_module
段中。
编译后所得的 HelloWorld.ko
需要通过 insmod
将其加载进内核,由于 insmod 是 busybox 提供的用户层命令,所以我们需要阅读 busybox 源码。
insmod_main
// modutils/insmod.c
int insmod_main(int argc UNUSED_PARAM, char **argv)
{
char *filename;
int rc;
/* Compat note:
* 2.6 style insmod has no options and required filename
* (not module name - .ko can't be omitted).
* 2.4 style insmod can take module name without .o
* and performs module search in default directories
* or in $MODPATH.
*/
IF_FEATURE_2_4_MODULES(
getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
argv += optind - 1;
);
filename = *++argv;
if (!filename)
bb_show_usage();
rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
if (rc)
bb_error_msg("can't insert '%s': %s", filename, moderror(rc));
return rc;
}
insmod_main
|
--> bb_init_module
|
--> init_module
bb_init_module
// modutils/modutils.c
/* Return:
* 0 on success,
* -errno on open/read error,
* errno on init_module() error
*/
int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
size_t image_size;
char *image;
int rc;
bool mmaped;
if (!options)
options = "";
//TODO: audit bb_init_module_24 to match error code convention
#if ENABLE_FEATURE_2_4_MODULES
if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
return bb_init_module_24(filename, options);
#endif
/*
* First we try finit_module if available. Some kernels are configured
* to only allow loading of modules off of secure storage (like a read-
* only rootfs) which needs the finit_module call. If it fails, we fall
* back to normal module loading to support compressed modules.
*/
# ifdef __NR_finit_module
{
int fd = open(filename, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
rc = finit_module(fd, options, 0) != 0;
close(fd);
if (rc == 0)
return rc;
}
}
# endif
image_size = INT_MAX - 4095;
mmaped = 0;
image = try_to_mmap_module(filename, &image_size);
if (image) {
mmaped = 1;
} else {
errno = ENOMEM; /* may be changed by e.g. open errors below */
image = xmalloc_open_zipped_read_close(filename, &image_size);
if (!image)
return -errno;
}
errno = 0;
init_module(image, image_size, options);
rc = errno;
if (mmaped)
munmap(image, image_size);
else
free(image);
return rc;
}
init_module
而 init_module 定义如下:
// modutils/modutils.c
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
因此,该系统调用对应内核层的 sys_init_module 函数。
include/linux/syscalls.h中:
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
从而形成sys_init_module
函数。
对应的内核实现
// kernel/module.c
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs);
err = copy_module_from_user(umod, len, &info);
if (err)
return err;
return load_module(&info, uargs, 0);
}
SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);
if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS
|MODULE_INIT_IGNORE_VERMAGIC))
return -EINVAL;
err = copy_module_from_fd(fd, &info);
if (err)
return err;
return load_module(&info, uargs, flags);
}
代码梳理:
SYSCALL_DEFINE3(init_module, ...)
load_module
do_init_module(mod)
do_one_initcall(mod->init);
从而执行了mod->init
,即module_init(xx)
中的xx
初始化函数。