linux设备驱动中的module_init

在linux中系统对驱动程序的加载提供了两种方式静态编译进内核和动态加载,并且驱动开发者通常会提供一个int xxx_init(void)函数,并通过诸如module_init(xxx_init)、early_initcall(xxx_init)等的方式使驱动程序被运行时能第一时间先执行xxx_init函数。那么内核是怎样做到这一点的呢?下面我们从驱动静态编译进内核和动态加载这两种来分析这个过程。
首先在include\linux\init.h代码中,可以看到关于module_init的定义有两种,通过预编译阶段判断MODULE宏是否被定义来确定采用哪种module_init的定义方式,这也就是在编译生成驱动的ko文件时为什么必须定义MODULE宏的原因。
1.驱动静态编译进内核(未定义MODULE宏)

/*下面我们逐步展开module_init*/
/*定义module_init为__initcall*/
#define module_init(x)	__initcall(x);
#define __initcall(fn) device_initcall(fn)   /*定义__initcall为device_initcall*/
/*
 * 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)  /*定义device_initcall为__define_initcall其中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)

#define __define_initcall(fn, id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" #id ".init"))) = fn; \
	LTO_REFERENCE_INITCALL(__initcall_##fn##id)
/*通过下面可以看到如果没有定义CONFIG_LTO宏那么LTO_REFERENCE_INITCALL展开后是一个空,所以我们展开__define_initcall宏的时候可以暂时不管它。*/
#ifdef CONFIG_LTO
#define LTO_REFERENCE_INITCALL(x) \
	; /* yes this is needed */			\
	static __used __exit void *reference_##x(void)	\
	{						\
		return &x;				\
	}
#else
#define LTO_REFERENCE_INITCALL(x)
#endif

/*通过上面的关系可以试着将例如module_init(test_init)的定义展开,结果如下:*/
module_init(test_init)	->  __define_initcall(test_init, 6)
__define_initcall(test_init, 6)
	static initcall_t __initcall_test_init6 __used \
	__attribute__((__section__(".initcall6.init"))) = test_init; 
/*最终module_init(test_init)的结果是*/
static initcall_t __initcall_test_init6 __used \
	__attribute__((__section__(".initcall6.init"))) = test_init;

不难看出module_init(test_init)实际上就是定义了一个类型是initcall_t 初始值为test_init的__initcall_test_init6变量,并且该变量存放在.initcall6.init段中。诸如:early_initcall(fn)、arch_initcall(fn)等的过程和这个类似,只是最后存放的段不一样而已具体是那个段由__define_initcall(fn, id)中的id决定。上面的用到的段都被在arch\arm\kernel\vmlinux.lds中定义。且各段的起始地址如下
__initcall_start = .; *(.initcallearly.init)
__initcall0_start = .; *(.initcall0.init) *(.initcall0s.init)
__initcall1_start = .; *(.initcall1.init) *(.initcall1s.init)
__initcall2_start = .; *(.initcall2.init) *(.initcall2s.init)
__initcall3_start = .; *(.initcall3.init) *(.initcall3s.init)
__initcall4_start = .; *(.initcall4.init) *(.initcall4s.init)
__initcall5_start = .; *(.initcall5.init) *(.initcall5s.init)
__initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init)
__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)
__initcall7_start = .; *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;

vmlinux.lds中如下代码定义各个initcall数据段
 .init.data : {
  *(.init.data) *(.meminit.data) *(.init.rodata) *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; *(__clk_of_table) *(__clk_of_table_end) . = ALIGN(8); __reservedmem_of_table = .; *(__reservedmem_of_table) *(__reservedmem_of_table_end) . = ALIGN(8); __clksrc_of_table = .; *(__clksrc_of_table) *(__clksrc_of_table_end) . = ALIGN(8); __cpu_method_of_table = .; *(__cpu_method_of_table) *(__cpu_method_of_table_end) . = ALIGN(8); __cpuidle_method_of_table = .; *(__cpuidle_method_of_table) *(__cpuidle_method_of_table_end) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; *(__irqchip_of_table) *(__irqchip_of_table_end)
  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
  __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
  __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
  __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
  . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
 }

