这篇帖子讲述了我为Raspberry Pi 3 (RPI)实现快速启动的旅程。此外,还讨论了一些可以应用于Qt (QML)应用程序的优化方法。最终,我们将从上电到Linux shell的启动时间缩短到1.75秒,从上电到Qt (QML)应用程序的启动时间缩短到2.82秒。
编辑: 有用户请求支持USB和网络功能的演示镜像。我将在空闲时间进行这项工作。你也可以自己尝试。如果遇到问题,请不要犹豫,从下面的支持链接提问。我简要地讨论了这个话题在这里。
技术支持: github.com/furkantokac/buildroot/issues
项目文件: github.com/furkantokac/buildroot
演示镜像: github.com/furkantokac/buildroot/releases
你可以在第6部分查看演示镜像的详细信息。
大纲
1. 引言
2. 项目需求
3. Raspberry 启动文件
4. Raspberry 启动优化
K1 - Raspberry 启动阶段
K2 - Linux 预启动阶段
K3 - Linux 启动阶段
K4 - 初始化系统
K5 - 应用程序
5. 更多优化!
6. 要点汇总
7. 结果
8. 参考文献
1. 引言
首先,我们需要充分了解目标设备,因为启动优化过程的一些关键阶段是低级别的(硬件依赖的)。我们需要能够回答诸如设备的启动顺序是什么、哪些文件按什么顺序运行以启动设备、哪些文件是绝对必需的等问题。此外,优化应该逐项进行和测试,以便可以看到效果。
RPI的启动过程与其他传统设备不同。RPI的启动过程基于GPU而非CPU。建议你在互联网上深入研究这个话题。(参见1,9)
RPI使用Broadcom的闭源芯片作为片上系统(SoC)。因此,与SoC相关的软件以二进制形式提供给我们。(参见2)这意味着没有逆向工程就无法定制它们。这就是为什么RPI启动优化过程中最困难的部分是与SoC相关的内容。
2. 项目需求
- 使用RPI作为设备。
- 使用Buildroot进行Linux的定制。
- RPI的GPIO和UART应当可用。
- GPIO和UART在Qt中应当可用。
- Qt (QML) 应用程序应自动启动。
3. Raspberry 启动文件
与RPI启动过程相关的文件及其用途简要如下:
- bootcode.bin:这是第二阶段引导加载程序,由制造商嵌入RPI中的第一阶段引导加载程序运行。运行在GPU上。激活RAM。其目的是运行start.elf(第三阶段引导加载程序)。
- config.txt:包含GPU设置。由start.elf使用。
- cmdline.txt:包含启动内核时传递给内核的参数。由start.elf使用。
- .dtb:编译后的设备树文件。它包含设备的硬件描述,如GPIO引脚、显示端口等。由start.elf和kernel.img使用。
- start.elf:这是由bootcode.bin运行的第三阶段引导加载程序。它包含GPU驱动程序。其目的是在GPU和CPU之间分配RAM,将config.txt文件中的设置应用到GPU,通过读取相应的.dtb文件进行必要的调整,并使用cmdline.txt文件中的参数运行kernel.img。执行这些操作后,它将在设备上作为GPU驱动程序运行,直到设备关闭。
- kernel.img:这是Linux内核,由start.elf运行。内核运行后,我们对一切有了完全的控制。
- 基本逻辑:RPI上电 -> RPI内部嵌入的软件运行 -> bootcode.bin运行 -> start.elf运行 -> 读取config.txt -> 读取.dtb -> 读取cmdline.txt -> kernel.img运行
4. Raspberry 启动优化
RPI从上电到Qt应用程序启动的过程如下:
K1 - Raspberry 启动阶段(第一和第二阶段引导加载程序)(bootcode.bin)
K2 - Linux 预启动阶段(第三阶段引导加载程序)(start.elf, bcm2710-rpi-3-b.dtb)
K3 - Linux 启动阶段(kernel.img)
K4 - 初始化系统(BusyBox)
K5 - 应用程序(Qt QML)
K1 - Raspberry 启动阶段
在这个部分,制造商嵌入设备中的软件运行bootcode.bin。由于bootcode.bin是闭源的,我们不能直接配置它,所以我们可以做两件事。要么尝试不同的bootcode.bin版本,要么尝试更改由bootcode.bin运行的文件。(我们忽略逆向工程)
我们去看RPI的Git页面(参见12),看到没有不同版本的bootcode.bin可用。我们查看bootcode.bin的旧提交并尝试旧版本,发现速度没有变化。我们可以转到另一个选项。
让我们检查start.elf。在RPI的Git页面上,我们看到有不同版本的start.elf文件:start_cd.elf、start_db.elf、start_x.elf。我们查看这些文件的差异,发现start_cd.elf是start.elf的简化版本。在start_cd.elf中,GPU功能被裁剪,这可能会引起问题,但让我们试一试。当我们用start_cd.elf替换我们的start.elf时,启动过程比以前快了0.5秒。然而,当我们运行Qt应用程序时,它失败了。为什么会失败,我们能修复吗?我们的GUI应用程序运行在OpenGL ES上,而start_cd.elf没有为GPU分配足够的内存。尽管我们尝试克服这一困难,但未成功,但我相信如果投入更多时间,可以解决这个问题。
K2 - Linux 预启动阶段
这部分由start.elf处理。由于start.elf是闭源的,我们不能直接对其进行操作,但有一些与start.elf相关的开源文件:bcm2710-rpi-3-b.dtb、kernel.img
我们可以做的第一件事是检查这些文件是否减慢了start.elf的速度。当我们将Device Tree移除时,内核无法启动。这里有两种可能性;问题是出在start.elf还是内核。我们需要找到一种方法来测试它。一个无需Device Tree即可运行的应用程序可以解决问题,这是一个基础的RPI应用程序。如果我们编写一个小应用程序,并让start.elf运行这个应用程序而不是内核,就可以看出移除Device Tree是否会带来任何速度变化。第二个选项是编译U-Boot并运行U-Boot而不是内核,但第一个选项更好。我们编写了一个基础的LED闪烁应用程序(参见13)。当我们运行它时,我们发现移除Device Tree使启动过程快了1.0秒。我们还尝试更改Device Tree的默认名称(bcm2710-rpi-3-b.dtb),仍然可以正常工作。所以结论是:即使我们不启动内核,Device Tree也会由start.elf处理,并且start.elf特别搜索名为“bcm2710-rpi-3-b.dtb”的Device Tree。总结一下,我们要么去掉Device Tree,要么通过更改其名称来使用它。
设备树选项的重命名可以按以下方式进行;我们可以编写一个由 start.elf 运行的基础软件,并通过使用重命名的设备树来处理内核启动过程。由于我们需要在这里运行额外的代码,因此会有时间损失。因此,让我们检查另一个选项,即取消设备树。
我们在测试中看到,设备树对于启动内核是绝对必要的,但不是 start.elf 所必需的。如果设备树与内核相关,我们可以通过某种方式尝试将设备树配置硬编码到内核中。当我们搜索关于设备树的信息时,发现内核已经存在类似的选项。(参见 3 第11页)当我们进行了必要的设置(K3 包含了此设置的信息)后,我们发现内核可以成功启动。让我们测试一切是否正常工作。
测试后,我们观察到:
- Qt 应用程序工作正常。
- UART 停止工作。
- 我们发现内核的启动时间慢了0.7秒。
让我们检查一下 UART 的问题。我们将 UART 出现问题的内核启动日志和 UART 正常运行的内核启动日志保存下来。(这里所说的启动日志是指“dmesg”命令的输出)。当我们比较日志时,发现从“内核命令行:”开始的行有所不同。在 UART 正常运行的系统中,“8250.nr_uarts = 1” 参数传递给了内核。我们将这个参数添加到问题内核的 cmdline.txt 文件中,UART 就完美地工作了。让我们继续解决其他问题。
我们应该检查是什么导致了大约1.0秒的启动延迟。我们将再次使用相同的日志。当我们比较有问题系统的日志和没有问题系统的日志时,发现有问题的系统中多了一条包含“random”的日志,延迟就在这里。当我们逐个关闭内核中的“random”设置时,找到了问题所在。(K3 包含了此设置的信息)关闭该设置后,我们看到一切恢复了正常。任务完成。
结果,启动过程加快了约2.0秒。K2 的总耗时为0.25秒。我们可以在这里继续优化,比如优化设备树,但我认为通过进入下一步会更高效地利用时间,所以让我们继续前进。
K3 - Linux 启动阶段
我们在 K2 部分解释了一些内核优化。本部分列出了我们已修改的内核特性。要了解特定特性如何影响内核启动过程,请访问项目的 Git 页面(参见 5),并根据需要在线进行详细的设置研究。
启用的特性
ARM_APPENDED_DTB : 嵌入设备树以加快启动速度。
ARM_ATAG_DTB_COMPAT : 必须将某些参数传递给内核。
禁用的特性
NET
SOUND
HW_RANDOM # 0.7秒
ALLOW_DEV_COREDUMP # 0.2秒(Core Release: 2.80a)
STRICT_KERNEL_RWX #===\ 0.1秒
STRICT_MODULE_RWX #===/
NAMESPACES # 0.1秒
FTRACE # 0.5秒
# 禁用 USB 支持
USB_SUPPORT
# 禁用调试
BLK_DEBUG_FS
DEBUG_BUGVERBOSE
DEBUG_FS
DEBUG_MEMORY_INIT
SLUB_DEBUG
PRINTK
BUG
DM_DELAY
ELF_CORE
KGDB
PRINT_QUOTA_WARNING
AUTOFS4_FS
# 下面这些主要影响大小
MEDIA_DIGITAL_TV_SUPPORT
MEDIA_ANALOG_TV_SUPPORT
MEDIA_CAMERA_SUPPORT
MEDIA_RADIO_SUPPORT
INPUT_MOUSEDEV
INPUT_JOYDEV
INPUT_JOYSTICK
INPUT_TABLET
INPUT_TOUCHSCREEN
IIO
RC_CORE
HID_LOGITECH
HID_MAGICMOUSE
HID