0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

initcall實現(xiàn)原理和調(diào)試方法介紹

冬至子 ? 來源:linux與soc ? 作者:linux與soc ? 2023-06-05 11:38 ? 次閱讀

0. 介紹

linux kernel啟動過程中,通過initcall機制調(diào)用初始化函數(shù)。initcall作為kernel經(jīng)典設(shè)計機制之一延續(xù)至今。在2018年,Steven Rostedt為了跟蹤各個初始化函數(shù)的執(zhí)行時間,增加了tracing功能。

在本篇文章中,將會介紹initcall的意義和使用方法、實現(xiàn)原理、執(zhí)行流程以及調(diào)試方法。

1. 意義和使用方法

正如文章最開始的地方所描述的那樣,其直接意義是在kernel啟動過程中執(zhí)行不同的初始化函數(shù),涉及到不同架構(gòu)下的CPU初始化以及各種外設(shè)驅(qū)動的初始化。

由于使用initcalls不需要顯示的傳遞、存儲和調(diào)用函數(shù)指針,我們只需要將函數(shù)標記為合適的initcall類型,內(nèi)核代碼就幫助我們完成了各函數(shù)的遍歷執(zhí)行,因此,基于initcall機制,可以使得代碼更具模塊化屬性以及更高的可維護性。

kernel中的基于initcall機制定義的初始化代碼遵循固定的規(guī)則:使用__init進行修飾,然后通過xxx_initcall聲明為不同的類型。

static int __init register_cpufreq_notifier(void)
{
...
}
core_initcall(register_cpufreq_notifier);

每一個initcall函數(shù)都通過不同的前綴加以修飾,例如:

pure_initcall
subsys_initcall
core_initcall
fs_initcall
arch_initcall
...

在kernel代碼中存在著大量的*_initcall修飾的函數(shù)。不同種類的initcall函數(shù)進行統(tǒng)計,如下圖所示:

圖片

initcall統(tǒng)計

2. 實現(xiàn)原理

initcall設(shè)計思想如下:

  1. 在生成vmlinux的鏈接階段為initcall創(chuàng)建特定的section
  2. 開發(fā)者創(chuàng)建相關(guān)的initcall函數(shù),并使用xxx_initcall聲明為不同類型
  3. 每一類initcall對應一組section
  4. 遍歷執(zhí)行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)

從上面的宏定義可以發(fā)現(xiàn),所有的xxx_initcall都是基于__define_initcall的,后者的定義位于同一個文件中,通過__define_initcall將各個xxx_initcall統(tǒng)一到一起,基于ID編號鏈接到不同的subsection,在同一個subsection中各個initcall的排序以鏈接的順序為準。

另外,__define_initcall中的ID編號還有另外一個作用,就是防止不同類型的xxx_initcall調(diào)用相同的符號引起編譯錯誤。

#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的實現(xiàn)細節(jié),如下圖所示,注意,在倒數(shù)第二個框圖內(nèi)可以看出來initcall機制使用到了GNU編譯工具鏈的屬性。

圖片

initcall實現(xiàn)細節(jié)

4. 執(zhí)行流程

根據(jù)前面的介紹,當xxx_initcall被鏈接到目標文件后,會生成不同類別的section,包含不同的initcall函數(shù),如下所示:

.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執(zhí)行順序由編譯順序決定,不同類的initcall執(zhí)行順序在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函數(shù)所對應的符號。

圖片

initcall符號表排布

基于以上分析,整理出initcalls的完整執(zhí)行流程如下:

圖片

initcall完整執(zhí)行流程

5. 調(diào)試方法

你可能會遇到kernel啟動時間特別長,而在啟動過程中會加載很多的initcalls,此時,該如何下手呢?

5.1 initcall_debug

CMDLINE中增加initcall_debug選項

console=ttyS0,115200...initcall_debug

打開CMDLINE選項

圖片

結(jié)果:

