0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

冬至子 来源:linux与soc 作者:linux与soc 2023-06-05 11:38 次阅读

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_initcall
subsys_initcall
core_initcall
fs_initcall
arch_initcall
...

在kernel代码中存在着大量的*_initcall修饰的函数。不同种类的initcall函数进行统计,如下图所示:

图片

initcall统计

2. 实现原理

initcall设计思想如下:

  1. 在生成vmlinux的链接阶段为initcall创建特定的section
  2. 开发者创建相关的initcall函数,并使用xxx_initcall声明为不同类型
  3. 每一类initcall对应一组section
  4. 遍历执行initcall section中的initcalls

xxx_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_debug

CMDLINE中增加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) < rostedt@goodmis.org > 2018-03-23 10:18:03 -0400
committer Steven Rostedt (VMware) < rostedt@goodmis.org > 2018-04-06 08:56:54 -0400
commit 4ee7c60de83ac01fa4c33c55937357601631e8ad (patch)
---
init, tracing: Add initcall trace events
Being able to trace the start and stop of initcalls is useful to see where
the timings are an issue. There is already an "initcall_debug" parameter,
but that can cause a large overhead itself, as the printing of the
information may take longer than the initcall functions.

Adding in a start and finish trace event around the initcall functions, as
well as a trace event that records the level of the initcalls, one can get a
much finer measurement of the times and interactions of the initcalls
themselves, 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
 ...
[...]
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux系统
    +关注

    关注

    4

    文章

    567

    浏览量

    26924
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21389
  • GNU
    GNU
    +关注

    关注

    0

    文章

    141

    浏览量

    17333
收藏 人收藏

    评论

    相关推荐

    介绍一下PID参数的基本调试方法

    在使用伺服驱动器过程中,我们都需要进行驱动器的调试。其中最关键的参数调试莫过于PID参数整定了。很多小白在这方面往往经验欠缺,不知从何入手,这里介绍一下PID参数的基本调试
    发表于 09-17 09:34

    长虹PC-5机芯背投彩电调试方法介绍

    长虹PC-5机芯背投彩电调试方法介绍,详细说明。
    发表于 09-18 16:26 19次下载

    TR5001T设备介绍及程序调试方法

    TR5001设备介绍及程序调试方法和程序调试技巧。
    发表于 06-16 18:21 0次下载

    python断点调试方法

    本文主要介绍了python断点调试方法,pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步
    发表于 01-14 10:44 7109次阅读
    python断点<b class='flag-5'>调试</b><b class='flag-5'>方法</b>

    介绍利用Keil的软件仿真功能来实现51单片机串口调试用户程序的方法

    下面介绍一种利用Keil的软件仿真功能来实现51单片机串口调试用户程序的方法。使用这种方法,无需任何硬件仿真器,甚至都不需要用户电路板。
    的头像 发表于 02-03 09:16 3w次阅读
    <b class='flag-5'>介绍</b>利用Keil的软件仿真功能来<b class='flag-5'>实现</b>51单片机串口<b class='flag-5'>调试</b>用户程序的<b class='flag-5'>方法</b>

    chipscope使用教程以及FPGA在线调试方法

    本文档内容介绍了基于chipscope使用教程以及FPGA在线调试方法,供参考
    发表于 03-02 14:09 9次下载

    友善串口调试助手怎么使用及使用方法说明

    本文首先介绍了友善串口调试助手主要特点及功能,其次详细介绍了一般串口调试助手使用教程,最后介绍了友善串口
    的头像 发表于 05-23 08:48 12.4w次阅读
    友善串口<b class='flag-5'>调试</b>助手怎么使用及使用<b class='flag-5'>方法</b>说明

    MATLAB程序调试方法及工具介绍

    MATLAB程序设计之MATLAB程序调试方法及工具介绍
    的头像 发表于 07-13 17:50 6165次阅读
    MATLAB程序<b class='flag-5'>调试</b>的<b class='flag-5'>方法</b>及工具<b class='flag-5'>介绍</b>

    keil的51单片机仿真调试中如何查看内存的内容实现方法说明

    本文档的主要内容详细介绍的是keil的51单片机仿真调试中如何查看内存的内容实现方法说明。
    发表于 07-09 17:40 3次下载
    keil的51单片机仿真<b class='flag-5'>调试</b>中如何查看内存的内容<b class='flag-5'>实现</b><b class='flag-5'>方法</b>说明

    shell脚本常用的调试方法介绍

    软件、配置编译环境,可以说使用起来非常的方便,但是它在调试方面常常令人头大,本文主要介绍shell脚本常用的调试方法 调试常用选项
    的头像 发表于 09-01 10:43 2992次阅读

    教程 8:调试方法

    教程 8:调试方法
    发表于 03-16 19:00 1次下载
    教程 8:<b class='flag-5'>调试</b><b class='flag-5'>方法</b>

    单片机常用的调试方法

    在单片机程序调试过程中,串口打印调试方法是非常重要的手段,在使用串口调试时,我们更多的是使用printf。但是下面我们不介绍printf,
    的头像 发表于 04-04 14:58 3774次阅读

    EtherCAT运动控制卡的辅助调试工具与方法介绍

    EtherCAT运动控制卡的辅助调试工具与方法介绍
    的头像 发表于 11-15 18:52 1756次阅读
    EtherCAT运动控制卡的辅助<b class='flag-5'>调试</b>工具与<b class='flag-5'>方法</b><b class='flag-5'>介绍</b>

    介绍FPGA在线调试的一大利器—VIO

    之前的文章介绍了FPGA在线调试方法,包括选定抓取信号,防止信号被优化的方法等等。
    发表于 06-20 10:38 3817次阅读
    <b class='flag-5'>介绍</b>FPGA在线<b class='flag-5'>调试</b>的一大利器—VIO

    教程 8:调试方法

    教程 8:调试方法
    发表于 07-06 19:46 0次下载
    教程 8:<b class='flag-5'>调试</b><b class='flag-5'>方法</b>