Linux 的module_init运行加载

本文详细介绍了Linux内核中的内存段,如text、data、bss和init段的用途。重点讨论了init段在kernel启动过程中的作用,展示了kernel启动时内存的布局,并通过System.map展示了__initcall_start等函数入口地址。通过module_init宏定义的展开,揭示了内核初始化调用层次和流程。最后,通过一个实际例子展示了驱动加载的跟踪,证实了do_one_initcall在驱动初始化中的关键角色。

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

Linux 的module_init运行加载

段说明

text段

代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定。

data段

数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

bss段

通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态内存分配。

init段

linux定义的一种初始化过程中才会用到的段,一旦初始化完成,那么这些段所占用的内存会被释放掉,

kernel启动过程中打印的内存相关信息

[    0.000000] Memory: 416732K/521952K available (9216K kernel code, 432K rwdata, 2280K rodata, 1024K init, 943K bss, 23300K reserved, 81920K cma-reserved, 0K highmem)
[    0.000000] Virtual kernel memory layout:
[    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
[    0.000000]     fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
[    0.000000]     vmalloc : 0xd0800000 - 0xff800000   ( 752 MB)
[    0.000000]     lowmem  : 0xb0000000 - 0xd0000000   ( 512 MB)
[    0.000000]     pkmap   : 0xafe00000 - 0xb0000000   (   2 MB)
[    0.000000]     modules : 0xaf000000 - 0xafe00000   (  14 MB)
[    0.000000]       .text : 0x(ptrval) - 0x(ptrval)   (10208 kB)
[    0.000000]       .init : 0x(ptrval) - 0x(ptrval)   (1024 kB)
[    0.000000]       .data : 0x(ptrval) - 0x(ptrval)   ( 433 kB)
[    0.000000]        .bss : 0x(ptrval) - 0x(ptrval)   ( 944 kB)

我们可以在vmlinux.lds中看到init.data的断包含了.initcallearly.init之类,这个就是我们上面通过宏定义定义的函数___define_initcall

 .init.data : AT(ADDR(.init.data) - 0) { KEEP(*(SORT(___kentry+*))) *(.init.data init.data.*) *(.meminit.data*) *(.init.rodata .init.rodata.*) *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; KEEP(*(__clk_of_table)) KEEP(*(__clk_of_table_end)) . = ALIGN(8); __reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8); __timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end)) . = ALIGN(8); __cpu_method_of_table = .; KEEP(*(__cpu_method_of_table)) KEEP(*(__cpu_method_of_table_end)) . = ALIGN(8); __cpuidle_method_of_table = .; KEEP(*(__cpuidle_method_of_table)) KEEP(*(__cpuidle_method_of_table_end)) . = ALIGN(32); __dtb_start = .; KEEP(*(.dtb.init.rodata)) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; KEEP(*(__irqchip_of_table)) KEEP(*(__irqchip_of_table_end)) . = ALIGN(8); __earlycon_table = .; KEEP(*(__earlycon_table)) __earlycon_table_end = .; . = ALIGN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .; __initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) __initcall0s_start = .; KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(.initcall1.init)) __initcall1s_start = .; KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) __initcall2s_start = .; KEEP(*(.initcall2s.init)) __initcall3_start = .; KEEP(*(.initcall3.init)) __initcall3s_start = .; KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(*(.initcall4.init)) __initcall4s_start = .; KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) __initcall5s_start = .; KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) __initcallrootfss_start = .; KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) __initcall6s_start = .; KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init)) __initcall7s_start = .; KEEP(*(.initcall7s.init)) __initcall_end = .; __con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_initcall_end = .; __security_initcall_start = .; KEEP(*(.security_initcall.init)) __security_initcall_end = .; . = ALIGN(4); __initramfs_start = .; KEEP(*(.init.ramfs)) . = ALIGN(8); KEEP(*(.init.ramfs.info)) }
 .exit.data : {
  *(.exit.data .exit.data.*) *(.fini_array .fini_array.*) *(.dtors .dtors.*) *(.memexit.data*) *(.memexit.rodata*)
 }

