普通视图

Received before yesterdayEcho小窝

U-Boot、内核移植与根文件系统构建(BeagleBone Green Gateway&AM335X)

作者Echo
2026年4月25日 22:36

前言

本文档主要记录了基于 BeagleBone Green Gateway 开发板(核心芯片为 TI AM3358)的 U-Boot、内核移植与根文件系统构建全流程。内容涵盖了在 Ubuntu 下搭建交叉编译环境、编译 Linux 4.19 内核U-Boot、设备树 的详细步骤,在移植过程中遇到的编译器兼容性问题(如 GCC 10+ 报错、架构检测失效)及其修复方法。

一:安装交叉编译工具链

关于 安装 ARM 交叉编译工具链 的教程笔记。

  • 宿主机 (Host): 你的电脑 (Ubuntu x86_64)。
  • 目标机 (Target): 开发板 (AM335x ARMv7)。
  • 交叉编译器: 在 x86 上运行,但生成的是能在 ARM 上运行的可执行代码。

方法 A:使用 APT 包管理器 (最简单,推荐)

这是最快的方法,直接从 Ubuntu 软件源安装。

1. 更新源

sudo apt-get update

2. 安装基础构建工具 (Host 工具)
编译内核不仅需要交叉编译器,还需要主机端的 gcc、make、bison 等工具。

sudo apt-get install build-essential bison flex libssl-dev lzop u-boot-tools device-tree-compiler

3. 安装交叉编译器
安装 Hard Float (hf) 版本的编译器,适配 AM335x 的硬件浮点单元。

# gcc (C编译器) 和 g++ (C++编译器)
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

方法 B:手动安装 Linaro 版本 (指定版本,进阶)

如果你需要特定版本的 GCC (例如为了避开 GCC 10+ 的语法检查严格问题,或者为了匹配旧内核),可以使用此方法。

1. 下载编译器包
前往 Linaro 官网下载 (例如 GCC 7.5):

  • 文件名示例:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

2. 创建目录并解压
通常安装在 /usr/local/arm 目录下。

sudo mkdir -p /usr/local/arm
# 假设压缩包在当前目录
sudo tar -vxf gcc-linaro-7.5.0-*.tar.xz -C /usr/local/arm/

3. 配置环境变量 (PATH)
为了让终端能在任何地方找到这个命令,需要修改 ~/.bashrc

vim ~/.bashrc

在文件最末尾添加:

# 注意将下面的路径换成你实际解压后的文件夹名
export PATH=$PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

4. 生效配置

source ~/.bashrc

验证安装

无论用哪种方法,安装完成后必须验证。

1. 检查版本

arm-linux-gnueabihf-gcc -v

2. 预期输出

  • 最后一行应显示 gcc version x.x.x ...
  • 如果提示 command not found,说明没安装好或者环境变量没配对。

后缀的区别 (坑点预警)

在下载或安装时,你会看到不同的后缀,千万别选错

后缀名全称含义适用场景
gnueabihfHard Float使用硬件 FPU (浮点单元) 进行运算AM335x (你的板子), Raspberry Pi 等现代 ARM
gnueabiSoft Float使用软件模拟浮点运算 (速度慢)非常古老的 ARM9 或无 FPU 的芯片
aarch64ARM6464位 ARM 架构树莓派4 (64位系统), 手机芯片, RK3399

常用命令别名

为了避免每次编译都要输那一长串前缀,可以在 ~/.bashrc 里加个变量:

# 在 ~/.bashrc 末尾添加
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

这样以后编译内核只需输:

make zImage  # 甚至不用输 ARCH 和 CROSS_COMPILE,只要 Makefile 支持读取环境变量

二: U-Boot编译和移植

目标:制作一张能引导开发板启动的 SD 卡。

编译 U-Boot

  • 作用:初始化硬件(CPU, DDR),为加载内核做准备。
  • 命令
# 获取
git clone https://github.com/beagleboard/u-boot.git
cd u-boot
git checkout v2021.10-bbb.io-am335x # 切换到对应分支

cd /home/dq/linux/uboot/beagleboard-u-boot

# 清理旧编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
# 配置 (AM335x 通用配置)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_defconfig
# 编译 (-j4 使用4核加速)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

产物MLO (一级引导), u-boot.img (二级引导)。

  • ROM Code(芯片出厂自带):寻找 MLO 并加载到 SRAM。
  • MLO:初始化 DDR3,寻找 u-boot.img 并搬运到 DDR3。
  • u-boot.img:运行命令行,加载 Kernel(Linux 内核)。
  • Kernel:挂载 Rootfs(根文件系统),启动用户空间。

SD 卡分区与烧录

  • 作用:将 SD 卡分为 Boot 分区 (FAT32) 和 Rootfs 分区 (EXT4)。
  • 命令

sd卡格式化脚本

#! /bin/sh
# mkcard.sh v0.5
# (c) Copyright 2009 Graeme Gregory <dp@xora.org.uk>
# Licensed under terms of GPLv2
#
# Parts of the procudure base on the work of Denys Dmytriyenko
# http://wiki.omap.com/index.php/MMC_Boot_Format

export LC_ALL=C

