内置平台驱动程序 __initcall 未在 Linux 内核初始化时调用
Builtin Platform Driver __initcall Not Called on Linux Kernel Init
背景
我正在通过 Yocto 为一些供应商提供的嵌入式硬件调出一个 Linux 内核。我已将映像配置为通过带有 initramfs 和 no rootfs 的 fitImage 启动(有持久存储,但这完全供用户 space 应用程序使用)。想想 PXE 实时映像,您就离得不远了。
在我的 initramfs 映像超过 ~128MB 标记之前,一切进展顺利。在此之下,一切都按预期启动,所有驱动程序都没有问题。在这个标记之上,内核仍然可以启动,但许多驱动程序(虽然不是全部)未被绑定。这非常令人费解,因为所有驱动程序都是静态内置到内核中的(该平台上没有使用任何模块)。不幸的是,这些模块之一运行的平台看门狗会导致完全可预测的重启。
到目前为止,我已验证所有符号都存在于 vmlinux 映像中:
$ objdump -x vmlinux | grep mtk_wdt
0000000000000000 l df *ABS* 0000000000000000 mtk_wdt.c
ffffff800880ac40 l F .text 000000000000004c mtk_wdt_stop
ffffff800880ac90 l F .text 0000000000000040 mtk_wdt_shutdown
ffffff80091de778 l F .init.text 0000000000000020 mtk_wdt_driver_init
ffffff800880acd0 l F .text 000000000000004c mtk_wdt_ping
ffffff800880ad20 l F .text 0000000000000070 mtk_wdt_set_timeout
ffffff800880ad90 l F .text 0000000000000074 mtk_wdt_start
ffffff800880ae08 l F .text 0000000000000144 mtk_wdt_resume
ffffff800880af50 l F .text 0000000000000120 mtk_wdt_suspend
ffffff800880b070 l F .text 0000000000000080 mtk_wdt_remove
ffffff80088977a8 l F .text 0000000000000210 mtk_wdt_isr
ffffff80091fe0a0 l F .exit.text 000000000000001c mtk_wdt_driver_exit
ffffff800880b4f0 l F .text 0000000000000310 mtk_wdt_probe
ffffff8008c2acd8 l O .rodata 0000000000000028 mtk_wdt_info
ffffff8008c2ad00 l O .rodata 0000000000000050 mtk_wdt_ops
ffffff8008c2ad98 l O .rodata 00000000000000b8 mtk_wdt_pm_ops
ffffff8008c2ae50 l O .rodata 0000000000000190 mtk_wdt_dt_ids
ffffff80093a3cb8 l O .data 00000000000000b0 mtk_wdt_driver
ffffff800a199368 l O .bss 0000000000000008 mtk_wdt1
ffffff8009285598 l O .init.data 0000000000000008 __initcall_mtk_wdt_driver_init6
此外,我在 fitImage 程序集之前、fitImage 程序集之后和解压缩到系统内存之后(通过引导加载程序)中有二进制内核(例如 linux.bin)、initramfs 和设备树的 sha256 校验和;全部匹配。据我所知,构建的内容就是解压缩和启动的内容。
此外,我启用了 initcall_debug
,当我看到其他 __initcall() 时,未绑定的驱动程序毫无疑问地丢失了。
我知道设备存在于设备树中并且配置正确。启动后,在看门狗启动之前,我有大约 5 秒的控制台访问时间;刚好有足够的时间来执行一两个命令。在“工作”图像(initramfs < ~128MB)和失败图像(initramfs > ~128MB)上,/sys/bus/platform/devices
的内容是相同的,我可以看到(除其他外),我的看门狗:
$ ls -lha /sys/bus/platform/devices
...
lrwxrwxrwx 1 root root 0 Jan 1 00:00 10007000.watchdog -> ../../../devices/platform/10007000.watchdog
执行相同的测试,但比较 /sys/bus/platform/devices
显示未 __initcall() 的驱动程序缺失。
我检查过的一些集体其他事情:
- 设备树。工作图像和损坏图像都使用相同的 DTB。我还如上所述验证了内存中的设备树。不使用设备树覆盖。
- 实际内存加载偏移量。一切都在应该在的地方,每个地区都有很多 space。我可以在内存中移动内核,但无论位置如何,问题仍然存在。
- 记性不好。此故障在多个单元中同样发生。
- 压缩错误。无论内核/initramfs 压缩如何,该问题都会出现。目前,我正在测试未压缩的所有内容,以尽量减少破损点。
- 签名错误。我已禁用签名验证(毕竟已应用于 fitImage 分区映像);那里也没有骰子。
- 正在尝试将 initramfs 直接捆绑到内核中。没变化。现在我有 fitImage 内置的 initramfs,但在其他方面独立加载和验证。
- 错误的内核命令行参数。我正在使用
root=/dev/ram initrd=0x48000000,384M
并一直追踪到 init/initramfs.c
解包完成的地方。我能够验证传递的偏移量确实在正确的虚拟内存中 space 并且总和为 384M。
- 根据 this 论坛 post 更新内核链接描述文件。我能够通过 objdump 在 vmlinux 中看到生成的
.initramfs
部分,但问题仍然存在。
综上所述,我唯一不知道如何验证的是从 vmlinux 到 linux.bin 的跳转。这是通过 objcopy 在 Yocto 中完成的,如下所示:
[ -n "${vmlinux_path}" ] && ${OBJCOPY} -O binary -R .note -R .comment -S "${vmlinux_path}" linux.bin
问题
- 如何验证给定符号是否包含在最终 linux.bin 中?
- 什么机制会影响在构建时包含或排除给定符号?
- 内核构建和运行时的哪些部分受 initramfs 大小影响?
- 是否有任何其他工具/技术/部落智慧可以帮助调试这种情况?
编辑 1:
下面是基本的内存映射,其中包含所有内容和 space 利用率。正如上面和评论中提到的,我可以将内核、DTB 和 initramfs 重新定位到(几乎)任意位置,但问题仍然存在。
0x40000000 - 0x40001000 = Bootloader arg area (Fixed usage)
0x40080000 - 0x41EDFFFF = Kernel (~12MB / 29.5MB used)
0x41E00000 - 0x42FF5FFF = Trampoline (96 bytes / ~6MB used)
0x42FF6000 - 0x42FFFFFF = ATF BL3-1 (Fixed usage)
0x43000000 - 0x43FFFFFF = Trusted OS (~476K / 16M used)
0x44000000 - 0x44FFFFFF = DTB (~77.3K / 16M used)
0x45000000 - 0x47FFFFFF = Trusted OS memory (dynamic)
0x48000000 - 0x5FFFFFFF = Initramfs (~129MB / 384MB used)
0x60000000 - MEM END = Free
所以,像大多数内核问题一样,真正的问题并不在我认为的地方。事实证明,问题是由 init 列表中较早的其他 driver 之一挂起核心引起的,从而阻止了看门狗 driver 的注册。 initramfs 如何影响这超出了我的范围,这是它自己的问题。
对于以后遇到此问题的任何人,下面列出了我上面的具体问题的答案:
- 如何验证给定符号是否包含在最终 linux.bin 中?
我不知道如何静态地执行此操作。也就是说,通过将 printk()
s 添加到 init/main.c
中的 do_initcall_level
,我能够在运行时打印 init 函数的地址。然后可以将打印的地址与 vmlinux 上 objdump 的输出进行比较(请参阅我的咒语问题)。
可以找到关于 initcall 进程的非常有用且 in-depth 的描述 here。
请注意,您还可以打开 initcall_debug
,这将打印每个函数名称。在我的例子中,我想要原始地址,这就是我选择 printk()
方法的原因。
- 什么机制会影响构建时包含或排除给定符号?
其中大部分归结为您的 .config。绝大多数包含/排除是通过预处理器完成的。其他有用的项目是 include/asm-generic/vmlinux.lds.h
处的通用链接描述文件 header 和设备 arch/<arch>/*/*.lds
.
处的平台链接描述文件
- 内核构建和运行时的哪些部分受 initramfs 大小影响?
对此一无所知。
- 是否有任何其他工具/技术/部落智慧可以帮助调试这种情况?
不要惊慌
背景
我正在通过 Yocto 为一些供应商提供的嵌入式硬件调出一个 Linux 内核。我已将映像配置为通过带有 initramfs 和 no rootfs 的 fitImage 启动(有持久存储,但这完全供用户 space 应用程序使用)。想想 PXE 实时映像,您就离得不远了。
在我的 initramfs 映像超过 ~128MB 标记之前,一切进展顺利。在此之下,一切都按预期启动,所有驱动程序都没有问题。在这个标记之上,内核仍然可以启动,但许多驱动程序(虽然不是全部)未被绑定。这非常令人费解,因为所有驱动程序都是静态内置到内核中的(该平台上没有使用任何模块)。不幸的是,这些模块之一运行的平台看门狗会导致完全可预测的重启。
到目前为止,我已验证所有符号都存在于 vmlinux 映像中:
$ objdump -x vmlinux | grep mtk_wdt
0000000000000000 l df *ABS* 0000000000000000 mtk_wdt.c
ffffff800880ac40 l F .text 000000000000004c mtk_wdt_stop
ffffff800880ac90 l F .text 0000000000000040 mtk_wdt_shutdown
ffffff80091de778 l F .init.text 0000000000000020 mtk_wdt_driver_init
ffffff800880acd0 l F .text 000000000000004c mtk_wdt_ping
ffffff800880ad20 l F .text 0000000000000070 mtk_wdt_set_timeout
ffffff800880ad90 l F .text 0000000000000074 mtk_wdt_start
ffffff800880ae08 l F .text 0000000000000144 mtk_wdt_resume
ffffff800880af50 l F .text 0000000000000120 mtk_wdt_suspend
ffffff800880b070 l F .text 0000000000000080 mtk_wdt_remove
ffffff80088977a8 l F .text 0000000000000210 mtk_wdt_isr
ffffff80091fe0a0 l F .exit.text 000000000000001c mtk_wdt_driver_exit
ffffff800880b4f0 l F .text 0000000000000310 mtk_wdt_probe
ffffff8008c2acd8 l O .rodata 0000000000000028 mtk_wdt_info
ffffff8008c2ad00 l O .rodata 0000000000000050 mtk_wdt_ops
ffffff8008c2ad98 l O .rodata 00000000000000b8 mtk_wdt_pm_ops
ffffff8008c2ae50 l O .rodata 0000000000000190 mtk_wdt_dt_ids
ffffff80093a3cb8 l O .data 00000000000000b0 mtk_wdt_driver
ffffff800a199368 l O .bss 0000000000000008 mtk_wdt1
ffffff8009285598 l O .init.data 0000000000000008 __initcall_mtk_wdt_driver_init6
此外,我在 fitImage 程序集之前、fitImage 程序集之后和解压缩到系统内存之后(通过引导加载程序)中有二进制内核(例如 linux.bin)、initramfs 和设备树的 sha256 校验和;全部匹配。据我所知,构建的内容就是解压缩和启动的内容。
此外,我启用了 initcall_debug
,当我看到其他 __initcall() 时,未绑定的驱动程序毫无疑问地丢失了。
我知道设备存在于设备树中并且配置正确。启动后,在看门狗启动之前,我有大约 5 秒的控制台访问时间;刚好有足够的时间来执行一两个命令。在“工作”图像(initramfs < ~128MB)和失败图像(initramfs > ~128MB)上,/sys/bus/platform/devices
的内容是相同的,我可以看到(除其他外),我的看门狗:
$ ls -lha /sys/bus/platform/devices
...
lrwxrwxrwx 1 root root 0 Jan 1 00:00 10007000.watchdog -> ../../../devices/platform/10007000.watchdog
执行相同的测试,但比较 /sys/bus/platform/devices
显示未 __initcall() 的驱动程序缺失。
我检查过的一些集体其他事情:
- 设备树。工作图像和损坏图像都使用相同的 DTB。我还如上所述验证了内存中的设备树。不使用设备树覆盖。
- 实际内存加载偏移量。一切都在应该在的地方,每个地区都有很多 space。我可以在内存中移动内核,但无论位置如何,问题仍然存在。
- 记性不好。此故障在多个单元中同样发生。
- 压缩错误。无论内核/initramfs 压缩如何,该问题都会出现。目前,我正在测试未压缩的所有内容,以尽量减少破损点。
- 签名错误。我已禁用签名验证(毕竟已应用于 fitImage 分区映像);那里也没有骰子。
- 正在尝试将 initramfs 直接捆绑到内核中。没变化。现在我有 fitImage 内置的 initramfs,但在其他方面独立加载和验证。
- 错误的内核命令行参数。我正在使用
root=/dev/ram initrd=0x48000000,384M
并一直追踪到init/initramfs.c
解包完成的地方。我能够验证传递的偏移量确实在正确的虚拟内存中 space 并且总和为 384M。 - 根据 this 论坛 post 更新内核链接描述文件。我能够通过 objdump 在 vmlinux 中看到生成的
.initramfs
部分,但问题仍然存在。
综上所述,我唯一不知道如何验证的是从 vmlinux 到 linux.bin 的跳转。这是通过 objcopy 在 Yocto 中完成的,如下所示:
[ -n "${vmlinux_path}" ] && ${OBJCOPY} -O binary -R .note -R .comment -S "${vmlinux_path}" linux.bin
问题
- 如何验证给定符号是否包含在最终 linux.bin 中?
- 什么机制会影响在构建时包含或排除给定符号?
- 内核构建和运行时的哪些部分受 initramfs 大小影响?
- 是否有任何其他工具/技术/部落智慧可以帮助调试这种情况?
编辑 1:
下面是基本的内存映射,其中包含所有内容和 space 利用率。正如上面和评论中提到的,我可以将内核、DTB 和 initramfs 重新定位到(几乎)任意位置,但问题仍然存在。
0x40000000 - 0x40001000 = Bootloader arg area (Fixed usage)
0x40080000 - 0x41EDFFFF = Kernel (~12MB / 29.5MB used)
0x41E00000 - 0x42FF5FFF = Trampoline (96 bytes / ~6MB used)
0x42FF6000 - 0x42FFFFFF = ATF BL3-1 (Fixed usage)
0x43000000 - 0x43FFFFFF = Trusted OS (~476K / 16M used)
0x44000000 - 0x44FFFFFF = DTB (~77.3K / 16M used)
0x45000000 - 0x47FFFFFF = Trusted OS memory (dynamic)
0x48000000 - 0x5FFFFFFF = Initramfs (~129MB / 384MB used)
0x60000000 - MEM END = Free
所以,像大多数内核问题一样,真正的问题并不在我认为的地方。事实证明,问题是由 init 列表中较早的其他 driver 之一挂起核心引起的,从而阻止了看门狗 driver 的注册。 initramfs 如何影响这超出了我的范围,这是它自己的问题。
对于以后遇到此问题的任何人,下面列出了我上面的具体问题的答案:
- 如何验证给定符号是否包含在最终 linux.bin 中?
我不知道如何静态地执行此操作。也就是说,通过将 printk()
s 添加到 init/main.c
中的 do_initcall_level
,我能够在运行时打印 init 函数的地址。然后可以将打印的地址与 vmlinux 上 objdump 的输出进行比较(请参阅我的咒语问题)。
可以找到关于 initcall 进程的非常有用且 in-depth 的描述 here。
请注意,您还可以打开 initcall_debug
,这将打印每个函数名称。在我的例子中,我想要原始地址,这就是我选择 printk()
方法的原因。
- 什么机制会影响构建时包含或排除给定符号?
其中大部分归结为您的 .config。绝大多数包含/排除是通过预处理器完成的。其他有用的项目是 include/asm-generic/vmlinux.lds.h
处的通用链接描述文件 header 和设备 arch/<arch>/*/*.lds
.
- 内核构建和运行时的哪些部分受 initramfs 大小影响?
对此一无所知。
- 是否有任何其他工具/技术/部落智慧可以帮助调试这种情况?
不要惊慌