前言

本文档记录基于 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

标签: 嵌入式

评论(4)

  1. 学习学习

  2. 专业啊

    1. 哈哈哈,都是皮毛

发表评论