Linux kernel 模块初始化()与核心初始化调用()与早期初始化调用()的比较
在驱动程序中,我经常看到使用这三种类型的init函数Linux kernel 模块初始化()与核心初始化调用()与早期初始化调用()的比较,linux-kernel,linux-device-driver,init,Linux Kernel,Linux Device Driver,Init,在驱动程序中,我经常看到使用这三种类型的init函数 module_init() core_initcall() early_initcall() 在什么情况下我应该使用它们 还有,还有其他的初始化方法吗 用于标记要用作Linux设备驱动程序入口点的函数。 它被称为 期间(对于内置驱动程序) 或 插入模块时(对于*.ko模块) 每个驱动程序模块只能有一个模块_init() *\u initcall()函数通常用于设置初始化各种子系统的函数指针 包含对各种initcall列表的调用,以及在
module_init()
core_initcall()
early_initcall()
它被称为
- 期间(对于内置驱动程序)
或 - 插入模块时(对于
模块)*.ko
模块_init()
*\u initcall()
函数通常用于设置初始化各种子系统的函数指针
包含对各种initcall列表的调用,以及在Linux内核引导期间调用它们的相对顺序
early\u initcall()
core\u initcall()
postcore\u initcall()
arch\u initcall()
subsys\u initcall()
fs\u initcall()
device\u initcall()
late\u initcall()
内置模块的结束
*.ko
模块的modprobe
或insmod
module_init()
请记住,在编译过程中,Linux内核中链接各种驱动程序对象文件(*.o
)的顺序非常重要;它确定在运行时调用它们的顺序
*\u initcall
同级功能
将在引导期间按链接顺序调用
例如,在drivers/SCSI/Makefile
中更改SCSI驱动程序的链接顺序将更改检测SCSI控制器的顺序,从而更改磁盘的编号。它们确定内置模块的初始化顺序。大多数情况下,驱动程序将使用设备_initcall
(或模块_init
;见下文)。早期初始化(Early_initcall
)通常由特定于体系结构的代码用于在初始化任何实际驱动程序之前初始化硬件子系统(电源管理、DMA等)
下面是需要理解的技术资料
看。通过arch/)中的代码进行一些特定于体系结构的初始化后:
#定义模块_init(x)_initcall(x);
然后我们遵循以下步骤:
\define\u initcall(fn)设备\u initcall(fn)
#定义设备初始化调用(fn)\定义初始化调用(fn,6)
所以,现在,module\u init(my\u func)
意味着\u define\u initcall(my\u func,6)
。这是\u define\u initcall
:
\define\u define\u initcall(fn,id)\
静态initcall_t__initcall_#fn#id_u使用\
__属性uuuu((uuu节uuuuu(“.initcall”#id.init”))=fn
也就是说,到目前为止,我们已经:
static initcall\u t\u initcall\u my\u func6\u使用
__属性uuuu((uuu节uuuu(.initcall6.init))=myfunc;
哇,有很多GCC的东西,但这只意味着创建了一个新的符号,\uuuuuInitCall\uMy\uFunc6
,它放在名为.initcall6.init
的ELF部分,正如您所看到的,它指向指定的函数(my\uFunc
)。将所有函数添加到此部分最终将创建完整的函数指针数组,所有函数指针都存储在.initcall6.init
ELF部分中
初始化示例
再看看这一块:
for(fn=initcall_levels[level];fn
让我们来看第6级,它表示所有用module\u init
初始化的内置模块。它从\uuuu initcall6\u start
开始,其值为.initcall6.init
部分中注册的第一个函数指针的地址,并在\uuu initcall7\u start
结束(不包括),每次都以*fn
的大小递增(这是一个initcall\t
,它是一个void*
,它是32位还是64位取决于体系结构)
do\u one\u initcall
将只调用当前条目指向的函数
在一个特定的初始化部分中,决定初始化函数为什么在另一个之前被调用的原因仅仅是makefile中文件的顺序,因为链接器将在各自的ELF init.部分中依次连接\uuuuuu initcall.*
符号
这个事实实际上在内核中使用,例如设备驱动程序():
tl;dr:Linux内核初始化机制非常漂亮,尽管它突出了GCC依赖性。似乎没有人关注链接器脚本是如何配置为提供用于初始化内核代码的函数指针的,所以让我们看看Linux内核为init调用创建链接器脚本有多漂亮
因为上面伟大的答案显示了Linux C代码如何创建和管理所有initcall,比如如何将函数定义为initcall、访问已定义函数的全局变量,以及在初始化阶段实际调用已定义initcall的函数,我不想再次讨论它们
因此,在这里,我们想重点讨论名为initcall\u levels[]的全局数组变量的每个元素是如何定义的,它意味着什么,initcall\u levels数组的每个元素指向的内存中包含什么,等等
首先,让我们尝试了解变量在Linux内核存储库中的定义位置。当您查看init/main.c文件时,您会发现initcall_levels数组的所有元素都没有在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[];
然而,你可以发现
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[];
#define __VMLINUX_SYMBOL(x) _##x
#define __VMLINUX_SYMBOL_STR(x) "_" #x
#else
#define __VMLINUX_SYMBOL(x) x
#define __VMLINUX_SYMBOL_STR(x) #x
#endif
/* Indirect, so macros are expanded before pasting. */
#define VMLINUX_SYMBOL(x) __VMLINUX_SYMBOL(x)
#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) = .;
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
#define INIT_DATA_SECTION(initsetup_align) \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
SECURITY_INITCALL \
INIT_RAM_FS \
}
//ordering nm result numerical order
$nm -n vmlinux > symbol
$vi symbol
ffffffff828ab1c8 T __initcall0_start
ffffffff828ab1c8 t __initcall_ipc_ns_init0
ffffffff828ab1d0 t __initcall_init_mmap_min_addr0
ffffffff828ab1d8 t __initcall_evm_display_config0
ffffffff828ab1e0 t __initcall_init_cpufreq_transition_notifier_list0
ffffffff828ab1e8 t __initcall_jit_init0
ffffffff828ab1f0 t __initcall_net_ns_init0
ffffffff828ab1f8 T __initcall1_start
ffffffff828ab1f8 t __initcall_xen_pvh_gnttab_setup1
ffffffff828ab200 t __initcall_e820__register_nvs_regions1
ffffffff828ab208 t __initcall_cpufreq_register_tsc_scaling1
......
ffffffff828ab3a8 t __initcall___gnttab_init1s
ffffffff828ab3b0 T __initcall2_start
ffffffff828ab3b0 t __initcall_irq_sysfs_init2
ffffffff828ab3b8 t __initcall_audit_init2
ffffffff828ab3c0 t __initcall_bdi_class_init2
static int __init ipc_ns_init(void)
{
const int err = shm_init_ns(&init_ipc_ns);
WARN(err, "ipc: sysv shm_init_ns failed: %d\n", err);
return err;
}
pure_initcall(ipc_ns_init);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);