if [ -z `which bc` ]; then
    echo "no bc binary found"
    exit 1;
fi

if [ $# -ne 1 ]; then
    echo "Usage: $0 <drive>"
    exit 1;
fi

DRIVE=$1

# 清除 SD 卡头部信息
dd if=/dev/zero of=$DRIVE bs=1024 count=1024

# 获取磁盘大小
SIZE=`fdisk -l $DRIVE | grep Disk | grep bytes | awk '{print $5}'`

echo DISK SIZE - $SIZE bytes

# 计算柱面
CYLINDERS=`echo $SIZE/255/63/512 | bc`

echo CYLINDERS - $CYLINDERS

# 使用 sfdisk 进行分区
# 分区1: 63MiB, 类型 0x0C (FAT32 LBA), 可启动 (*)
# 分区2: 4GiB (或剩余空间), 类型默认 (Linux)
sudo sfdisk $DRIVE << EOF
8192,63MiB,0x0C,*
137216,4GiB,,-
EOF

sleep 1

# 格式化分区 1 为 FAT32 (boot)
if [ -b ${DRIVE}1 ]; then
    umount ${DRIVE}1
    mkfs.vfat -F 32 -n "boot" ${DRIVE}1
else
    if [ -b ${DRIVE}p1 ]; then
        umount ${DRIVE}p1
        mkfs.vfat -F 32 -n "boot" ${DRIVE}p1
    else
        echo "Cant find boot partition in /dev"
    fi
fi

# 格式化分区 2 为 ext4 (rootfs)
if [ -b ${DRIVE}2 ]; then
    umount ${DRIVE}2
    mkfs.ext4 -L "rootfs" ${DRIVE}2
else
    if [ -b ${DRIVE}p2 ]; then
        umount ${DRIVE}p2
        mkfs.ext4 -j -L "rootfs" ${DRIVE}p2
    else
        echo "Cant find rootfs partition in /dev"
    fi
fi
cd /home/dq/linux/tool
# 运行脚本格式化 sdb (注意:你的设备号是 sdb)
sudo ./mkcard.sh /dev/sdb

# 挂载并拷贝引导文件
sudo mount /dev/sdb1 /mnt/boot  # (或依赖自动挂载 /media/dq/boot)
sudo cp ../../linux/uboot/beagleboard-u-boot/MLO /mnt/boot/
sudo cp ../../linux/uboot/beagleboard-u-boot/u-boot.img /mnt/boot/

三:Linux 内核编译与部署

目标:编译内核,并解决 U-Boot 网卡驱动不兼容的问题(采用“SD卡存内核 + NFS存文件系统”的混合策略)。

编译内核与设备树

  • 作用:编译操作系统核心 (zImage) 和硬件描述文件 (.dtb)。
  • 命令
# 获取
git clone https://github.com/beagleboard/linux.git --depth 1 -b 4.19.94-ti-r42

cd /home/dq/linux/tool/linux-4.19.94-ti-r42/
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig
# 编译内核、设备树和模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules -j4

生成三个部分

  • zImage: 内核镜像。位于 arch/arm/boot/zImage
  • dtbs: 设备树二进制文件。位于 arch/arm/boot/dts/am335x-bonegreen-gateway.dtb
  • modules: 驱动模块。它们被编译成了分布在各个目录下的 .ko 文件。

TFTP 配置

目的:uboot启动后通过tftp拉取镜像zimag和设备树dtb

  • 无需手动拷贝:Ubuntu 编译生成的产物直接放在 TFTP 根目录,开发板重启时自动从网络拉取。
  • 保护 SD 卡:减少对物理存储介质的读写,避免频繁挂载导致的 FAT 分区损坏。
  • 纯内存运行:内核和设备树直接加载到 RAM,不占用 SD 卡空间。

Ubuntu 配置 TFTP

1. 安装服务程序

使用最稳健的 tftpd-hpa 包:

sudo apt-get update
sudo apt-get install tftpd-hpa tftp-hpa

2. 创建工作目录并赋权 (关键)

默认路径通常在 /var/lib/tftpboot,但为了开发方便,建议修改到用户目录下,并赋予最高权限防止“Permission denied”。

# 1. 创建目录 (建议放在你的工作区)
mkdir -p /home/dq/linux/tftpboot

# 2. 赋予最高权限 (允许任意读写,开发环境图方便)
chmod 777 /home/dq/linux/tftpboot

3. 修改配置文件

编辑 /etc/default/tftpd-hpa,将默认配置修改为指向你的新目录。

sudo vim /etc/default/tftpd-hpa

修改后的完整内容:

# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
# 修改 1: 指向你刚才创建的目录
TFTP_DIRECTORY="/home/dq/linux/tftpboot" 
TFTP_ADDRESS=":69"
# 修改 2: 增加 --create 允许上传(可选),保持 --secure
TFTP_OPTIONS="--secure --create"

4. 重启服务并生效

每次修改配置文件后,必须重启服务:

sudo service tftpd-hpa restart

5. 避坑指南:防火墙 (必做)

TFTP 使用 UDP 69 端口,极易被 Ubuntu 防火墙拦截。开发阶段建议直接关闭,或者放行端口。

# 方案 A: 简单粗暴关闭防火墙 (推荐)
sudo ufw disable

# 方案 B: 仅放行 UDP 69
sudo ufw allow 69/udp

6. 本地自测 (验证服务是否存活)

在烧录板子前,先自己连自己测试一下,确保不是 Ubuntu 内部的问题。

# 在 tftpboot 目录下创建一个测试文件
touch /home/dq/linux/tftpboot/test.txt

# 回到家目录尝试下载
cd ~
tftp 127.0.0.1
tftp> get test.txt
tftp> q

# 检查是否下载成功
ls test.txt

如果 ls 能看到文件,说明服务端配置完美!


备选方案 (SD卡部署)

  • 现状:你的 U-Boot 探测不到 PHY 地址 1,网络链路(Link Up)无法建立。
  • 代价:每次修改内核或设备树,必须先进入 Linux,手动挂载 /dev/mmcblk0p1/mnt/sd_boot,然后 cp 覆盖,效率较低
  • 原因:U-Boot 无法通过 TFTP 联网下载,所以直接把内核拷进 SD 卡让它读取。
  • 命令
# 假设 SD 卡 boot 分区挂载在 /media/dq/boot
sudo cp arch/arm/boot/zImage /media/dq/boot/
sudo cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /media/dq/boot/
sync  # 同步数据,防止丢失

PS: 后面替换设备树和系统镜像可以通过将sd卡挂载到开发板,然后通过nfs共享文件,将其拷贝到sd卡boot分区


开发板配置

设置产物自动归档
在编译脚本末尾或手动执行,将产物推送到 TFTP 目录(假设为 /home/dq/tftpboot):

cp arch/arm/boot/zImage /home/dq/tftpboot/
cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /home/dq/tftpboot/

U-Boot 配置指令 (开发板端)

在 U-Boot 命令行设置网络参数及自动化命令:

  1. 设置网络 IP
setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv
  1. 定义自动化启动脚本 (bootcmd)
# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv
  1. 正常工作时的现象

当一切配置正确且 PHY 地址匹配时,U-Boot 启动会显示:

link up on port 0, speed 100, full duplex
Using ethernet@4a100000 device
TFTP from server 192.168.10.200; our IP address is 192.168.10.13
Filename 'am335x-bonegreen-gateway.dtb'.
Load address: 0x88000000
Loading: ############  (此处会出现大量井号,表示数据高速传输)
done
Bytes transferred = 56234 (dbaa hex)

四:根文件系统构建以及NFS配置

目标:在 Ubuntu 上建立一个文件夹,让开发板通过网线把它当成“硬盘”来挂载,实现文件实时同步,同时加载根文件系统。

# 根文件系统下载
wget -c https://rcn-ee.com/rootfs/eewiki/minfs/debian-10.3-minimal-armel-2020-02-10.tar.xz

安装模块到根文件系统

❯ pwd
/home/dq/linux/nfs/rootfs/lib/modules/4.19.94

# 生成配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4

# 安装
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/home/dq/linux/nfs/rootfs/ modules_install

Ubuntu服务端配置

  1. 安装 NFS 服务
sudo apt-get update
sudo apt-get install nfs-kernel-server
  1. 创建并准备根文件系统目录

假设你的根文件系统(Rootfs)存放在 /home/dq/linux/nfs/rootfs

PS: 不同根文件压缩包格式需要不同方式解压

# 1. 创建目录
mkdir -p /home/dq/linux/nfs/rootfs

# 2. 假设你在压缩包所在目录
# -x: 解压, -v: 显示进度, -J: 处理 xz 格式, -f: 指定文件
sudo tar -xvJf debian-10.3-minimal-armel-2020-02-10.tar.xz -C /home/dq/linux/nfs/rootfs/
  1. 配置共享权限 (exports)

NFS 的权限控制通过 /etc/exports 文件管理。

sudo vim /etc/exports

在文件末尾添加以下一行:

/home/dq/linux/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)
  • 参数解析
  • rw:可读可写。
  • sync:同步写入(保证数据一致性)。
  • no_root_squash最关键参数。允许板子的 root 用户拥有 Ubuntu 侧对应的 root 权限。如果不加这个,板子无法以 root 身份运行,会导致权限报错。
  • no_subtree_check:禁用子树检查,提高稳定性。
  1. 重启并生效服务
