掌握嵌入式Linux编程——如何自行构建 Linux 操作系统

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

可能的报错

  1. openssl/bio.h: No such file or directory
    解决:
    $ apt install libssl-dev

  2. /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 命令,因为设备一旦创建,它们就只是文件了

proc 和 sysfs 文件系统

内核模块

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值