module_init的加载过程

本文详细解析了Linux内核中的模块初始化过程,包括module_init宏的定义与作用,以及不同初始化级别的实现机制。通过分析源代码,揭示了从early_initcall到late_initcall的加载顺序,解释了MODULE定义下initcall的统一处理方式。

module_init这个网上讲解的一大堆,这里作个学习记录。
initcall加载是分两步的
第一步:加载early_initcall
start_kernel->
rest_init->
kernel_init->
kernel_init_freeable->
do_pre_smp_initcalls
第二步:加载从__initcall0_start到__initcall7_start
start_kernel->
rest_init->
kernel_init->
kernel_init_freeable->
do_basic_setup->
do_initcalls

static initcall_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

#define INIT_CALLS_LEVEL(level)						\
		VMLINUX_SYMBOL(__initcall##level##_start) = .;		\
		*(.initcall##level##.init)				\
		*(.initcall##level##s.init)				\

#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		*(.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) = .;

这里要注意的,这个数组里面并没有
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
__initcallrootfs_start的引用,所以按道理rootfs_initcall的定义不会加载,但是我这个代码是从官网上下载的原始代码,可能不涉及文件系统加载吧。搜索代码也没看到其它哪里单独加载了__define_initcall(fn, rootfs)这个。所以我认为实际中,需要把__initcallrootfs_start添加到__initcall5_start这个的后面,不知道理解是否正确

首先module_init的定义是在文件:
include/linux/module.h
下面来看看具体定义

#ifndef MODULE
#define module_init(x)	__initcall(x);
#define module_exit(x)	__exitcall(x);

#else /* MODULE */

#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)));

#endif

上面宏定义:
1.如果没有定义了MODULE,那么就用 include/linux/init.h文件里面__initcall宏定义,等会具体看看这个宏
2.如果有定义MODULE,那么各种initcall全部被定义成module_init。不会存在我们印像中的加载顺序了。因为MODULE一般是针对单独编译的ko文件,就无所谓加载顺序,module_init宏定义,看起来了就是为回调函数起了个别名 init_module,在insmod时候统一调用init_module
3.当然现在一般都不会使用MODULE,很早之前做驱动时经常会说内核裁剪。裁剪的原因大体上就是节省空间而ko文件也在那时候很流行。现在硬件空间已经不是问题了,基本上没听说过内核裁剪的话题了。由于DTS流行,基本上也不需要内核裁剪,ko文件好像也已经绝迹了。大概是因为ko文件不太安全,污染内核

下面来看看include/linux/init.h里面定义的各种initcall

#define __define_initcall(fn, id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" #id ".init"))) = fn; \
	LTO_REFERENCE_INITCALL(__initcall_##fn##id)


#define early_initcall(fn)		__define_initcall(fn, early)
#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)

#define __initcall(fn) device_initcall(fn)

上面定义了17种加载级别,宏定义__define_initcall。我们拿级别为1的来展开看看
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)

core_initcall(xxx)
static initcall_t __initcall_xxx1 __used
attribute((section(".initcall" 1 “.init”))) = xxx;
LTO_REFERENCE_INITCALL(__initcall_xxx1)

core_initcall_sync(xxx)
static initcall_t __initcall_xxx1s __used
attribute((section(".initcall" 1s “.init”))) = xxx;
LTO_REFERENCE_INITCALL(__initcall_xxx1s)
要注意级1和级1S是两个级别,1先调,1S后调。下面来看看
文件:include/ams-generic/vmlinux.lds.h

#define INIT_CALLS_LEVEL(level)						\
		VMLINUX_SYMBOL(__initcall##level##_start) = .;		\
		*(.initcall##level##.init)				\
		*(.initcall##level##s.init)				\

#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		*(.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) = .;

注意INIT_CALLS_LEVEL这个宏定义,拿INIT_CALLS_LEVEL(0) 展开

	VMLINUX_SYMBOL(__initcal1_start) = .;		\
	*(.initcall1.init)				\
	*(.initcal1s.init)				\
可以看出每次INIT_CALLS_LEVEL是涉及到两个级别的调用。一个n和一个ns,initcallearly.init最先调用对应的宏定义为:
#define early_initcall(fn)		__define_initcall(fn, early)		//所有initcall中级别最高

下面回过头来看看我们经常用的module_init
#define module_init(x) __initcall(x); //不使用MODULE定义
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
可以看到,module_init的级别是比较低的,在17个级别中。排行倒数第三

<think>好的,我现在需要回答用户关于Linux内核中驱动程序是否都通过module_init进行加载的问题。首先,我得确认用户的问题核心是什么。用户可能想知道module_init的作用,以及是否所有驱动都使用这个机制。 首先,我应该回忆Linux内核模块的加载机制。module_init是一个宏,用于指定模块初始化函数的入口点。当模块被加载时,内核会调用这个函数。但是是否所有驱动都使用这个呢? 接下来,需要区分编译进内核的驱动和动态加载的模块。对于编译进内核的驱动,它们的初始化函数可能被放置在特定的段中,由内核在启动时自动调用,不需要通过module_init。而动态加载的模块(.ko文件)必须使用module_init来指定初始化函数,因为insmod或modprobe需要知道该调用哪个函数。 然后,要想到内核初始化的级别。引用中提到device_initcall的级别是6,这可能影响初始化顺序。编译进内核的驱动可能使用不同的initcall级别,比如通过__initcall宏来指定,而module_init对于模块来说对应的是device_initcall级别。因此,静态编译的驱动可能使用不同的机制,但动态模块必须用module_init。 还需要考虑模块卸载的情况,module_exit对应的清理函数。这可能不需要详细说明,但可以提及。 另外,用户可能混淆了“所有驱动”是否包括静态和动态的情况。需要明确说明两种情况下的不同处理方式。 最后,总结是否所有驱动都使用module_init。答案是否,只有动态加载的模块必须使用,而编译进内核的驱动可能通过其他方式注册初始化函数。 现在需要验证这些信息是否正确。比如,确认module_init在静态编译和动态模块中的行为是否不同。查阅资料,发现对于静态编译进内核的模块,module_init实际上将函数放到特定的initcall段中,而动态模块则使用它来指定加载函数。因此,无论是静态还是动态,驱动都可能使用module_init,但动态模块必须使用,而静态的则由内核在启动时处理。 但用户的问题是否包括静态编译的情况?用户问的是“驱动程序是否都通过module_init加载”,可能包括所有驱动类型。这时候需要澄清,如果驱动作为模块编译,必须用module_init;如果编译进内核,则初始化函数可能被链接到内核的初始化段,但开发时仍然使用module_init宏,只是宏的行为不同。 因此,正确的结论应该是:无论驱动是静态编译还是动态模块,都使用module_init宏,但具体处理方式不同。动态模块必须通过module_init指定加载函数,而静态编译的驱动在启动时由内核自动调用其初始化函数,但同样通过module_init宏定义。 因此,用户的问题答案应该是:是的,Linux内核驱动程序无论是静态编译还是动态加载,都需要使用module_init宏来指定初始化函数,但其加载机制有所不同。动态模块在加载时调用,静态编译的驱动在内核启动时调用。 不过需要再仔细检查。例如,有些驱动可能直接使用其他initcall级别,比如early_initcall,这属于特殊情况,但大部分驱动使用module_init对应的device_initcall级别。所以,module_init是常用的,但并非唯一方式,但大多数驱动使用它。因此,严格来说,是否所有驱动都使用module_init?可能不是,但大多数情况下是的。不过用户的问题可能更关注于是否必须使用module_init加载,而答案需要结合静态和动态的情况。 可能我的之前的思考有误。需要更准确的信息: 当驱动程序作为模块编译时,必须使用module_init,否则无法被加载。当驱动程序编译进内核时,module_init宏会将初始化函数放入对应的initcall段,在内核启动时按照级别顺序调用。因此,无论是哪种情况,驱动程序的初始化函数都是通过module_init宏定义的。因此,用户的问题的答案是:是的,所有使用模块化方式编写的Linux内核驱动程序都通过module_init宏进行加载初始化,无论是静态编译进内核还是动态加载为模块。但对于静态编译的情况,module_init宏的作用是将函数放入初始化调用链中,而不是在模块插入时调用。 但需要确认这个结论是否正确。例如,module_init在编译为模块时,会展开成特定代码,使得模块加载时调用该函数;而编译进内核时,会将该函数放入device_initcall段,在内核启动时调用。因此,无论哪种情况,驱动程序都是通过module_init宏来声明初始化函数的。因此,用户的问题的答案是肯定的:是的,驱动程序都通过module_init进行加载。但需要说明静态和动态两种情况下的差异。 所以最终答案应该是:是的,Linux内核驱动程序无论编译为模块还是静态链接进内核,都通过module_init宏指定初始化函数。动态加载的模块在插入时调用该函数,而静态编译的驱动在内核启动阶段按初始化级别调用[^1]。</think>### Linux内核驱动程序的module_init加载机制 在Linux内核中,**驱动程序通常通过`module_init`宏声明初始化函数**,但其具体加载机制因驱动类型(静态编译或动态模块)而异: 1. **动态加载的模块** 当驱动程序编译为**可加载模块**(.ko文件)时,必须使用`module_init`指定初始化函数入口。例如: ```c static int __init my_driver_init(void) { /* 初始化代码 */ } module_init(my_driver_init); ``` 通过`insmod`或`modprobe`加载模块时,内核会调用此函数完成初始化[^1]。 2. **静态编译进内核的驱动** 若驱动程序直接**编译进内核镜像**,`module_init`宏会将初始化函数注册到内核的初始化调用链中。此时,函数会在内核启动阶段按**初始化级别**(如`device_initcall`,级别6)自动执行,而非在模块加载时触发。 3. **关键区别** - **动态模块**:依赖`module_init`定义的函数响应模块加载事件。 - **静态驱动**:通过`module_init`将函数嵌入初始化流程,由内核启动时调用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bruk_spp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值