# 重新扫描 exports 文件并使配置生效
sudo exportfs -arv

# 重启 NFS 服务
sudo service nfs-kernel-server restart
  1. 避坑指南:网络与防火墙

NFS 依赖多个端口(RPC),如果防火墙开启,挂载必然失败。

# 直接关闭防火墙(开发环境推荐)
sudo ufw disable

U-Boot设置

直接看第五阶段

五:U-Boot 启动配置

目标:设置 U-Boot 环境变量,启动系统。

TFTP可以使用

即第二阶段tftp开发板的配置

setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv

# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv

# 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

TFTP无法使用

  • 作用:告诉板子“从 SD 卡读内核,去 192.168.10.200 挂载 NFS”。
  • 命令 (在串口终端执行):
# 1. 从 SD 卡读取内核和 DTB
setenv bootcmd 'load mmc 0:1 0x82000000 zImage; load mmc 0:1 0x88000000 am335x-bonegreen-gateway.dtb; bootz 0x82000000 - 0x88000000'

# 2. 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

六:Hello World

目标:验证开发板镜像是否正常工作。

  1. 编写与静态编译
  2. 问题:动态编译报错 No such file (缺少动态库)。
  3. 解决:使用 -static 静态链接。
  4. 命令 (在 Ubuntu 端):