通过System.map中可以看到,定义了__initcall_start 函数入口地址

b0d4dc48 T __initcall_start
b0d4dc48 T __setup_end
b0d4dc4c t __initcall_init_static_idmapearly
b0d4dc50 t __initcall_spawn_ksoftirqdearly
b0d4dc54 t __initcall_migration_initearly
b0d4dc58 t __initcall_check_cpu_stall_initearly
b0d4dc5c t __initcall_srcu_bootup_announceearly
b0d4dc60 t __initcall_rcu_spawn_gp_kthreadearly
b0d4dc64 t __initcall_cpu_stop_initearly
b0d4dc68 t __initcall_jump_label_init_moduleearly
b0d4dc6c t __initcall_dummy_timer_registerearly
b0d4dc70 t __initcall_initialize_ptr_randomearly
b0d4dc74 T __initcall0_start

module_init的宏定义

#define module_init(x)	__initcall(x);
    -->#define __initcall(fn) device_initcall(fn)
    	-->#define device_initcall(fn)		__define_initcall(fn, 6)
    		-->#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
    			-->  #define ___define_initcall(fn, id, __sec) \
						static initcall_t __initcall_##fn##id __used \
						__attribute__((__section__(#__sec ".init"))) = fn; \
    					typedef int (*initcall_t)(void); initcall_t是个函数指针

内核的init加载函数

 // Only for built-in code, not modules.
/*
 * 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 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 console_initcall(fn)	___define_initcall(fn, con, .con_initcall)
#define security_initcall(fn)	___define_initcall(fn, security, .security_initcall)
//外部定义了__initcall_start相关参数,在System.map当中可以找到__initcall_start的地址
extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
extern initcall_entry_t __initcall1_start[];
extern initcall_entry_t __initcall2_start[];
extern initcall_entry_t __initcall3_start[];
extern initcall_entry_t __initcall4_start[];
extern initcall_entry_t __initcall5_start[];
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];

static initcall_entry_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};
static char *initcall_level_names[] __initdata = {
	"pure",
	"core",
	"postcore",
	"arch",
	"subsys",
	"fs",
	"device",
	"late",
};

do_basic_setup
    -->do_initcalls
    	-->	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) \  initcall_levels是上面的函数指针数组的大小
				do_initcall_level(level);
			-->	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) \
					do_one_initcall(initcall_from_entry(fn)); initcall_from_entry--> *fn
				-->ret = fn();
do_one_initcall时,实际上就是取函数的地址,可以追踪到System.map中__initcall0_start,在上述已经列出

根据dump_stack()追踪驱动的的加载流程,我们可以看出跟我们跟踪的代码是一致的,执行do_one_initcall后,执行了gpioctl_driver_init,,符合流程

[    2.041595] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 4.19.111 #1074
[    2.041661] Hardware name: Generic DT based system
[    2.041711] [<b010f408>] (unwind_backtrace) from [<b010b96c>] (show_stack+0x10/0x14)
[    2.041753] [<b010b96c>] (show_stack) from [<b098cc44>] (dump_stack+0x90/0xa4)
[    2.041794] [<b098cc44>] (dump_stack) from [<b0d23654>] (gpioctl_driver_init+0x8/0x34)
[    2.041830] [<b0d23654>] (gpioctl_driver_init) from [<b010265c>] (do_one_initcall+0x54/0x194)
[    2.041866] [<b010265c>] (do_one_initcall) from [<b0d00edc>] (kernel_init_freeable+0x144/0x1dc)
[    2.041900] [<b0d00edc>] (kernel_init_freeable) from [<b09a1524>] (kernel_init+0x8/0x110)
[    2.041932] [<b09a1524>] (kernel_init) from [<b01010d8>] (ret_from_fork+0x14/0x3c)
[    2.041948] Exception stack(0xcec75fb0 to 0xcec75ff8)
[    2.041971] 5fa0:                                     00000000 00000000 00000000 00000000
[    2.041998] 5fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[    2.042024] 5fe0: 00000000 00000000 00000000 00000000 00000013 00000000
<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`将函数嵌入初始化流程,由内核启动时调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值