通过上面的方式已经将module_init灯修饰的函数地址放在了各自的代码段中统一管理的起来,接下来要做的就是内核启动过程中在这些段中依次把所有的函数地址拿出开,并执行相对应的函数。下面是执行这些段中函数的调用过程:

kernel_init
	-> kernel_init_freeable
		-> do_basic_setup
			-> do_initcalls
				-> do_initcall_level(level); /**/

do_initcalls和do_initcall_level分析《init\main.c》

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[];
/*将上面vmlinux.lds中定义的各个initcall段的首地址存放到initcall_levels的数组中,目的在后面将通过遍历数组的方式来达到遍历整个initcall数据段,并且initcall_levels也会被编译器存放在__initdata数据区。内核加载完成后会通过kernel_init->free_initmem调用将整个__initdata区释放。目的应该是为了节省内存空间吧。*/
static initcall_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
	"early",
	"core",
	"postcore",
	"arch",
	"subsys",
	"fs",
	"device",
	"late",
};

static void __init do_initcall_level(int level)
{
	initcall_t *fn;

	strcpy(initcall_command_line, saved_command_line);
	parse_args(initcall_level_names[level],
		   initcall_command_line, __start___param,
		   __stop___param - __start___param,
		   level, level,
		   &repair_env_string);
	/*遍历level指定的段区域,并获取到该段区所存放的初始化函数地址,调用do_one_initcall函数执行相应的初始化函数。*/
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		 /*调用执行函数fn。*/
		do_one_initcall(*fn); 
}

static void __init do_initcalls(void)
{
	int level;
	/*循环将initcall_levels数组中存放8个段首地址拿出来,并作为参数传递给do_initcall_level。*/
	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
		do_initcall_level(level);
}

2.动态加载(定义MODULE宏,将编译成KO模块)

/*动态加载时所有的initcall实际上都是module_init,且并没有加载的优先级区别,具体的加载顺序是由文件系统中insmod的顺序决定的。*/
#define early_initcall(fn)		module_init(fn)
#define core_initcall(fn)		module_init(fn)
#define core_initcall_sync(fn)		module_init(fn)
#define postcore_initcall(fn)		module_init(fn)
#define postcore_initcall_sync(fn)	module_init(fn)
#define arch_initcall(fn)		module_init(fn)
#define subsys_initcall(fn)		module_init(fn)
#define subsys_initcall_sync(fn)	module_init(fn)
#define fs_initcall(fn)			module_init(fn)
#define fs_initcall_sync(fn)		module_init(fn)
#define rootfs_initcall(fn)		module_init(fn)
#define device_initcall(fn)		module_init(fn)
#define device_initcall_sync(fn)	module_init(fn)
#define late_initcall(fn)		module_init(fn)
#define late_initcall_sync(fn)		module_init(fn)

#define console_initcall(fn)		module_init(fn)
#define security_initcall(fn)		module_init(fn)

/* 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(test_init)为例参照上面关系进行展开:

#define module_init(test_init)					\
	static inline initcall_t __inittest(void)		\
	{ return test_init; }					\
	int init_module(void) __attribute__((alias(test_init)));
 /*上面最后一句的作用就是为test_init定义一个别名,别名为init_module。
 *所以最近上面的过程也就是定义一个__inittest函数,函数中返回init_module(别名会替换到原有函数的名称test_init)函数指针。
 *所有加载KO文件是执行的第一个函数应该是__inittest,然后__inittest会返回驱动初始化的函数,接着在执行这个初始化函数。*/
//最终结果如下:
static inline initcall_t __inittest(void)
{ return init_module; }

对于module_exit的过程实质上和module_init的过程系统。但是对于静态编译进内核的驱动中module_exit在内核启动阶段是不会再kernel_init->free_initmem中释放module_exit所存放的那个段空间的,原因吗就是释放后在关机或重启时需要调用退出函数时就不存在了,会出现错误。
在.init.data段中分布图:

你可能感兴趣的:(经验分享,linux,驱动开发,运维)