initcall实现原理和调试方法介绍

0. 介绍在linux kernel启动过程中,通过initcall机制调用初始化函数。initcall作为kernel经典设计机制之一延续至今。在2018年,steven rostedt为了跟踪各个初始化函数的执行时间,增加了tracing功能。
在本篇文章中,将会介绍initcall的意义和使用方法、实现原理、执行流程以及调试方法。
1. 意义和使用方法正如文章最开始的地方所描述的那样,其直接意义是在kernel启动过程中执行不同的初始化函数,涉及到不同架构下的cpu初始化以及各种外设驱动的初始化。
由于使用initcalls不需要显示的传递、存储和调用函数指针,我们只需要将函数标记为合适的initcall类型,内核代码就帮助我们完成了各函数的遍历执行,因此,基于initcall机制,可以使得代码更具模块化属性以及更高的可维护性。
kernel中的基于initcall机制定义的初始化代码遵循固定的规则:使用__init进行修饰,然后通过xxx_initcall声明为不同的类型。
static int __init register_cpufreq_notifier(void){...}core_initcall(register_cpufreq_notifier);每一个initcall函数都通过不同的前缀加以修饰,例如:
pure_initcallsubsys_initcallcore_initcallfs_initcallarch_initcall...在kernel代码中存在着大量的*_initcall修饰的函数。不同种类的initcall函数进行统计,如下图所示:
initcall统计
2. 实现原理initcall设计思想如下:
在生成vmlinux的链接阶段为initcall创建特定的section开发者创建相关的initcall函数,并使用xxx_initcall声明为不同类型每一类initcall对应一组section遍历执行initcall section中的initcallsxxx_initcall的定义位于include/linux/init.h中,从这个文件的名字也可以看出xxx_initcall是针对初始化操作的。
#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)从上面的宏定义可以发现,所有的xxx_initcall都是基于__define_initcall的,后者的定义位于同一个文件中,通过__define_initcall将各个xxx_initcall统一到一起,基于id编号链接到不同的subsection,在同一个subsection中各个initcall的排序以链接的顺序为准。
另外,__define_initcall中的id编号还有另外一个作用,就是防止不同类型的xxx_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)以rockchip_grf_init()为例拆解分析xxx_initcall的实现细节,如下图所示,注意,在倒数第二个框图内可以看出来initcall机制使用到了gnu编译工具链的属性。
initcall实现细节
4. 执行流程根据前面的介绍,当xxx_initcall被链接到目标文件后,会生成不同类别的section,包含不同的initcall函数,如下所示:
.initcallearly.init 0000000000000008 __initcall_trace_init_flags_sys_exitearly.initcall0.init 0000000000000008 __initcall_ipc_ns_init0.initcall1.init 0000000000000008 __initcall_map_entry_trampoline1.initcall2.init 0000000000000008 __initcall_bdi_class_init2.initcall3.init 0000000000000008 __initcall_dma_bus_init3.initcall4.init 0000000000000008 __initcall_fbmem_init4.initcall5.init 0000000000000008 __initcall_chr_dev_init5.initcall6.init 0000000000000008 __initcall_hwrng_modinit6.initcall7.init 0000000000000008 __initcall_deferred_probe_initcall7.initcallrootfs.init 0000000000000008 __initcall_populate_rootfsrootfs同一类的initcall执行顺序由编译顺序决定,不同类的initcall执行顺序在init/main.c中定义,如下所示:
static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end,};在include/asm-generic/vmlinux.lds.h中将xxx_start和.initcall*.init链接到了一起,do_initcalls()遍历不同id的initcall时,基于xxx_start找到相对应的.initcall entry,之后遍历各个initcalls。
#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) = .;在arch/arm64/kernel/vmlinux.lds中可以看到initcall的符号排布如下图所示,基于*_start可以定位到各个initcall函数所对应的符号。
initcall符号表排布
基于以上分析,整理出initcalls的完整执行流程如下:
initcall完整执行流程
5. 调试方法你可能会遇到kernel启动时间特别长,而在启动过程中会加载很多的initcalls,此时,该如何下手呢?
5.1 initcall_debugcmdline中增加initcall_debug选项
console=ttys0,115200...initcall_debug打开cmdline选项
结果:
[root@rk3399:/]# dmesg | grep initcall[ 0.000000] kernel command line: initcall_debug storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal androidboot.slot_suffix= androidboot.serialno=d3143e5cd395b593 rw rootwait earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 console=ttyfiq0 root=partuuid=614e0000-0000 rootfstype=ext4 coherent_pool=1m[ 0.126902] initcall trace_init_flags_sys_exit+0x0/0x1c returned 0 after 0 usecs......[ 0.227475] initcall rockchip_grf_init+0x0/0x12c returned 0 after 976 usecs[ 0.227515] initcall rockchip_pm_domain_drv_register+0x0/0x20 returned 0 after 0 usecs......[ 10.106112] initcall hci_uart_init+0x0/0x1000 [hci_uart_aw] returned 0 after 2840 usecs[root@rk3399:/]#虽然initcall_debug是一个不错的调试手段,可以用来检测各initcall的执行时间。然而,当内核打印级别设置的不合适时,这些调试日志会直接打印在控制台上,并且和其他日志信息混杂到了一起,便会显得杂乱无章。
5.2 ftrace如果是2018年以后的内核(4.16.0-rc4),则可以基于ftrace分析initcall的执行情况。
author steven rostedt (vmware) 2018-03-23 10:18:03 -0400committer steven rostedt (vmware) 2018-04-06 08:56:54 -0400commit 4ee7c60de83ac01fa4c33c55937357601631e8ad (patch)---init, tracing: add initcall trace eventsbeing able to trace the start and stop of initcalls is useful to see wherethe timings are an issue. there is already an initcall_debug parameter,but that can cause a large overhead itself, as the printing of theinformation may take longer than the initcall functions.adding in a start and finish trace event around the initcall functions, aswell as a trace event that records the level of the initcalls, one can get amuch finer measurement of the times and interactions of the initcallsthemselves, as trace events are much lighter than printk()s.打开trace相关功能,cmdline中增加trace选项
console=ttys0,...trace_event=initcall:initcall_level,initcall:initcall_start,initcall:initcall_finish结果:
# mount -t debugfs nodev /sys/kernel/debug# cat /sys/kernel/debug/tracing/trace# tracer: nop## entries-in-buffer/entries-written: 1090/1090 #p:4## _-----= > irqs-off# / _----= > need-resched# | / _---= > hardirq/softirq# || / _--= > preempt-depth# ||| / delay# task-pid cpu# |||| timestamp function# | | | |||| | | -0 [000] .... 0.000125: initcall_level: level=console -0 [000] .... 0.000136: initcall_start: func=con_init+0x0/0x220 -0 [000] .... 0.000232: initcall_finish: func=con_init+0x0/0x220 ret=0 -0 [000] .... 0.000235: initcall_start: func=univ8250_console_init+0x0/0x3c -0 [000] .... 0.000246: initcall_finish: func=univ8250_console_init+0x0/0x3c ret=0 swapper/0-1 [000] .... 0.002016: initcall_level: level=early swapper/0-1 [000] .... 0.002026: initcall_start: func=trace_init_flags_sys_exit+0x0/0x24 ...[...]

4+64g,首发联发科x30,2699元起,魅族pro7曝光
雷声公司推出面向中程防空的新型GhostEye MR雷达
ISSCC 2019,思特威成为图像传感领域首次入选的中国企业!
风力发电的优缺点
健身房智能镜子显示器可自动记录每一天身体的变化
initcall实现原理和调试方法介绍
名门锁业滑盖指纹锁EZ06A简介
微软亚洲研究院"创新汇": AI为数字化转型注入动能
LED价格贴近成本价 2016年LED跌价空间有限
人脸识别技术的五大发展趋势
aigo固态硬盘S500的上手体验:花小钱带来大满足
AN-653: 改善高动态范围均方根射频功率检波器的温度稳定性和线性度
DM7276/DM7275高精度直流电压计的功能特性及特点
英国首个人工智能技术开发出的药物即将进入临床测试阶段 AI制药将获突破
Art-Pi+TMC2209步进电机细分控制测试
电动汽车的平台化
华为智慧屏V65尊爵版开启预售 售价7999元
将socket程序从linux移植到windows上
重磅!新材料技术应用大会即将召开!齐聚30+院士聚焦材料前沿
3公里远程调频发射机电路