#include <stdio.h>

int main() {
    printf("--------------------------------------\n");
    printf("  Hello, BBGG! This is Gemini speaking. \n");
    printf("  NFS cross-compile works perfectly!   \n");
    printf("--------------------------------------\n");
    return 0;
}
cd /home/dq/linux/nfs/rootfs/home/debian
# 编写代码
sudo vim hello.c 

# 交叉编译 (静态链接)
sudo arm-linux-gnueabihf-gcc -static hello.c -o hello
  1. 板上运行
  2. 命令 (在串口终端):
cd /home/debian
./hello
  • 结果:成功输出 Hello, BBGG!...

补充

在嵌入式 Linux 开发中,解决 GCC 版本冲突(重定义错误)架构检测失败(ISB 指令错误) 是适配旧内核(如 4.19)的关键步骤。以下是针对这两处修改的笔记整理:


1. 解决主机工具编译冲突 (yylloc 多重定义)

背景:当 Ubuntu 宿主机的 GCC 版本 10 时,默认开启 -fno-common,导致内核自带的 dtc(设备树编译器)工具在链接时报错。

  • 修改文件:顶层 Makefile
  • 具体位置:搜索 KBUILD_HOSTCFLAGS 定义处(通常在 400 行左右)。
  • 修改内容
    在变量末尾手动添加 -fcommon 参数。
# 原代码:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

# 修改后:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 -fcommon $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

2. 解决架构降级导致的汇编错误 (isb 指令不支持)

背景:内核通过 cc-option 测试编译器。在使用硬浮点(Hard-Float)交叉编译器时,由于测试命令缺少 FPU 参数导致测试失败,内核 Makefile 会误认为编译器不支持 ARMv7,从而自动降级到 ARMv5,导致不识别 v7 指令 isb

  • 修改文件arch/arm/Makefile
  • 具体位置:搜索 CONFIG_CPU_32v7 所在行(通常在 65 行左右)。
  • 修改内容
    废除自动检测逻辑,直接硬编码(Hardcode)指定架构为 -march=armv7-a
# 原代码:
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a)

# 修改后(删除检测函数,直接指定架构):
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 -march=armv7-a

Git命令汇总

作者Echo
2023年12月21日 23:18

前言

单纯的对一些经常用的 git 命令进行总结,方便以后查询使用,没啥营养

1. 基础

git add          # 提交到 暂存区
git commit -m "commnet"    # 提交到 版本库
git branch -M main # 重新命名分支
git remote add origin # 添加远程仓库
git pull origin master # 从名为 origin 的远程仓库的 master 分支拉取最新的提交,并将其合并到当前分支
git push origin main   # 将本地仓库的文件push到远程仓库(若 push 不成功,可加 -f 进行强推操作)
git diff read.txt # 查看文件变化

2. 版本回退

git reset --hard HEAD^ # 恢复到上一个版本
git reset --hard HEAD~10 # 恢复到网上10个版本
git reset --hard commitID # 恢复到指定commit版本

3. 撤销修改

git restore  # 工作区
git restore --staged  # 暂存区,工作区需要执行上一步 add
git reset --hard HEAD^ # 工作区、暂存区、本地仓库都回退 commit

4. 删除文件

rm file
git add file
git commit -m ""

5. 分支操作

git branch test # 创建分支 test
git branch # 查看当前分支
git switch -c test # 创建test分支,然后切换到test分支 git branch test git checkout test
git switch master # 切换
git merge test # 合并指定分支到当前分支
git merge --no-ff -m "no-ff" test # 禁用快进(fast-forward)合并,强制创建一个新的合并提交
git branch -d test # 删除分支
git branch -D test # 强制删除
git log --graph # 查看分支合并图

6. 保存和恢复

git stash save "Your stash message" # 保存工作进度
git stash list # 查看 stash 列表
git stash apply [] # 应用
git stash drop [] # 删除
git stash pop [] # 应用并删除
git cherry-pick # 将一个或多个提交从一个分支应用到另一个分支

7. 多人协作

git remote -v # 查看远程库的信息
git switch -c dev origin/dev # 本地创建一个新分支 dev,并将其设置为跟踪远程仓origin/dev 分支
git branch -u origin/dev dev # 本地分支 dev 设置为跟踪远程仓库的 origin/dev 分支
git push origin master   # 将本地仓库文件push到远程(若push不成功,可加-f进行强推)
git pull origin master # 从远程仓库origin的master分支拉取最新提交,并将其合并到当前分支
git rebase # 把本地未push的分叉提交历史整理成直线

8. 标签

