本文主要分析linux内核如何顺序调用init_call初始化函数,所使用的内核版本为linux5.0.5。
一.内核按级别调用初始化函数过程 linux内核initcall调用的入口函数为do_initcalls(),该函数位于init/main.c中。
该函数根据级别通过do_initcall_level函数依次进行所有级别的初始化工作,在linux内核该级别定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 const char *initcall_level_names[] __initdata = { "pure" , "core" , "postcore" , "arch" , "subsys" , "fs" , "device" , "late" , };
initcall_levels数组中总共0-7这八个级别,通过对应的initcall_level_names数组可知其类别。数组中每一项为该级别中第一个初始化函数的地址,在do_initcall_level函数中通过for循环调用do_one_initcall函数调用一个级别中的所有初始化函数。
1 2 for (fn = initcall_levels[level]; fn < initcall_levels[level+1 ]; fn++) do_one_initcall(initcall_from_entry(fn));
do_one_initcall函数传入参数为initcall_t类型,该类型在内核中的定义如下:
1 2 3 4 5 typedef int (*initcall_t ) (void ) ;typedef void (*exitcall_t ) (void ) ;
由以上定义可知,内核模块和子系统入口和出口函数的格式。
函数do_one_initcall所做的工作:
do_one_initcall函数首先判断要调用的函数是否在“黑名单”中,如果在则不调用这个初始化函数。
1 2 if (initcall_blacklisted(fn)) return -EPERM;
紧接着调用该初始化函数指针。
1 2 3 do_trace_initcall_start(fn); ret = fn(); do_trace_initcall_finish(fn, ret);
然后判断优先级是否被改变,中断是否被屏蔽,并根据判断结果做相应的恢复工作。
1 2 3 4 5 6 7 8 if (preempt_count() != count) { sprintf (msgbuf, "preemption imbalance " ); preempt_count_set(count); } if (irqs_disabled()) { strlcat(msgbuf, "disabled interrupts " , sizeof (msgbuf)); local_irq_enable(); }
上面代码片段中的count是在do_one_initcall开始的时候进行保存的。
1 int count = preempt_count();
二.初始化入口函数设置 以内核USB子系统为例,usb子系统的初始化设置如下:
1 subsys_initcall(usb_init);
由以上代码可知,设置子系统初始化函数的宏为subsys_initcall,该宏定义在内核init.h中。
1 #define subsys_initcall(fn) __define_initcall(fn, 4)
__define_initcall宏的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS #define ___define_initcall(fn, id, __sec) \ __ADDRESSABLE(fn) \ asm (".section \"" #__sec ".init\", \"a\" \n" \ "__initcall_" #fn #id ": \n" \ ".long " #fn " - . \n" \ ".previous \n" ); #else #define ___define_initcall(fn, id, __sec) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(#__sec ".init" ))) = fn; #endif #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
完全展开该宏后可知将usb_init函数放到了.initcall4.init段。该工作由架构目录下内核链接脚本完成。根据以上步骤分析module_init宏得知模块初始化放到.initcall6.init段,说明模块初始化优先级低于子系统初始化,而内核正是按照这种优先级别完成顺序初始化工作的。