【Linux kernel基础】arch_initcall到底是怎么是一回事【doing】

本文详细探讨了Linux内核初始化过程中的arch_initcall机制,通过分析`customize_machine`函数的调用路径,解释了arch_initcall宏如何将函数添加到初始化调用链中。同时,解释了相关宏的层次展开,如`__initcall_id`、`__initcall_stub`等,展示了它们在构建初始化函数名称和代码段名称中的作用。文章还提及了链接文件在调用这些初始化函数中的角色,并讨论了不同initcall类型的执行顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

在学习【奔跑吧Linux内核——基于Linux 4.x内核源代码问题分析】中断管理机制章节中如下部分:
系统初始化时,customize_machine()函数会去枚举并初始化“arm,amba-bus”和
“simple-bus”总线上的设备,最终解析 DTS 中的相关信息,把相关信息添加到 struct device
数据结构中,向 Linux 内核注册一个新的外设。我们只关注中断相关信息的枚举过程:
[customize_machine()->of_platform_populate()->of_platform_bus_create()->of
_amba_device_create()]

查看customize_machine接口,看到arch_initcall,产生好奇,不知道其具体是做什么,及customize_machine在哪里被调到(在代码中直接找,没找到调用点)
machine_desc->init_machine和machine_desc是全局变量?待研究,TODO

[arch/arm/kernel/setup.c]
static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	if (machine_desc->init_machine)
		machine_desc->init_machine();

	return 0;
}
arch_initcall(customize_machine);

arch_initcall宏定义

代码中找到两处定义,为啥两处都有定义,看代码后应该是init.h
module.h里内容待后边研究,TODO
在这里插入图片描述

[include/linux/init.h]
/*
 * 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)


[include/linux/module.h]
/*
 * In most cases loadable modules do not need custom
 * initcall levels. There are still some valid cases where
 * a driver may be needed early if built in, and does not
 * matter when built as a loadable module. Like bus
 * snooping debug drivers.
 */
#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)