标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
git tag v1.0 # 打一个新标签V1.0,默认是打在最新提交的commit上
git log --pretty=oneline --abbrev-commit # 每一行包含了一个提交的简略哈希和提交信息
git tag v0.9 f52c633 # 在特点commit上打标签
git tag -a v0.1 -m "v0.1 released" 1094adb # 创建带有说明的标签,用-a指定标签名,-m指定说明文字
git tag # 查看所有标签
git show v0.9 # 查看标签信息,标签不是按时间顺序列出,而是按字母排序
git tag -d v0.1 # 删除标签
git push origin v1.0 # 推送标签到远程
git push origin --tags # 一次性推送全部尚未推送到远程的本地标签
git push origin -d tag tagName # 删除一个远程标签
git ls-remote --tags origin # 查看删除远程tags执行效果

U-Boot、内核移植与根文件系统构建(BeagleBone Green Gateway&amp;AM335X)

作者Echo
2026年4月25日 22:36

前言

本文档记录基于 BeagleBone Green Gateway 开发板(核心芯片为 TI AM3358)的 U-Boot、Linux 内核移植与根文件系统构建流程。内容包括 Ubuntu 下交叉编译环境搭建、U-Boot 编译、Linux 4.19 内核与设备树编译、TFTP/NFS 启动配置,以及移植过程中遇到的编译器兼容性问题(如 GCC 10+ 报错、架构检测失效)和对应修复方法。

一:安装交叉编译工具链

本节用于安装 ARM 交叉编译工具链。

  • 宿主机 (Host):Ubuntu x86_64 电脑。
  • 目标机 (Target):BeagleBone Green Gateway,AM335x ARMv7 架构。
  • 交叉编译器:运行在 x86 主机上,但生成 ARM 平台可执行代码。

方法 A:使用 APT 包管理器(推荐)

这是最快的方法,直接从 Ubuntu 软件源安装。

1. 更新源

sudo apt-get update

2. 安装基础构建工具 (Host 工具)
编译内核不仅需要交叉编译器,还需要主机端的 gcc、make、bison 等工具。

sudo apt-get install build-essential bison flex libssl-dev lzop u-boot-tools device-tree-compiler

3. 安装交叉编译器
安装 Hard Float (hf) 版本的编译器,适配 AM335x 的硬件浮点单元。

# gcc (C编译器) 和 g++ (C++编译器)
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

方法 B:手动安装 Linaro 版本(指定版本)

如果需要特定版本的 GCC(例如为了避开 GCC 10+ 的严格检查,或者匹配旧内核),可以使用此方法。

1. 下载编译器包
前往 Linaro 官网下载 (例如 GCC 7.5):

  • 文件名示例:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

2. 创建目录并解压
通常安装在 /usr/local/arm 目录下。

sudo mkdir -p /usr/local/arm
# 假设压缩包在当前目录
sudo tar -vxf gcc-linaro-7.5.0-*.tar.xz -C /usr/local/arm/

3. 配置环境变量 (PATH)
为了让终端能在任何地方找到这个命令,需要修改 ~/.bashrc

vim ~/.bashrc

在文件最末尾添加:

# 注意将下面的路径换成你实际解压后的文件夹名
export PATH=$PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

4. 生效配置

source ~/.bashrc

验证安装

无论用哪种方法,安装完成后必须验证。

1. 检查版本

arm-linux-gnueabihf-gcc -v

2. 预期输出

  • 最后一行应显示 gcc version x.x.x ...
  • 如果提示 command not found,说明未安装成功或环境变量未配置正确。

后缀的区别(坑点预警)

在下载或安装时,你会看到不同的后缀,千万别选错

后缀名全称含义适用场景
gnueabihfHard Float使用硬件 FPU (浮点单元) 进行运算AM335x (你的板子), Raspberry Pi 等现代 ARM
gnueabiSoft Float使用软件模拟浮点运算 (速度慢)非常古老的 ARM9 或无 FPU 的芯片
aarch64ARM6464位 ARM 架构树莓派4 (64位系统), 手机芯片, RK3399

常用命令别名

为了避免每次编译都要输那一长串前缀,可以在 ~/.bashrc 里加个变量:

# 在 ~/.bashrc 末尾添加
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

这样以后编译内核只需输:

make zImage  # 甚至不用输 ARCH 和 CROSS_COMPILE,只要 Makefile 支持读取环境变量

二:U-Boot 编译和移植

目标:制作一张能引导开发板启动的 SD 卡。

编译 U-Boot

  • 作用:初始化硬件(CPU, DDR),为加载内核做准备。
  • 命令
# 获取
mkdir -p /home/dq/linux/uboot
cd /home/dq/linux/uboot
git clone https://github.com/beagleboard/u-boot.git beagleboard-u-boot
cd beagleboard-u-boot
git checkout v2021.10-bbb.io-am335x # 切换到对应分支

# 清理旧编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
# 配置 (AM335x 通用配置)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_defconfig
# 编译 (-j4 使用4核加速)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