[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是一個不錯的調(diào)試手段,可以用來檢測各initcall的執(zhí)行時間。然而,當內(nèi)核打印級別設(shè)置的不合適時,這些調(diào)試日志會直接打印在控制臺上,并且和其他日志信息混雜到了一起,便會顯得雜亂無章。

5.2 ftrace

如果是2018年以后的內(nèi)核(4.16.0-rc4),則可以基于ftrace分析initcall的執(zhí)行情況。

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相關(guān)功能,CMDLINE中增加trace選項

console=ttyS0,...trace_event=initcall:initcall_level,initcall:initcall_start,initcall:initcall_finish

結(jié)果:

# 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
 ...
[...]
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux系統(tǒng)
    +關(guān)注

    關(guān)注

    4

    文章

    588

    瀏覽量

    27274
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    316

    瀏覽量

    21586
  • GNU
    GNU
    +關(guān)注

    關(guān)注

    0

    文章

    143

    瀏覽量

    17438
收藏 人收藏

    評論

    相關(guān)推薦

    介紹6種常見的反調(diào)試方法

    開發(fā)相應的安全措施來保護系統(tǒng),這時,惡意軟件開發(fā)人員就會使用反調(diào)試技術(shù)阻礙逆向人員的分析,以達到增加自己惡意代碼的存活時間。此外,安全人員也需要了解反調(diào)試技術(shù),當遇到反調(diào)試代碼時,可以使用相對應的反反
    的頭像 發(fā)表于 01-15 09:53 ?3302次閱讀
    <b class='flag-5'>介紹</b>6種常見的反<b class='flag-5'>調(diào)試</b><b class='flag-5'>方法</b>

    介紹一下PID參數(shù)的基本調(diào)試方法

    在使用伺服驅(qū)動器過程中,我們都需要進行驅(qū)動器的調(diào)試。其中最關(guān)鍵的參數(shù)調(diào)試莫過于PID參數(shù)整定了。很多小白在這方面往往經(jīng)驗欠缺,不知從何入手,這里介紹一下PID參數(shù)的基本調(diào)試
    發(fā)表于 09-17 09:34

    長虹PC-5機芯背投彩電調(diào)試方法介紹

    長虹PC-5機芯背投彩電調(diào)試方法介紹,詳細說明。
    發(fā)表于 09-18 16:26 ?19次下載

    采用MATLAB的DSP調(diào)試方法

    本文結(jié)合具體例證,介紹基于MATLAB 的DSP 應用程序調(diào)試方法。 MATLAB 具有強大的分析、計算和可視化功能,利用MATLAB 提供的數(shù)十個專業(yè)工具箱,可以方便、靈活地實現(xiàn)
    發(fā)表于 06-07 08:39 ?2963次閱讀
    采用MATLAB的DSP<b class='flag-5'>調(diào)試</b><b class='flag-5'>方法</b>

    TR5001T設(shè)備介紹及程序調(diào)試方法

    TR5001設(shè)備介紹及程序調(diào)試方法和程序調(diào)試技巧。
    發(fā)表于 06-16 18:21 ?0次下載

    python斷點調(diào)試方法

    本文主要介紹了python斷點調(diào)試方法,pdb 是 python 自帶的一個包,為 python 程序提供了一種交互的源代碼調(diào)試功能,主要特性包括設(shè)置斷點、單步
    發(fā)表于 01-14 10:44 ?7242次閱讀
    python斷點<b class='flag-5'>調(diào)試</b><b class='flag-5'>方法</b>

    介紹利用Keil的軟件仿真功能來實現(xiàn)51單片機串口調(diào)試用戶程序的方法

    下面介紹一種利用Keil的軟件仿真功能來實現(xiàn)51單片機串口調(diào)試用戶程序的方法。使用這種方法,無需任何硬件仿真器,甚至都不需要用戶電路板。
    的頭像 發(fā)表于 02-03 09:16 ?3.1w次閱讀
    <b class='flag-5'>介紹</b>利用Keil的軟件仿真功能來<b class='flag-5'>實現(xiàn)</b>51單片機串口<b class='flag-5'>調(diào)試</b>用戶程序的<b class='flag-5'>方法</b>

    chipscope使用教程以及FPGA在線調(diào)試方法

    本文檔內(nèi)容介紹了基于chipscope使用教程以及FPGA在線調(diào)試方法,供參考
    發(fā)表于 03-02 14:09 ?9次下載

    友善串口調(diào)試助手怎么使用及使用方法說明

    本文首先介紹了友善串口調(diào)試助手主要特點及功能,其次詳細介紹了一般串口調(diào)試助手使用教程,最后介紹了友善串口
    的頭像 發(fā)表于 05-23 08:48 ?12.7w次閱讀
    友善串口<b class='flag-5'>調(diào)試</b>助手怎么使用及使用<b class='flag-5'>方法</b>說明

    MATLAB程序調(diào)試方法及工具介紹

    MATLAB程序設(shè)計之MATLAB程序調(diào)試方法及工具介紹。
    的頭像 發(fā)表于 07-13 17:50 ?6748次閱讀
    MATLAB程序<b class='flag-5'>調(diào)試</b>的<b class='flag-5'>方法</b>及工具<b class='flag-5'>介紹</b>

    keil的51單片機仿真調(diào)試中如何查看內(nèi)存的內(nèi)容實現(xiàn)方法說明

    本文檔的主要內(nèi)容詳細介紹的是keil的51單片機仿真調(diào)試中如何查看內(nèi)存的內(nèi)容實現(xiàn)方法說明。
    發(fā)表于 07-09 17:40 ?3次下載
    keil的51單片機仿真<b class='flag-5'>調(diào)試</b>中如何查看內(nèi)存的內(nèi)容<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>方法</b>說明

    shell腳本常用的調(diào)試方法介紹

    軟件、配置編譯環(huán)境,可以說使用起來非常的方便,但是它在調(diào)試方面常常令人頭大,本文主要介紹shell腳本常用的調(diào)試方法 調(diào)試常用選項
    的頭像 發(fā)表于 09-01 10:43 ?3207次閱讀

    單片機常用的調(diào)試方法

    在單片機程序調(diào)試過程中,串口打印調(diào)試方法是非常重要的手段,在使用串口調(diào)試時,我們更多的是使用printf。但是下面我們不介紹printf,
    的頭像 發(fā)表于 04-04 14:58 ?4682次閱讀

    EtherCAT運動控制卡的輔助調(diào)試工具與方法介紹

    EtherCAT運動控制卡的輔助調(diào)試工具與方法介紹
    的頭像 發(fā)表于 11-15 18:52 ?3016次閱讀
    EtherCAT運動控制卡的輔助<b class='flag-5'>調(diào)試</b>工具與<b class='flag-5'>方法</b><b class='flag-5'>介紹</b>

    如何實現(xiàn)工業(yè)設(shè)備遠程調(diào)試方法

    隨著工業(yè)4.0的推進,智能化、網(wǎng)絡化、遠程化已經(jīng)成為現(xiàn)代工業(yè)設(shè)備的重要發(fā)展方向。其中,遠程調(diào)試作為一種有效的技術(shù)手段,能夠顯著提高設(shè)備維護效率、降低成本,具有非常重要的實踐意義。本文將探討如何實現(xiàn)
    的頭像 發(fā)表于 09-14 10:03 ?1057次閱讀