[linux 技术分享][启动调试][initcall_debug 与 ignore_loglevel]

在 Linux 内核启动调试中,initcall_debugignore_loglevel 是关键的启动参数,可深度追踪初始化过程和日志输出,下面以魔方派 3 Ubuntu/Debian 系统使用的 6.8 内核为例说明。

initcall_debug

参数启用后,内核会打印所有初始化函数和驱动 probe 等函数的执行细节,包括函数返回值、耗时和运行的进程 ID。这对定位启动卡顿或初始化失败至关重要,如下所示:

[    7.042579] probe of 100000000.gpio-regulator returned -517 after 0 usecs
[    7.042587] probe of 0.gpio-regulator returned -517 after 0 usecs
[    7.042702] calling  gpio_regulator_init+0x0/0x50 @ 1
[    7.042799] initcall gpio_regulator_init+0x0/0x50 returned 0 after 1000 usecs

启用方法:

  1. 编辑板子中的 /etc/default/grub 文件,在 GRUB_CMDLINE_LINUX 变量中追加 initcall_debug,如下所示:

    GRUB_CMDLINE_LINUX="root=LABEL=writable rw console=ttyMSM0,115200n8 pcie_pme=nomsi earlycon ignore_loglevel initcall_debug"

  2. 使用如下命令更新 grub 的配置,重启生效,若想在开机时将调试信息通过串口打印出来,可在 GRUB_CMDLINE_LINUX 变量中加入 ignore_loglevel 参数;

    sudo update-grub
    sudo reboot
    

或在开机时在 GRUB 菜单按下 e 键,使用新的启动参数启动内核。

源码分析:

init/main.c

bool initcall_debug;
core_param(initcall_debug, initcall_debug, bool, 0644);

若内核启动参数中定义了 initcall_debug,则会将 initcall_debug 变量置为 true,在 start_kernel 函数中,调用 initcall_debug_enable
最终通过 trace_initcall_start_cbtrace_initcall_finish_cb 函数打印初始化函数的执行参数,如下所示:

void start_kernel(void)
{

...
	if (initcall_debug)
		initcall_debug_enable();
...

}

static void __init initcall_debug_enable(void)
{
	int ret;

	ret = register_trace_initcall_start(trace_initcall_start_cb,
					    &initcall_calltime);
	ret |= register_trace_initcall_finish(trace_initcall_finish_cb,
					      &initcall_calltime);
	WARN(ret, "Failed to register initcall tracepoints\n");
}

static __init_or_module void
trace_initcall_start_cb(void *data, initcall_t fn)
{
	ktime_t *calltime = data;

	printk(KERN_DEBUG "calling  %pS @ %i\n", fn, task_pid_nr(current));
	*calltime = ktime_get();
}

static __init_or_module void
trace_initcall_finish_cb(void *data, initcall_t fn, int ret)
{
	ktime_t rettime, *calltime = data;

	rettime = ktime_get();
	printk(KERN_DEBUG "initcall %pS returned %d after %lld usecs\n",
		 fn, ret, (unsigned long long)ktime_us_delta(rettime, *calltime));
}

对于 probe 函数的信息打印则是在 __driver_probe_device 函数通过,initcall_debug 变量判断调用 really_probereally_probe_debug 函数:
drivers/base/dd.c

static int __driver_probe_device(struct device_driver *drv, struct device *dev)
{
...
	if (initcall_debug)
		ret = really_probe_debug(dev, drv);
	else
		ret = really_probe(dev, drv);
...
}

ignore_loglevel

强制在终端输出所有级别的内核日志,避免因日志级别过滤丢失调试信息。适用于早期启动问题排查。

启用方法

  1. 编辑板子中的 /etc/default/grub 文件,在 GRUB_CMDLINE_LINUX 变量中追加 ignore_loglevel,如下所示:

    GRUB_CMDLINE_LINUX="root=LABEL=writable rw console=ttyMSM0,115200n8 pcie_pme=nomsi earlycon ignore_loglevel initcall_debug"

  2. 使用如下命令更新 grub 的配置,重启生效;

    sudo update-grub
    sudo reboot
    

或在开机时在 GRUB 菜单按下 e 键,使用新的启动参数启动内核。

源码分析

kernel/printk/printk.c

static bool __read_mostly ignore_loglevel;

static int __init ignore_loglevel_setup(char *str)
{
	ignore_loglevel = true;
	pr_info("debug: ignoring loglevel setting.\n");

	return 0;
}

early_param("ignore_loglevel", ignore_loglevel_setup);
module_param(ignore_loglevel, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ignore_loglevel,
		 "ignore loglevel setting (prints all kernel messages to the console)");

static bool suppress_message_printing(int level)
{
	return (level >= console_loglevel && !ignore_loglevel);
}


ignore_loglevel 加入启动参数后,根据上述代码可以发现 ignore_loglevel 变量会置为 true,那么 suppress_message_printing 函数会始终返回 false,表示不禁用日至显示;

同时在开机后,也可通过 /sys/module/printk/parameters/ignore_loglevel 这个节点随时设置 ignore_loglevel 参数:

cat /sys/module/printk/parameters/ignore_loglevel   # 查看当前
echo 1 > /sys/module/printk/parameters/ignore_loglevel   # 设置为 true
echo 0 > /sys/module/printk/parameters/ignore_loglevel  # 设置为 false

更多的内核参数可查看内核源码中的 Documentation/admin-guide/kernel-parameters.txt 文件查看。

更新日期:2025.06.30

1 个赞