/* Each module must use one module_init(). */
#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, __initdata);

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)					\
	static inline exitcall_t __maybe_unused __exittest(void)		\
	{ return exitfn; }					\
	void cleanup_module(void) __copy(exitfn)		\
		__attribute__((alias(#exitfn)));		\
	__CFI_ADDRESSABLE(cleanup_module, __exitdata);

module.h里内容按下不表
先看init.h中的定义:

#define arch_initcall(fn)		__define_initcall(fn, 3)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

___define_initcall宏第三个参数.initcall##id:
查看该宏定义处,可知为__sec,即为代码段名称,最终展开为字符串.initcall3

#define ___define_initcall(fn, id, __sec)			\
	__unique_initcall(fn, id, __sec, __initcall_id(fn))

#define __unique_initcall(fn, id, __sec, __iid)			\
	____define_initcall(fn,					\
		__initcall_stub(fn, __iid, id),			\
		__initcall_name(initcall, __iid, id),		\
		__initcall_section(__sec, __iid))

__initcall_id(fn)宏

/*
 * initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 *
 * Initcalls are run by placing pointers in initcall sections that the
 * kernel iterates at runtime. The linker can do dead code / data elimination
 * and remove that completely, so the initcall sections have to be marked
 * as KEEP() in the linker script.
 */

/* Format: <modname>__<counter>_<line>_<fn> */
#define __initcall_id(fn)				\
	__PASTE(__KBUILD_MODNAME,			\
		__PASTE(__,						\
			__PASTE(__COUNTER__,		\
				__PASTE(_,				\
					__PASTE(__LINE__,	\
						__PASTE(_, fn)	\
					)					\
				)						\
			)							\
		)								\
	)									\

__initcall_id(fn)宏按层剥开:
在这里插入图片描述

再结合__PASTE宏(字符拼接)

/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)

__initcall_id(fn)宏小结:

__initcall_id(fn)宏展开是下边个鬼玩意:
在这里插入图片描述
当然上述宏展开后字符串中的三个宏,
在这里插入图片描述
编译器最终都会替换成对应的字符串
自己理解,整这么一长串,是为了防止重名

__unique_initcall(fn, id, __sec, __iid)宏

#define __unique_initcall(fn, id, __sec, __iid)					\
	____define_initcall(fn,										\
						__initcall_stub(fn, __iid, id),			\
						__initcall_name(initcall, __iid, id),	\
						__initcall_section(__sec, __iid)		\
	)															\

tip:宏参数__iid就是上一小节探究的__initcall_id(fn)宏

__initcall_name(prefix, __iid, id)宏

/* Format: __<prefix>__<iid><id> */
#define __initcall_name(prefix, __iid, id)			\
	__PASTE(__,						\
	__PASTE(prefix,						\
	__PASTE(__,						\
	__PASTE(__iid, id))))

结合该宏上层调用处,prefix(前缀)是:initcall字符
展开后:
prefix,,__iid,id
替换后
(真实字符串是需要去掉,的,此处为方便阅读保留)
initcall,,__KBUILD_MODNAME____COUNTER_____LINE___fn,id
替换id后:
在这里插入图片描述这是完整的初始化函数名称

__initcall_section(__sec, __iid)宏

#ifdef CONFIG_LTO_CLANG
#define __initcall_section(__sec, __iid)			\
	#__sec ".init.." #__iid
#define __initcall_stub(fn, __iid, id)				\
	__initcall_name(initstub, __iid, id)
#else
#define __initcall_section(__sec, __iid)			\
	#__sec ".init"
#define __initcall_stub(fn, __iid, id)	fn
#endif

由上文可知参数:
__sec是.initcall3,
__iid是__KBUILD_MODNAME____COUNTER_____LINE___fn,
展开后
.initcall3.init…__KBUILD_MODNAME____COUNTER_____LINE___fn,
或.initcall3.init
这是完整的代码段名称

__initcall_stub(fn, __iid, id)宏

#define __initcall_stub(fn, __iid, id)				\
	__initcall_name(initstub, __iid, id)

__initstub____KBUILD_MODNAME____COUNTER_____LINE___fn3
这是完整的初始化打桩函数名称

____define_initcall(fn, __unused, __name, __sec)宏

#define ____define_initcall(fn, __unused, __name, __sec)	\
	static initcall_t __name __used 			\
		__attribute__((__section__(__sec))) = fn;

把上边宏展开后:
其中__name是:
__initcall____KBUILD_MODNAME____COUNTER_____LINE___fn3

	static initcall_t __name __used 			\
		__attribute__((__section__(.initcall3.init))) = fn;

arch_initcall(fn)宏总结

arch_initcall(fn)-->fn: customize_machine
	__define_initcall(fn, id)-->id: 3
		___define_initcall(fn, id, __sec)-->__sec: .initcall3
			__unique_initcall(fn, id, __sec, __iid)-->__iid: __initcall_id(fn)
				____define_initcall(fn, __unused, __name, __sec)

调用处:
#define __unique_initcall(fn, id, __sec, __iid)					\
	____define_initcall(fn,										\
						__initcall_stub(fn, __iid, id),			\
						__initcall_name(initcall, __iid, id),	\
						__initcall_section(__sec, __iid)		\
	)
定义处:
#define ____define_initcall(fn, __unused, __name, __sec)	\
	static initcall_t __name __used 			\
		__attribute__((__section__(__sec))) = fn;

__iid: __initcall_id(fn)
	(__KBUILD_MODNAME)__(__COUNTER__)_(__LINE__)_fn
__unused: __initcall_stub(fn, __iid, id)(与编译宏CONFIG_LTO_CLANG是否定义有关)
__name: __initcall_name(initcall, __iid, id)
	__initcall__((__KBUILD_MODNAME)__(__COUNTER__)_(__LINE__)_fn)id

__sec:__initcall_section(__sec, __iid)
	.initcall3.init

__KBUILD_MODNAME:当前模块名字
__COUNTER__:出现次数?
__LINE__:行号

static initcall_t __name __used 			\
	__attribute__((__section__(.initcall3.init))) = fn;

下边需要查看链接文件,看该代码段在哪里被调用,TODO
待填坑。。。

用arch_initcall宏处理后的函数调用处



参考wiki:

各种initcall的执行先后顺序(module_init、postcore_initcall、arch_initcall、subsys_initcall、 fs_initcall)

<think>好的,我现在需要解释Linux内核中的一系列驱动加载宏,包括pure_initcall、pure_initcall_sync以及它们的各个变种。首先,我得回忆一下这些宏的作用和它们在启动过程中的顺序。 我记得Linux内核的初始化是通过一系列初始化函数(initcalls)来完成的,这些函数按照优先级顺序执行。每个宏对应不同的优先级,确保内核组件和驱动程序按正确的顺序初始化。例如,有些硬件需要先初始化,其他驱动才能依赖它们。 接下来,我需要确定每个宏的具体名称和对应的顺序。常见的initcall级别包括pure、core、postcore、arch等。每个级别可能有对应的_sync版本,这可能与并行初始化有关。 pure_initcall应该是最早执行的,用于那些不依赖其他服务的纯初始化函数。而core_initcall可能在pure之后,处理核心子系统。postcore_initcall可能跟在核心之后,处理后续的驱动。arch_initcall可能和架构相关的初始化有关,比如特定于ARM或x86的代码。 关于_sync后缀,可能表示这些初始化函数需要在多核环境下同步执行,或者必须在前一阶段所有函数完成后再执行,而不是并行处理。这可能用于需要严格顺序的初始化步骤。 然后,我需要验证每个宏的具体定义。例如,在Linux内核代码中,这些宏通常通过__define_initcall宏来定义,参数指定优先级。例如,pure_initcall可能是0级,core是1,postcore是2,arch是3,等等。 应用场景方面,pure_initcall可能用于非常早期的初始化,比如内存管理系统的设置。core_initcall用于核心子系统,如总线或中断控制器。arch_initcall用于架构特定的初始化,比如设置CPU特定的功能。 同时,需要说明用户何时应该选择这些宏。比如,当开发一个不依赖其他子系统的基础驱动时,使用pure_initcall;如果是设备驱动,可能需要根据依赖关系选择core、postcore或arch级别。 另外,需要注意同步版本的应用场景。例如,当某个初始化函数必须等待同一级别的所有其他函数完成后才能执行,这时使用_sync版本,确保顺序执行,避免竞态条件。 最后,需要确保解释清晰,结构合理,帮助用户理解这些宏的作用和正确使用方式。</think>Linux内核的驱动加载宏用于控制初始化函数的执行顺序,以下是逐级说明和应用场景分析: --- ### 一、层级关系(按执行顺序排列) 1. **pure_initcall** - **级别**:最高优先级(LEVEL=0) - **特点**:最早执行的初始化函数 - **应用场景**:不依赖任何其他内核服务的纯初始化(如内存池、基础算法库) 2. **core_initcall** - **级别**:LEVEL=1 - **特点**:核心子系统初始化 - **应用场景**:内核核心组件(如调度器、中断控制器) 3. **postcore_initcall** - **级别**:LEVEL=2 - **特点**:核心初始化完成后执行 - **应用场景**:依赖核心子系统的早期驱动(如总线类型注册) 4. **arch_initcall** - **级别**:LEVEL=3 - **特点**:架构相关初始化 - **应用场景**:CPU/SoC特定功能(如ARM的缓存配置、x86的ACPI表解析) --- ### 二、同步与非同步版本差异 所有宏均有 `*_sync` 变体(如 `core_initcall_sync`): - **非同步版本**:允许同一级别的初始化函数并行执行(若内核配置了 `CONFIG_SMP`) - **同步版本**:强制同一级别的所有函数完成后,再执行下一级别 - **典型用途**: ```c // 并行初始化可能引发竞态时使用同步版本 core_initcall_sync(init_func); // 确保所有core_initcall完成 ``` --- ### 三、技术实现原理 通过 `__define_initcall` 宏定义优先级: ```c #define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define postcore_initcall(fn) __define_initcall(fn, 2) #define arch_initcall(fn) __define_initcall(fn, 3) ``` 内核启动时遍历 `initcall` 段,按优先级顺序执行函数。 --- ### 四、实际开发建议 1. **避免滥用高级别宏**:非必要不使用 `pure_initcall`,减少启动延迟 2. **依赖关系管理**: - 若驱动依赖总线子系统,使用 `postcore_initcall` - 若驱动与硬件架构强相关,使用 `arch_initcall` 3. **调试技巧**:通过内核参数 `initcall_debug` 跟踪执行顺序: ```bash sudo dmesg | grep "initcall" ``` --- ### 五、完整执行顺序参考 完整层级(共8级): $$ \text{pure} \rightarrow \text{core} \rightarrow \text{postcore} \rightarrow \text{arch} \rightarrow \text{subsys} \rightarrow \text{fs} \rightarrow \text{device} \rightarrow \text{late} $$ 每个层级对应不同子系统,合理选择可优化启动速度和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值