uboot
拉取 uboot 源码、配置、编译
$ git clone git://git.denx.de/u-boot.git
$ cd u-boot
$ git checkout v2021.01
$ CROSS_COMPILE=arm_none_eabi-
$ make am335x_evm_defconfig
$ make
移植 uboot 到新的板卡(PCB级)
linux
拉取 linus 维护的内核源码仓库
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torv
alds/linux.git
扫盲
每次 commit log 都有上万个条目,没可能通过读这些日志搞清楚内核更新的脉络,
但有网站:https://kernelnewbies.org/ ,介绍 每个内核版本的特性
拉取稳定版本的仓库
早期( 2.6 以前)使用奇偶版本号区别试用版和稳定版,现在已经不再做此区分。
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linustable.git
checkout 切换到指定版本
$ cd linux-stable
$ git checkout v5.4.50
获取指定版本的源码
Linux 主线支持的硬件较少,一般是第三方开源( Linaro/Yocto )或者硬件厂商提供定制的嵌入式 Linux
如果确认主线支持自己的硬件,可以从 https://www.kernel.org/ 获取指定版本的内核源码:
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.50.tar.xz
$ tar xf linux-5.4.50.tar.xz
$ mv linux-5.4.50 linux-stable
编译内核
$ make ARCH=arm multi_v7_defconfig
$ make -j 4 ARCH=arm CROSS=COMPILE=arm-none-eabi- zImage
System.map 是文本格式的符号表描述文件
大多数 BootLoader 不能直接读取 ELF 文件,因而需要对 vmlinux 和其它放在
arch/$ARCH/boot 目录下二进制文件作进一步的处理:
- Image: 把 vmlinux 转换成 raw binary 格式
- zImage: 一般供 PowerPC 架构使用, Image 的压缩版本, BootLoader 必须完成解压和重定位
- uImage: zImage 加上一个 64 字节的 U-Boot header
可能的报错
-
openssl/bio.h: No such file or directory
解决:
$ apt install libssl-dev -
/bin/sh: 1: bc: not found
解决:
$ apt install bc
编译设备树
$ make ARCH=arm dtbs
编译模块
$ make -j 4 ARCH=arm CORSS_COMPILE=arm-none-eabi- modules
清理源码目录
- clean: Removes object files and most intermediates.
- mrproper: 删除所有中间文件,包括.config文件。可在克隆或提取源代码后立即将源代码树返回到它所处的状态。(Mr. Proper是世界上一些地方常见的一种清洁产品)。
- distclean: 除了清理 proper 的清理对象,还会把编辑器备份文件、补丁(patch files)、其他开发过程的文件(artifacts of software development)清理掉
启动 linux 内核
- 编译脚本
# !/bin/bash
make ARCH=arm CROSS_COMPILE=arm-none-eabi- mrproper
make ARCH=arm versatile_defconfig
make -j4 ARCH=arm CROSS_COMPILE=arm-none-eabi- zImage
make -j4 ARCH=arm CROSS_COMPILE=arm-none-eabi- modules
make ARCH=arm dtbs
启动 qemu 仿真
QEMU_AUDIO_DRV=none qemu-system-arm -m 256M -nographic -M versatilepb -kernel arch/arm/boot/zImage -append "console=ttyAMA0,115200" -dtb arch/arm/boot/dts/versatile-pb.dtb
细说启要素
编译完成以后,内核镜像(image)、设备树 blobs (包括DTO/DTBO等) 和引导参数 (boot parameters) 都在 arch/arm/boot/ 目录下
-
DTO (设备树叠加层) 可让主要的设备树 Blob (DTB,Device Tree Blob) 叠加在设备树上。使用 DTO 的引导加载程序可以维护系统芯片 (SoC) DT,并动态叠加针对特定设备的 DT,从而向树中 添加节点 并对现有树中的属性进行更改
-
DTBO(DTB Overlay,设备树块覆盖)
用于在 DTB(Device Tree Blob,设备树块)的基础上添加或 修改节点信息
U-Boot 在启动时,它会匹配 DTBO 并将其内容合入或者覆盖到匹配的 DTB 中,随后加载到 DDR,再将地址传给 Kernel
可能遇到的问题
1 内核错误
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
CPU: 0 PID: 1 Comm: swapper Not tainted 5.4.50 #1
Hardware name: ARM-Versatile (Device Tree Support)
[] (unwind_backtrace) from [] (show_stack+0x10/0x14)
[] (show_stack) from [] (panic+0xe8/0x2e4)
[] (panic) from [] (mount_block_root+0x1f4/0x2b8)
[] (mount_block_root) from [] (prepare_namespace+0x15c/0x1b0)
[] (prepare_namespace) from [] (kernel_init+0x8/0xe8)
[] (kernel_init) from [] (ret_from_fork+0x14/0x34)
Exception stack(0xcf825fb0 to 0xcf825ff8)
5fa0: 00000000 00000000 00000000 00000000
5fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
5fe0: 00000000 00000000 00000000 00000000 00000013 00000000
—[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]—
Kernel Panic 是在内核遇到无法恢复的错误的情况,发生 Kernel Panic 之后内核会停止运行(halt),这里引发了 Kernel Panic 的原因是没有找到根文件系统: Unable to mount root fs on unknown-block(0,0)
制作根文件系统
即使最最最基本的根文件系统,也需要大约50个实用程序。逐个获取程序的源代码并交叉编译的工作量相当大,由此产生的程序集合将占用几十兆字节,编译和存储都是问题。
当然了,嵌入式 Linux 发展多年,利器已存,BusyBox 就是一种解决构建根文件系统时上述两个问题的实用工具:
- 将所有工具组合到一个二进制文件中,以共享代码,缩减整体大小
获取 BusyBox 源码
下载源码
$ git clone git://busybox.net/busybox.git
切到指定版本
$ cd busybox
$ git checkout master
编译 BusyBox
从默认配置开始配置BusyBox,这将启用BusyBox的几乎所有特性
$ make distclean
$ make defconfig
$ make ARCH=arm CROSS_COMPILE=arm-none-eabi-
可能的报错
include/platform.h:164:11: fatal error: byteswap.h: No such file or directory #include <byteswap.h>
- 编译工具的库里没有对应头文件,换成 arm-linux-gneabi-gcc 就行
date.c:(.text.date_main+0x248): undefined reference to `stime’
创建根文件系统暂存目录
在主机上创建一个暂存目录,用来组织将要构建的根文件系统
$ mkdir rootfs
$ cd rootfs
$ mkdir bin dev etc home lib proc sbin sys tmp usr var
$ mkdir usr/bin usr/lib usr/sbin
$ mkdir -p var/log
查看 busybox 字段确定并添加需要的共享库到 rootfs/lib
在 ELF(可执行与可链接格式)文件中,Interpreter指定了动态链接器的路径,用于加载和解析共享库;而Shared Library包含了可重用的代码和数据,可以被多个程序共享并动态加载到内存中。
借助 readelf 把编译得到的 elf 文件(busybox)转成文本形式(busybox.elf)
$ arm-linux-gnueabi-readelf -a busybox > busybox.elf
查找 program interpreter
$ grep "program interpreter" busybox.elf
[Requesting program interpreter: /lib/ld-linux.so.3]
查找 shared library
$ grep "Shared library" busybox.elf
0x00000001 (NEEDED) Shared library: [libm.so.6]
0x00000001 (NEEDED) Shared library: [libresolv.so.2]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x00000001 (NEEDED) Shared library: [ld-linux.so.3]
以上就是 busybox 需要的共享库,我们需要把这些库文件拷贝到先前准备的根文件系统暂存目录的 lib 目录下,这些库文件一般在 /usr/arm-linux-gnueabi-gcc/lib/ 目录下
$ ls /usr/arm-linux-gnueabi/lib/
Mcrt1.o libanl.so.1 libdl.so.2 libnss_compat.so.2 librt.so.1
Scrt1.o libasan.so.6 libg.a libnss_dns.so.2 libstdc++.so.6
crt1.o libasan.so.6.0.0 libgcc_s.so.1 libnss_files.so.2 libstdc++.so.6.0.30
crti.o libatomic.so.1 libgomp.so.1 libnss_hesiod.so libthread_db.so
crtn.o libatomic.so.1.2.0 libgomp.so.1.0.0 libnss_hesiod.so.2 libthread_db.so.1
gcrt1.o libc.a libm.a libpcprofile.so libubsan.so.1
ld-linux.so.3 libc.so libm.so libpthread.a libubsan.so.1.0.0
libBrokenLocale.a libc.so.6 libm.so.6 libpthread.so.0 libutil.a
libBrokenLocale.so libc_malloc_debug.so libmcheck.a libresolv.a libutil.so.1
libBrokenLocale.so.1 libc_malloc_debug.so.0 libmemusage.so libresolv.so
libanl.a libc_nonshared.a libnsl.so.1 libresolv.so.2
libanl.so libdl.a libnss_compat.so librt.a
$ cd rootfs
$ cp -a /usr/arm-linux-gnueabi/lib/ld-linux.so.3 ./lib/
$ cp -a /usr/arm-linux-gnueabi/lib/libresolv.so.2 ./lib/
$ cp -a /usr/arm-linux-gnueabi/lib/libc.so.6 ./lib/
$ cp -a /usr/arm-linux-gnueabi/lib/libm.so.6 ./lib/
添加必要的设备节点到 rootfs/dev
在最精简的文件系统中,只需要为准备两个设备节点(device node),即 console 和 null
console 只能被 root 用户访问(access permissions are 600,rw-------),null 则可以被所有用户访问(access permissions are 666,rw-rw-rw-)
$ cd rootfs
$ sudo mknod -m 666 dev/null c 1 3
$ sudo mknod -m 600 dev/console c 5 1
$ ls -l dev
total 0
crw------- 1 root root 5, 1 Jun 2 22:31 console
crw-rw-rw- 1 root root 1, 3 Jun 2 22:31 null
sudo mknod -m 666 dev/null c 1 3
用于创建一个设备文件 /dev/null,命令中的各个部分含义如下:
- sudo: 以超级用户权限执行命令
- mknod: 用于创建设备文件的命令
- -m 666: 设置设备文件的权限为666,即所有用户都有读写权限
- dev/null: 要创建的设备文件的路径和名称
- c: 表示要创建的是一个字符设备
- 1 3: 这两个数字是设备文件的主设备号和次设备号,用于标识设备类型和设备
删除设备节点使用 rm 命令。没有 rmnod 命令,因为设备一旦创建,它们就只是文件了