产物MLO (一级引导), u-boot.img (二级引导)。

  • ROM Code(芯片出厂自带):寻找 MLO 并加载到 SRAM。
  • MLO:初始化 DDR3,寻找 u-boot.img 并搬运到 DDR3。
  • u-boot.img:运行命令行,加载 Kernel(Linux 内核)。
  • Kernel:挂载 Rootfs(根文件系统),启动用户空间。

SD 卡分区与烧录

  • 作用:将 SD 卡分为 Boot 分区 (FAT32) 和 Rootfs 分区 (EXT4)。
  • 命令
注意:下面脚本会清空目标磁盘。执行前必须用 lsblk 确认 SD 卡设备名,不能把系统盘误写成 /dev/sda 等设备。

SD 卡格式化脚本

#! /bin/sh
set -e

# mkcard.sh v0.5
# (c) Copyright 2009 Graeme Gregory <dp@xora.org.uk>
# Licensed under terms of GPLv2
#
# Parts of the procudure base on the work of Denys Dmytriyenko
# http://wiki.omap.com/index.php/MMC_Boot_Format

export LC_ALL=C

if [ -z "$(which bc)" ]; then
    echo "no bc binary found"
    exit 1;
fi

if [ $# -ne 1 ]; then
    echo "Usage: $0 <drive>"
    exit 1;
fi

DRIVE=$1

if [ ! -b "$DRIVE" ]; then
    echo "$DRIVE is not a block device"
    exit 1
fi

echo "Target drive: $DRIVE"
lsblk "$DRIVE"
printf "This will erase all data on %s. Type YES to continue: " "$DRIVE"
read confirm
if [ "$confirm" != "YES" ]; then
    echo "Aborted"
    exit 1
fi

# 清除 SD 卡头部信息
dd if=/dev/zero of="$DRIVE" bs=1024 count=1024

# 获取磁盘大小
SIZE=`fdisk -l "$DRIVE" | grep Disk | grep bytes | awk '{print $5}'`

echo DISK SIZE - $SIZE bytes

# 计算柱面
CYLINDERS=`echo $SIZE/255/63/512 | bc`

echo CYLINDERS - $CYLINDERS

# 使用 sfdisk 进行分区
# 分区1: 63MiB, 类型 0x0C (FAT32 LBA), 可启动 (*)
# 分区2: 4GiB (或剩余空间), 类型默认 (Linux)
sudo sfdisk "$DRIVE" << EOF
8192,63MiB,0x0C,*
137216,4GiB,,-
EOF

sleep 1

# 格式化分区 1 为 FAT32 (boot)
if [ -b "${DRIVE}1" ]; then
    umount "${DRIVE}1" || true
    mkfs.vfat -F 32 -n "boot" "${DRIVE}1"
else
    if [ -b "${DRIVE}p1" ]; then
        umount "${DRIVE}p1" || true
        mkfs.vfat -F 32 -n "boot" "${DRIVE}p1"
    else
        echo "Cant find boot partition in /dev"
    fi
fi

# 格式化分区 2 为 ext4 (rootfs)
if [ -b "${DRIVE}2" ]; then
    umount "${DRIVE}2" || true
    mkfs.ext4 -L "rootfs" "${DRIVE}2"
else
    if [ -b "${DRIVE}p2" ]; then
        umount "${DRIVE}p2" || true
        mkfs.ext4 -j -L "rootfs" "${DRIVE}p2"
    else
        echo "Cant find rootfs partition in /dev"
    fi
fi
cd /home/dq/linux/tool
# 先用 lsblk 确认设备号,再运行脚本。这里假设 SD 卡是 /dev/sdb。
sudo ./mkcard.sh /dev/sdb

# 挂载并拷贝引导文件
sudo mount /dev/sdb1 /mnt/boot  # (或依赖自动挂载 /media/dq/boot)
sudo cp /home/dq/linux/uboot/beagleboard-u-boot/MLO /mnt/boot/
sudo cp /home/dq/linux/uboot/beagleboard-u-boot/u-boot.img /mnt/boot/

三:Linux 内核编译与部署

目标:编译内核、设备树和模块,并根据 U-Boot 网络是否可用选择启动方式。

本文档保留两种启动路线:

  • 路线 A:TFTP + NFS。U-Boot 网络可用时,从 TFTP 下载 zImage.dtb,再通过 NFS 挂载根文件系统。
  • 路线 B:SD 卡 + NFS。U-Boot 网络不可用时,从 SD 卡读取 zImage.dtb,内核启动后通过 NFS 挂载根文件系统。

编译内核与设备树

  • 作用:编译操作系统核心 (zImage) 和硬件描述文件 (.dtb)。
  • 命令
# 获取
git clone https://github.com/beagleboard/linux.git --depth 1 -b 4.19.94-ti-r42

cd /home/dq/linux/tool/linux-4.19.94-ti-r42/
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig
# 编译内核、设备树和模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules -j4

生成三个部分

  • zImage: 内核镜像。位于 arch/arm/boot/zImage
  • dtbs: 设备树二进制文件。位于 arch/arm/boot/dts/am335x-bonegreen-gateway.dtb
  • modules: 驱动模块。它们被编译成了分布在各个目录下的 .ko 文件。

TFTP 配置

目的:U-Boot 启动后通过 TFTP 拉取内核镜像 zImage 和设备树 .dtb

  • 无需手动拷贝:Ubuntu 编译生成的产物直接放在 TFTP 根目录,开发板重启时自动从网络拉取。
  • 保护 SD 卡:减少对物理存储介质的读写,避免频繁挂载导致的 FAT 分区损坏。
  • 纯内存运行:内核和设备树直接加载到 RAM,不占用 SD 卡空间。

Ubuntu 配置 TFTP

1. 安装服务程序

使用最稳健的 tftpd-hpa 包:

sudo apt-get update
sudo apt-get install tftpd-hpa tftp-hpa

2. 创建工作目录并赋权 (关键)

默认路径通常在 /var/lib/tftpboot。为了开发方便,可以修改到用户目录下,并赋予读写权限,避免出现 Permission denied

# 1. 创建目录 (建议放在你的工作区)
mkdir -p /home/dq/linux/tftpboot

# 2. 赋予读写权限(开发环境使用)
chmod 777 /home/dq/linux/tftpboot

3. 修改配置文件

编辑 /etc/default/tftpd-hpa,将默认配置修改为指向你的新目录。

sudo vim /etc/default/tftpd-hpa

修改后的完整内容:

# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
# 修改 1: 指向你刚才创建的目录
TFTP_DIRECTORY="/home/dq/linux/tftpboot" 
TFTP_ADDRESS=":69"
# 修改 2: 增加 --create 允许上传(可选),保持 --secure
TFTP_OPTIONS="--secure --create"

4. 重启服务并生效

每次修改配置文件后,必须重启服务:

sudo service tftpd-hpa restart

5. 避坑指南:防火墙 (必做)

TFTP 使用 UDP 69 端口,极易被 Ubuntu 防火墙拦截。开发阶段建议直接关闭,或者放行端口。

# 方案 A: 开发阶段直接关闭防火墙
sudo ufw disable

# 方案 B: 仅放行 UDP 69
sudo ufw allow 69/udp

6. 本地自测 (验证服务是否存活)

在烧录板子前,先自己连自己测试一下,确保不是 Ubuntu 内部的问题。

# 在 tftpboot 目录下创建一个测试文件
touch /home/dq/linux/tftpboot/test.txt

# 回到家目录尝试下载
cd ~
tftp 127.0.0.1
tftp> get test.txt
tftp> q

# 检查是否下载成功
ls test.txt

如果 ls 能看到 test.txt,说明 TFTP 服务端配置正常。


备选方案 (SD卡部署)

  • 现状:你的 U-Boot 探测不到 PHY 地址 1,网络链路(Link Up)无法建立。
  • 代价:每次修改内核或设备树,必须先进入 Linux,手动挂载 /dev/mmcblk0p1/mnt/sd_boot,然后 cp 覆盖,效率较低。
  • 原因:U-Boot 无法通过 TFTP 联网下载,所以直接把内核拷进 SD 卡让它读取。
  • 命令
# 假设 SD 卡 boot 分区挂载在 /media/dq/boot
sudo cp arch/arm/boot/zImage /media/dq/boot/
sudo cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /media/dq/boot/
sync  # 同步数据,防止丢失

PS: 后续替换内核和设备树时,可以在开发板上挂载 SD 卡 boot 分区,再通过 NFS 共享目录把新文件拷贝到 SD 卡。


开发板配置

设置产物自动归档
在编译脚本末尾或手动执行,将产物推送到 TFTP 目录(假设为 /home/dq/linux/tftpboot):

cp arch/arm/boot/zImage /home/dq/linux/tftpboot/
cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /home/dq/linux/tftpboot/

U-Boot 配置指令 (开发板端)

在 U-Boot 命令行设置网络参数及自动化命令:

  1. 设置网络 IP
setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv
  1. 定义自动化启动脚本(bootcmd
# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv
  1. 正常工作时的现象

当一切配置正确且 PHY 地址匹配时,U-Boot 启动会显示:

link up on port 0, speed 100, full duplex
Using ethernet@4a100000 device
TFTP from server 192.168.10.200; our IP address is 192.168.10.13
Filename 'am335x-bonegreen-gateway.dtb'.
Load address: 0x88000000
Loading: ############  (此处会出现大量井号,表示数据高速传输)
done
Bytes transferred = 56234 (dbaa hex)

四:根文件系统构建以及 NFS 配置

目标:在 Ubuntu 上建立一个文件夹,让开发板通过网线把它当成“硬盘”来挂载,实现文件实时同步,同时加载根文件系统。

# 根文件系统下载
# 注意:前面使用的是 arm-linux-gnueabihf 工具链,因此这里应选择 armhf 根文件系统。
wget -c https://rcn-ee.com/rootfs/eewiki/minfs/debian-10.3-minimal-armhf-2020-02-10.tar.xz

安装模块到根文件系统

# 在 Linux 内核源码目录执行
cd /home/dq/linux/tool/linux-4.19.94-ti-r42/

# 生成配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4

# 安装
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/home/dq/linux/nfs/rootfs/ modules_install

Ubuntu 服务端配置

  1. 安装 NFS 服务
sudo apt-get update
sudo apt-get install nfs-kernel-server
  1. 创建并准备根文件系统目录

假设你的根文件系统(Rootfs)存放在 /home/dq/linux/nfs/rootfs

PS: 不同根文件压缩包格式需要不同方式解压

# 1. 创建目录
mkdir -p /home/dq/linux/nfs/rootfs

# 2. 假设你在压缩包所在目录
# -x: 解压, -v: 显示进度, -J: 处理 xz 格式, -f: 指定文件
sudo tar -xvJf debian-10.3-minimal-armhf-2020-02-10.tar.xz -C /home/dq/linux/nfs/rootfs/
  1. 配置共享权限 (exports)

NFS 的权限控制通过 /etc/exports 文件管理。

sudo vim /etc/exports

在文件末尾添加以下一行:

/home/dq/linux/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)

参数解析

  • rw:可读可写。
  • sync:同步写入(保证数据一致性)。
  • no_root_squash最关键参数。允许板子的 root 用户拥有 Ubuntu 侧对应的 root 权限。如果不加这个,板子无法以 root 身份运行,会导致权限报错。
  • no_subtree_check:禁用子树检查,提高稳定性。
  1. 重启并生效服务
# 重新扫描 exports 文件并使配置生效
sudo exportfs -arv

# 重启 NFS 服务
sudo service nfs-kernel-server restart
  1. 避坑指南:网络与防火墙

NFS 依赖多个端口(RPC),如果防火墙开启,挂载必然失败。

# 直接关闭防火墙(开发环境推荐)
sudo ufw disable

U-Boot 设置

具体命令见第五节。

五:U-Boot 启动配置

目标:设置 U-Boot 环境变量,启动系统。

路线 A:TFTP 可用

适用于 U-Boot 阶段网络正常、可以从 Ubuntu 主机拉取内核和设备树的情况。

setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv

# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv

# 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'
saveenv

# 启动
boot

路线 B:TFTP 不可用

  • 作用:告诉板子“从 SD 卡读内核,去 192.168.10.200 挂载 NFS”。
  • 命令 (在串口终端执行):
# 1. 从 SD 卡读取内核和 DTB
setenv bootcmd 'load mmc 0:1 0x82000000 zImage; load mmc 0:1 0x88000000 am335x-bonegreen-gateway.dtb; bootz 0x82000000 - 0x88000000'

# 2. 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'
saveenv

# 启动
boot

六:Hello World

目标:验证开发板镜像是否正常工作。

  1. 编写与静态编译
  • 问题:如果动态编译后的程序运行时报 No such file,通常是根文件系统缺少对应的动态加载器/运行库,或者工具链 ABI 与 rootfs 不一致。
  • 临时解决:使用 -static 静态链接,先验证交叉编译和 NFS 运行链路是否正常。
  • 长期建议:保持工具链和 rootfs ABI 一致。本文使用 arm-linux-gnueabihf 工具链,因此 rootfs 也应选择 armhf
  • 命令 (在 Ubuntu 端):
#include <stdio.h>

int main() {
    printf("--------------------------------------\n");
    printf("  Hello, BBGG!                         \n");
    printf("  NFS cross-compile works perfectly!   \n");
    printf("--------------------------------------\n");
    return 0;
}
cd /home/dq/linux/nfs/rootfs/home/debian
# 编写代码
sudo vim hello.c 

# 交叉编译 (静态链接)
sudo arm-linux-gnueabihf-gcc -static hello.c -o hello
  1. 板上运行
  2. 命令 (在串口终端):
cd /home/debian
./hello
  • 结果:成功输出 Hello, BBGG!...

补充

在嵌入式 Linux 开发中,解决 GCC 版本冲突(重定义错误)架构检测失败(ISB 指令错误) 是适配旧内核(如 4.19)的关键步骤。以下是针对这两处修改的笔记整理:


1. 解决主机工具编译冲突 (yylloc 多重定义)

背景:当 Ubuntu 宿主机的 GCC 版本 >= 10 时,默认开启 -fno-common,导致内核自带的 dtc(设备树编译器)工具在链接时报错。

  • 修改文件:顶层 Makefile
  • 具体位置:搜索 KBUILD_HOSTCFLAGS 定义处(通常在 400 行左右)。
  • 修改内容
    在变量末尾手动添加 -fcommon 参数。
# 原代码:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

# 修改后:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 -fcommon $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

2. 解决架构降级导致的汇编错误 (isb 指令不支持)

背景:内核通过 cc-option 测试编译器。在使用硬浮点(Hard-Float)交叉编译器时,由于测试命令缺少 FPU 参数导致测试失败,内核 Makefile 会误认为编译器不支持 ARMv7,从而自动降级到 ARMv5,导致不识别 v7 指令 isb

  • 修改文件arch/arm/Makefile
  • 具体位置:搜索 CONFIG_CPU_32v7 所在行(通常在 65 行左右)。
  • 修改内容
    废除自动检测逻辑,直接硬编码(Hardcode)指定架构为 -march=armv7-a
# 原代码:
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a)

# 修改后(删除检测函数,直接指定架构):
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 -march=armv7-a
❌