Linux系统初始化与U-boot引导加载器全解析
1. Linux系统初始化
1.1 内核初始化与启动流程
在系统启动时,内核需要对设备进行初始化以满足自身需求。在初始化过程中,内核会输出大量描述其操作的消息。接着,内核会挂载根文件系统,此时内核运行在内核空间。最后,内核启动 init 进程,从而过渡到用户空间。
1.2 Systemd初始化机制
内核启动的最后一步是启动PID为1的进程,该进程将成为系统中所有其他进程的最终父进程。过去,这一过程相对简单,涉及可执行文件 /sbin/init 、配置文件 inittab 和一些脚本,这就是System V初始化机制。
现在,有一种名为 systemd 的“改进版”初始化机制,它包含约900个文件。目前,大多数主流Linux发行版都采用了 systemd ,但自2011年引入以来,它一直是一个颇具争议的话题。支持者认为System V init速度太慢,因为它是串行启动进程,一次只启动一个;而 systemd 可以并行处理很多活动。反对者则认为“如果没坏,就别修它”。
systemd 管理和操作名为“单元”的对象,其中最常见的单元类型是服务,由以 .service 结尾的文件表示。可以查看目标文件系统的 /lib/systemd/system 目录,会看到许多 *.service 文件。例如,打开 bonescript.service 文件, WorkingDirectory 通常包含可执行文件的支持文件,由 ExecStart 指定可执行文件。
可以使用 systemctl 命令来管理 systemd ,常见命令如下表所示:
| 命令 | 描述 |
| — | — |
| start <unit> | 启动指定的单元 |
| stop <unit> | 停止指定的单元 |
| restart <unit> | 重启指定的单元。如果未运行,则启动它 |
| reload <unit> | 要求指定的单元重新加载其配置文件 |
| status <unit> | 显示指定单元的状态 |
| enable <unit> | 创建符号链接,使单元在启动时自动启动 |
| disable <unit> | 删除导致单元在启动时启动的符号链接 |
| daemon-reload | 重新加载 systemd 管理器的配置。每次更改 systemd 文件时都要运行此命令 |
作为 systemctl 的首次实验,直接输入不带参数的 systemctl 命令,会得到BeagleBone Black(BBB)上所有可用服务的列表。 systemctl 会将输出通过管道传输给 more (或 less ),以便逐页显示,可用服务数量众多。
1.3 创建温控服务
目标是让温控器在设备启动时自动执行,这可以通过创建温控服务来实现。在 /home/samples/src 目录下有两个简单脚本: leds-off.sh 用于关闭LED的触发器, adc-on.sh 用于启用A/D转换器。在 src/network 目录下创建 thermo.sh 脚本,如下所示:
#!/bin/bash
#
/home/samples/src/leds-off.sh
/home/samples/src/adc-on.sh
/home/samples/src/network/thermostat_t
复制 bonescript.service 文件,命名为 thermostat.service ,最终版本如下:
[Unit]
Description=Networked thermostat server
[Service]
WorkingDirectory=/home/samples/src/network
ExecStart=/home/samples/src/network/thermo.sh
StandardOutput=tty
SyslogIdentifier=thermo
[Install]
WantedBy=multi-user.target
将该文件保存到 /lib/systemd/system 目录,然后执行以下 systemctl 命令:
systemctl daemon-reload
systemctl enable thermostat.service
systemctl start thermostat.service
systemctl daemon-reload 会让 systemd 识别到添加了 thermostat.service 。 systemctl start 命令会启动温控器, systemctl enable 命令会在 /etc/systemd/system/multi-user.target 中创建一个指向 /lib/systemd/system/thermostat.service 的链接,从而使温控器在启动时自动启动。重启开发板后,内核启动后温控器应该会立即运行。若要恢复命令行操作,删除 /etc/systemd/system/multi-user.wants/thermostat.service 指向 /lib/systemd/system/thermostat.service 的链接即可。
1.4 用户空间初始化的“旧方法”
在Debian 7.4版本中, systemd 被视为“实验性”特性,System V初始化代码仍然存在。查看 uEnvnet.txt 文件,顶部附近定义了 systemd 变量,用于将 init 重定向到 systemd ,该变量在 netargs 定义的末尾使用。若要恢复System V初始化,只需从 netargs 定义中删除该变量。
init 可执行文件通常是 /sbin/init ,不过内核也会搜索其他几个替代位置。 init 从 /etc/inittab 文件获取指令,该文件顶部的注释能很好地说明其工作原理。每个条目有四个由冒号分隔的字段:
| 字段 | 描述 |
| — | — |
| id | 与进程连接的tty。如果进程不使用控制台,则为NULL |
| runlevel | Linux可以启动的七个运行级别中的一个或多个。指定的运行级别将执行此条目 |
| action | init 处理进程的八种方式之一 |
| process | 要运行的程序,包括参数(如果有) |
允许的操作如下:
- once :执行进程一次
- wait :执行进程一次, init 等待进程终止
- askfirst :询问用户是否运行此进程
- sysinit :在其他任何操作执行之前执行这些进程
- respawn :进程终止时重新启动
- restart :类似于 respawn
- shutdown :系统关机时执行这些进程
- ctrlaltdel :当 init 收到 SIGINT 信号(即操作员按下 CTRL-ALT-DEL )时执行
在这种情况下, sysinit 操作是执行 rcS 脚本。
启动应用程序最简单的方法可能是在 inittab 中进行配置。找到文件底部附近的行:
T0:23:respawn:/sbin/getty -L ttyO0 115200 vt102
复制该行并将其修改为:
th:23:respawn:/home/samples/src/network/thermo.sh
由于不需要控制台终端,注释掉以 T0: 开头的现有行。保存 inittab 文件,从 uEnv.txt 的 netargs 定义中删除 ${systemd} ,并将文件替换到引导分区。重置目标设备并启动Linux,应该会看到温控器启动。
另一种启动应用程序的方法是直接替换 /sbin/init ,将其创建为指向应用程序可执行文件的符号链接。对于简单应用程序来说这可能可行,但 init 中有很多功能在最终产品中可能会很有用。
此时还可以考虑基于 BusyBox 构建一个最小的根文件系统。
2. U-boot引导加载器
2.1 U-boot背景
引导加载器是一个程序,它在加载和运行操作系统及其支持基础设施之前进行一些初始化工作。从某种意义上说,引导加载器类似于桌面PC或服务器中的BIOS(基本输入/输出系统),主要区别在于引导加载器在系统上电时执行一次,然后就不再起作用;而BIOS会一直存在以提供低级I/O服务。
桌面Linux系统除了BIOS之外还有引导加载器,现在通常是GRUB(GRand Unified Bootloader)或GRUB2。由于BIOS负责硬件初始化的大部分工作,GRUB本身的作用主要是加载和启动操作系统。如果基于传统的x86 PC硬件构建嵌入式系统,GRUB可能是一个不错的选择。
而BeagleBone Black(BBB)目标板使用一种非常流行、功能强大的开源跨平台引导加载器 u-boot 来启动系统。
u-boot 最初是由Magnus Damm编写的名为 8xxROM 的PowerPC引导加载器,后来Wolfgang Denk将项目转移到Source Forge并更名为 PPCBoot 。Sysgo GmbH曾将源代码短暂分叉为 ARMBoot ,当 ARMBoot 分支合并回 PPCBoot 树时,名称改为 u-boot 。如今, u-boot 支持大约十几种架构和1000多种不同的开发板。 u-boot 的开发与Linux密切相关,它与Linux共享一些头文件,部分源代码源自内核源代码树。
u-boot 支持广泛的命令集,不仅便于引导,还能管理闪存、通过网络下载文件等。其命令集通过环境变量和脚本语言进行扩展。
2.2 安装和配置U-boot
可以从以下git仓库获取当前版本的 u-boot :
git clone git://git.denx.de/u-boot.git
顶级目录中有一个非常详细的 README 文件, doc/ 目录中还有其他 README 文件,主要描述特定开发板的特性。
还有一个针对BeagleBoard主线代码的补丁,将网页浏览器指向:
https://rcn-ee.com/repos/git/u-boot-patches/
选择与从git获取的 u-boot 版本对应的子文件夹,将 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch 下载到 u-boot 目录,然后执行:
patch -p1 < 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch
可能会遇到部分文件补丁失败的情况,但手动修补通常比较容易。
当前的 u-boot 实现支持与Linux内核和 BusyBox 相同的Kconfig配置机制,需要先使用默认配置。下载的 u-boot 版本有针对1000多种开发板的 _defconfigs ,对于BBB,使用以下命令:
make am335x_boneblack_defconfig
接着执行:
make xconfig
u-boot 的顶级 xconfig 菜单会显示选择的ARM架构和TI OMAP2 1变体,除了要包含哪些命令外,可能不需要进行太多更改。
需要注意的是, xconfig 菜单中没有 CROSS_COMPILE 前缀,因此需要编辑 Makefile 或在 make 命令中指定。同时,在 Makefile 中找不到 ARCH 变量的定义,所以也需要在 make 命令行中指定。 make 命令如下:
make CROSS_COMPILE=arm-linux- ARCH=arm
在编译过程中可能会遇到问题,例如首次运行 make 时可能会出现错误消息 bad value (armv5) for -march= switch 。通过查看 arch/arm/Makefile 文件,可以找到相关代码:
arch-$(CONFIG_CPU_V7) = $(call cc-option, -march=armv7-a, \
$(call cc-option, -march=armv7, -march=armv5))
由于 CONFIG_CPU_V7 已设置,这些行会被添加到 gcc 命令行中。可以尝试从第二行中删除 , -march=armv5 , make 命令应该就能成功。另外,还需要安装 python-devel 包。
make 步骤会生成多个输出文件:
- MLO :将 u-boot 加载到RAM的二级程序加载器(SPL)
- u-boot :ELF二进制格式的可执行文件,用途不太明确
- u-boot.bin :原始二进制映像,适合写入闪存
- u-boot-nodtb.bin :似乎与 u-boot.bin 相同
- u-boot.img :在 u-boot.bin 前面添加了64字节的 u-boot 头
- u-boot.map :链接器输出的映射文件
- u-boot.cfg :以C头文件表示的 .config 文件
- u-boot.sym :符号表
- u-boot.srec :Motorola S记录格式的可执行文件
同时, make 步骤还会在 tools/ 目录下构建几个工具。
2.3 测试新的U-boot
在将新的 u-boot 烧录到闪存之前,需要先在RAM中进行测试。将 u-boot.bin 复制到 /var/lib/tftpboot ,将BBB启动到 u-boot 并执行以下命令:
setenv ipaddr 192.168.1.50
setenv serverip 192.168.1.2
tftp 80800000 u-boot.bin
go 80800000
需要设置 ipaddr 和 serverip ,因为通常在 u-boot 读取 uEnv.txt 之前这些值不会被设置。 go 命令将控制权转移到指定的内存地址,新的引导加载器应该会开始执行并引导内核,除非中断自动引导。
如果有JTAG(联合测试行动组)硬件调试器,也可以使用它进行调试。对于进行了大量更改且确实需要测试新 u-boot 的情况,JTAG调试器可能是在引导加载器代码上使用断点的唯一方法。
2.4 “沙箱”测试
如果对 u-boot 的修改与架构无关,可以在工作站上进行测试。 u-boot 提供了一个名为“沙箱”的目标“开发板”,类似于调试嵌入式软件时的模拟环境。沙箱将 u-boot 作为用户空间应用程序运行,允许使用Gnu DeBugger(GDB)调试修改。构建沙箱版本可能需要安装 openssl-devel 包。
默认情况下,沙箱需要 libsdl (Simple DirectMedia Layer), u-boot 文档暗示这对于显示和键盘支持是必要的。该包在CentOS仓库中名为 libsdl1.2-dev ,可能找不到。可以通过在 make 命令中添加 NO_SDL=1 来构建不支持SDL的沙箱。
但这还不够,还需要编辑 include/configs/sandbox.h 文件,大约在第104行有以下三行:
#ifndef SANDBOX_NO_SDL
#define CONFIG_SANDBOX_SDL
#endif
将这些行注释掉,然后执行:
make NO_SDL=1
构建似乎会成功,但最后可能会出现以下消息:
./scripts/dtc-version.sh: line 17: dtc: command not found
./scripts/dtc-version.sh: line 18: dtc: command not found
*** Your dtc is too old, please upgrade to dtc 1.4 or newer
make: *** [checkdtc] Error 1
dtc 指的是设备树编译器,它是Linux内核源代码树的一部分,CentOS仓库中似乎没有对应的包。一个简单的解决方法是将 linux/scripts/dtc/dtc 复制到路径中的某个位置,如 /usr/local/bin 。
2.5 设备树
将Linux等操作系统移植到新平台时,最大的问题之一是描述硬件。因为硬件描述分散在几十个设备驱动程序、内核和引导加载器等多个部分,最终导致这些软件的各个部分针对每个平台都是唯一的,配置选项数量增加,每个开发板都需要独特的内核映像。
为了解决这个问题,有多种方法。“板级支持包”(BSP)的概念试图将所有与硬件相关的代码集中在几个文件中。可以说Linux内核源代码树的整个 arch/ 子树就是一个巨大的板级支持包。
查看内核的 arch/arm/ 子树,会发现大量以 mach-* 和 plat-* 形式命名的目录,分别代表“机器”和“平台”。这些目录中的大多数文件为ARM架构的特定实现提供配置信息,而且每个实现描述配置的方式都不同。
设备树的出现就是为了用一种单一的语言来明确描述计算机系统的硬件。
系统中的外围设备可以从多个维度进行分类,例如字符设备与块设备、内存映射设备以及通过I2C或USB等外部总线连接的设备。还有平台设备和可发现设备。可发现设备位于PCI和USB等外部总线上,能够告知系统它们是什么以及如何配置,即可以被内核“发现”。识别设备后,加载相应的驱动程序并询问设备的精确配置相对简单。
而平台设备缺乏自我识别机制,像Sitara这样的片上系统(SoC)实现中充斥着这些平台设备,如系统时钟、中断控制器、GPIO、串口等。设备树机制对于管理平台设备特别有用。
设备树的概念在内核的PowerPC分支中发展起来,并且在那里使用得最多。实际上,现在所有PowerPC平台在启动时都必须向内核传递一个设备树。设备树的文本表示是扩展名为 .dts 的文件,这些 .dts 文件通常位于内核源代码树的 arch/$ARCH/boot/dts 目录中。
设备树是一种层次化的数据结构,用于描述计算机系统的设备和互连总线集合。它以节点的形式组织,根节点用“/”表示,就像根文件系统一样。每个节点都有一个名称,由名称 - 值对的“属性”组成,还可能包含“子节点”。以下是一个示例设备树:
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
BBB使用设备树来描述其硬件,设备树源文件( *.dts )位于 linux/arch/arm/boot/dts 目录中,BBB的设备树文件是 am335x-boneblack.dts 。该文件定义的内容不多,但包含了其他几个文件(注意,包含的设备树文件扩展名为 .dtsi ), am33xx.dtsi 定义了系统的很多内容。
2.6 将应用程序放入eMMC闪存
在完成了U-boot的安装、配置与测试后,接下来可以将应用程序放入eMMC闪存,这样在设备启动时就能自动运行。以下是大致的操作步骤:
-
准备应用程序 :确保应用程序已经编译好,并且生成了可以在目标设备上运行的二进制文件。
-
连接设备 :通过USB或者网络等方式将开发主机与目标设备(BBB)连接好,保证能够进行文件传输。
-
传输应用程序 :可以使用
scp或者tftp等工具将应用程序文件传输到目标设备上。例如使用scp命令:
scp /path/to/your/application root@<BBB_ip>:/path/on/BBB
其中 /path/to/your/application 是开发主机上应用程序的路径, <BBB_ip> 是BBB的IP地址, /path/on/BBB 是目标设备上存放应用程序的路径。
- 配置U-boot启动参数 :在U-boot中设置合适的启动参数,使得设备启动时能够自动运行应用程序。可以通过以下命令在U-boot命令行中设置环境变量:
setenv bootcmd "run your_application_command"
saveenv
其中 your_application_command 是运行应用程序的具体命令, saveenv 命令用于保存设置的环境变量。
2.7 资源与总结
在学习和使用Linux系统初始化以及U-boot引导加载器的过程中,有很多有用的资源可以参考:
- BusyBox官方网站 : www.busybox.net ,可以获取到关于BusyBox的详细信息。
- systemd文档 : access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Unit_Files.html ,提供了关于systemd的详细文档。
- systemd教程 :可以通过谷歌搜索“systemd tutorial”获取很多相关教程,例如 www.linux.com/learn/understanding-and-using-systemd 和 www.digitalocean.com/community/tutorials/systemd-essentials-working-with-services-units-and-the-journal 。
- 构建最小BeagleBone Black系统的博客文章 : gist.github.com/vsergeev/2391575 和 www.bootembedded.com/embedded-linux/building-embedded-linux-scratch-beaglebone-black 。
综上所述,Linux系统的初始化过程涉及内核初始化、不同的初始化机制(如Systemd和System V),通过合理配置可以实现设备启动时自动运行特定的服务和应用程序。而U-boot作为一个强大的引导加载器,在嵌入式系统中发挥着重要作用,从安装、配置到测试,每个环节都需要仔细处理,同时结合设备树等技术可以更好地管理硬件资源。在实际应用中,可以根据具体需求灵活选择和配置这些技术,以构建出高效、稳定的嵌入式系统。
流程图总结
以下是一个简单的mermaid流程图,总结了Linux系统启动和U-boot相关操作的主要流程:
graph TD;
A[系统上电] --> B[U-boot启动];
B --> C[初始化硬件];
C --> D[加载内核];
D --> E[内核初始化];
E --> F[挂载根文件系统];
F --> G[启动init进程];
G --> H1[Systemd初始化];
G --> H2[System V初始化];
H1 --> I1[启动服务];
H2 --> I2[执行inittab指令];
B1[开发主机] --> C1[下载U-boot源码];
C1 --> D1[配置U-boot];
D1 --> E1[编译U-boot];
E1 --> F1[测试U-boot];
F1 --> G1[烧录到闪存];
这个流程图展示了系统启动的整体过程,包括U-boot的操作以及Linux内核初始化和不同初始化机制的选择,帮助读者更清晰地理解整个流程。
超级会员免费看
1万+

被折叠的 条评论
为什么被折叠?



