copy_to_tftp.sh - TFTP文件复制脚本详解¶
脚本概述¶
copy_to_tftp.sh 是 IMX-Forge 项目中用于自动化复制编译产物到 TFTP 目录的辅助脚本。在网络启动开发流程中,每次编译内核或设备树后,都需要将产物复制到 TFTP 服务器目录才能被开发板下载。这个脚本简化了这个过程,支持灵活的路径配置,并提供文件验证功能。
核心功能¶
- 自动文件复制:将编译好的内核镜像(zImage)和设备树文件(DTB)复制到TFTP目录
- 源文件验证:复制前检查源文件是否存在,避免复制失败
- 目录自动创建:目标目录不存在时自动创建
- 工具链适配:优先使用 rsync,回退到 cp 命令
- 灵活配置:支持命令行参数覆盖默认路径
设计理念¶
这个脚本遵循"简单即美"的设计原则,专注于解决一个具体问题:快速将编译产物部署到 TFTP 目录。
为什么需要这个脚本:
- 提高效率:手动复制文件容易出错,路径容易写错
- 减少重复:开发过程中频繁修改代码、编译、部署,自动化复制能节省时间
- 统一路径:项目中所有开发者使用相同的 TFTP 路径配置
- 验证机制:复制前验证源文件存在,提前发现问题
在开发工作流中的位置¶
┌─────────────────────────────────────────────────────────────┐
│ 1. 代码修改 │
│ - 修改内核代码 │
│ - 修改设备树文件 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 编译构建 │
│ - ./scripts/build_helper/build-linux.sh │
│ - 生成 out/linux/arch/arm/boot/zImage │
│ - 生成 out/linux/arch/arm/boot/dts/.../*.dtb │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 部署到TFTP ← copy_to_tftp.sh 在这里 │
│ - ./scripts/server_helper/copy_to_tftp.sh │
│ - 复制 zImage 到 ~/tftp │
│ - 复制 DTB 到 ~/tftp │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 网络启动 │
│ - U-Boot: tftp 0x80800000 zImage │
│ - U-Boot: bootz 0x80800000 - 0x83000000 │
└─────────────────────────────────────────────────────────────┘
依赖关系¶
被集成到: - 构建脚本的后处理步骤 - 手动开发工作流 - CI/CD 流水线(可选)
参数说明¶
命令行参数¶
| 参数 | 说明 | 默认值 | 必需/可选 |
|---|---|---|---|
--kernel=PATH |
内核镜像文件路径 | out/linux/arch/arm/boot/zImage |
可选 |
--dts=PATH |
设备树文件路径 | out/linux/arch/arm/boot/dts/nxp/imx/imx6ull-aes.dtb |
可选 |
--tftp-path=PATH |
TFTP服务器目录路径 | ~/tftp |
可选 |
-h, --help |
显示帮助信息 | - | 可选 |
默认路径详解¶
内核默认路径¶
路径结构解析:
out/linux/ # 内核编译输出根目录
└── arch/arm/boot/ # ARM架构的启动镜像目录
├── Image # 未压缩的内核镜像
└── zImage # 压缩的内核镜像(默认使用)
为什么使用 zImage:
zImage是压缩后的内核镜像,占用空间更小- TFTP传输速度更快
- U-Boot会自动解压
设备树默认路径¶
路径结构解析:
out/linux/arch/arm/boot/dts/ # 设备树编译输出目录
└── nxp/imx/ # NXP i.MX 系列设备树
└── imx6ull-aes.dtb # AES开发板的设备树(二进制)
设备树文件命名规则:
.dts:设备树源文件(文本格式).dtb:编译后的设备树二进制文件(U-Boot使用)- 名称通常匹配板子型号,如
imx6ull-14x14-evk.dtb
TFTP目录默认路径¶
默认值展开:
~展开为用户的 home 目录(如/home/charliechen)- 最终路径:
/home/charliechen/tftp
为什么选择 ~/tftp:
- 用户目录有写权限,不需要 sudo
- 避免系统目录权限问题
- 符合 Linux 用户目录结构规范
执行流程¶
总体架构¶
┌─────────────────────────────────────────────────────────────┐
│ 1. 初始化阶段 │
│ - 设置默认路径 │
│ - 解析命令行参数 │
│ - 检测复制命令(rsync/cp) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 预处理阶段 │
│ - 切换到项目根目录 │
│ - 显示配置信息 │
│ - 展开 TFTP 路径中的 ~ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 复制内核镜像 │
│ - check_and_copy() 验证源文件 │
│ - 创建目标目录(如需要) │
│ - 执行复制操作 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 复制设备树文件 │
│ - check_and_copy() 验证源文件 │
│ - 创建目标目录(如需要) │
│ - 执行复制操作 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. 完成确认 │
│ - 显示成功消息 │
└─────────────────────────────────────────────────────────────┘
函数详解¶
usage()¶
作用:显示帮助信息并退出。
实现方式:
实现原理:
- 使用
sed提取脚本文件自身的注释部分 - 从
# Usage:行开始,到空行结束 - 去除
#前缀和空格 - 显示提取的帮助文本
好处:
- 帮助信息就在脚本文件中,单文件维护
- 代码即文档,不会不同步
- 符合 Unix 工具的传统
输出示例:
Usage: copy_to_tftp.sh [OPTIONS]
Options:
--kernel=PATH Path to zImage kernel file
(default: out/linux/zImage)
--dts=PATH Path to DTB file
(default: out/linux/arch/arm/boot/dts/nxp/imx/imx6ull-aes.dtb)
--tftp-path=PATH Path to TFTP directory
(default: ~/tftp)
-h, --help Show this help message
参数解析逻辑¶
代码片段:
while [[ $# -gt 0 ]]; do
case "$1" in
--kernel=*)
KERNEL="${1#*=}"
;;
--dts=*)
DTS="${1#*=}"
;;
--tftp-path=*)
TFTP_PATH="${1#*=}"
;;
-h|--help)
usage
;;
*)
echo "Error: Unknown option '$1'"
usage
;;
esac
shift
done
解析原理:
${1#*=}:bash 参数扩展,删除=及其之前的内容,保留等号后的值- 示例:
--kernel=/path/to/kernel→ 提取/path/to/kernel
参数展开示例:
| 输入 | 变量 | 值 |
|---|---|---|
--kernel=/tmp/test.zImage |
KERNEL |
/tmp/test.zImage |
--dts=custom.dtb |
DTS |
custom.dtb |
--tftp-path=/var/tftp |
TFTP_PATH |
/var/tftp |
复制命令选择逻辑¶
代码片段:
检测方式:
command -v rsync:检查命令是否存在&> /dev/null:隐藏输出(包括 stdout 和 stderr)- 存在则使用 rsync,否则回退到 cp
为什么优先使用 rsync:
| 特性 | rsync | cp |
|---|---|---|
| 增量传输(只传变化部分) | ✓ | ✗ |
| 传输进度显示 | ✓ | ✗ |
| 权限保留 | ✓ | ✓ |
| 速度(小文件) | 相当 | 相当 |
| 速度(大文件) | 更快 | - |
| 可用性 | 需安装 | 内置 |
命令参数说明:
rsync -ah --progress:-a:归档模式,保留权限、时间戳等-h:人类可读的输出格式-
--progress:显示传输进度 -
cp -v: -v:verbose 模式,显示复制的文件
check_and_copy()¶
作用:验证源文件存在并执行复制操作。
函数签名:
执行流程:
┌─────────────────────────────────────────┐
│ Step 1: 检查源文件是否存在 │
│ if [[ ! -f "${src}" ]] │
│ → 输出错误信息 │
│ → return 1 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step 2: 创建目标目录 │
│ mkdir -p "$(dirname "${dst}")" │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step 3: 执行复制 │
│ ${COPY_CMD} "${src}" "${dst}" │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step 4: 检查执行结果 │
│ if [[ $? -eq 0 ]] │
│ → 输出成功信息 │
│ → return 0 │
│ else │
│ → 输出失败信息 │
│ → return 1 │
└─────────────────────────────────────────┘
关键实现细节:
- 文件存在性检查:
- 使用
-f检查常规文件(排除目录、设备文件等) -
错误信息包含文件描述和完整路径,便于排查
-
目录自动创建:
dirname提取路径的目录部分- 示例:
/home/user/tftp/zImage→/home/user/tftp -p参数确保父目录不存在时递归创建-
如果目录已存在,不会报错
-
复制执行:
- 使用变量存储的命令(rsync 或 cp)
- 路径加引号防止空格问题
主执行流程¶
切换项目根目录:
路径解析:
为什么要切换到项目根目录:
- 相对路径(如
out/linux/...)基于根目录解析 - 脚本可以从任何位置执行
- 保证路径一致性
波浪号展开:
展开原理:
${变量/#模式/替换}:bash 参数扩展,从开头匹配模式并替换- 将
~替换为$HOME的值
示例:
| 输入 | 输出 |
|---|---|
~/tftp |
/home/charliechen/tftp |
~user/tftp |
~user/tftp(不变,只处理 ~) |
/var/tftp |
/var/tftp(不变,无 ~) |
为什么不直接使用 eval:
eval有安全风险(可能执行任意代码)- 参数扩展更安全、更高效
复制执行:
# 内核
KERNEL_DST="${TFTP_PATH}/$(basename "${KERNEL}")"
check_and_copy "${KERNEL}" "${KERNEL_DST}" "Kernel" || exit 1
# 设备树
DTB_DST="${TFTP_PATH}/$(basename "${DTS}")"
check_and_copy "${DTS}" "${DTB_DST}" "DTB" || exit 1
目标路径构造:
basename:提取文件名- 示例:
- KERNEL:
out/linux/arch/arm/boot/zImage→zImage - DTS:
out/linux/arch/arm/boot/dts/nxp/imx/imx6ull-aes.dtb→imx6ull-aes.dtb
错误处理:
|| exit 1:复制失败时立即退出脚本- 避免继续执行导致状态不一致
TFTP配置¶
TFTP服务概述¶
TFTP(Trivial File Transfer Protocol)是一个简单的文件传输协议,主要用于网络启动场景:
- 端口:UDP 69
- 无认证:任何客户端都可以访问
- 简单协议:只支持读写操作,不支持列出目录
- 小文件优化:适合传输内核、设备树等小文件
TFTP目录结构¶
标准TFTP目录布局:
~/tftp/
├── zImage # 内核镜像
├── imx6ull-aes.dtb # 设备树
├── uImage # U-Boot镜像(可选)
└── boot.scr # U-Boot脚本(可选)
权限要求:
为什么需要 777 权限:
- TFTP服务通常以
tftp用户运行 tftp用户需要读权限- 开发阶段简化权限管理
- 生产环境应使用更严格的权限
WSL下的特殊配置¶
WSL2网络模式:
WSL2有两种网络模式,影响TFTP可达性:
| 模式 | 特点 | TFTP可用性 |
|---|---|---|
| NAT | WSL在独立网段 | 需要端口转发 |
| Mirrored | WSL共享主机网络 | 直接可用(推荐) |
切换到Mirrored模式:
编辑 Windows 用户目录下的 .wslconfig:
重启WSL:
TFTP服务安装:
TFTP配置文件:/etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/charliechen/tftp"
TFTP_ADDRESS="192.168.60.1:69"
TFTP_OPTIONS="--secure"
配置说明:
TFTP_ADDRESS:必须绑定到开发板可访问的IP地址--secure:限制访问在TFTP目录内(安全)- 可选
--create:允许上传文件
Windows防火墙配置¶
问题:WSL2的网络流量经过Windows防火墙,默认阻止UDP 69。
解决方案:在管理员PowerShell中执行:
New-NetFirewallRule -DisplayName "WSL TFTP" `
-Direction Inbound `
-Protocol UDP `
-LocalPort 69 `
-Action Allow
验证规则:
测试端口:
输出示例:
ComputerName : 192.168.60.1
RemoteAddress : 192.168.60.1
RemotePort : 69
InterfaceAlias : 网桥
TcpTestSucceeded : False
注意:TFTP使用UDP,TcpTestSucceeded为False是正常的。
使用示例¶
基本用法¶
输出示例:
TFTP Copy Helper
================
Kernel: out/linux/arch/arm/boot/zImage
DTB: out/linux/arch/arm/boot/dts/nxp/imx/imx6ull-aes.dtb
TFTP dir: ~/tftp
Success: Copied Kernel to '/home/charliechen/tftp/zImage'
Success: Copied DTB to '/home/charliechen/tftp/imx6ull-aes.dtb'
All files copied successfully!
自定义内核路径¶
适用场景:
- 使用未压缩内核镜像
- 测试不同编译配置的输出
自定义设备树路径¶
# 指定自定义设备树
./scripts/server_helper/copy_to_tftp.sh --dts=out/linux/arch/arm/boot/dts/nxp/imx/imx6ull-14x14-evk.dtb
适用场景:
- 使用不同的开发板设备树
- 测试修改后的设备树
自定义TFTP目录¶
适用场景:
- 系统级TFTP服务
- 多项目共享TFTP目录
组合使用¶
# 完整自定义
./scripts/server_helper/copy_to_tftp.sh \
--kernel=build/output/zImage \
--dts=build/output/custom.dtb \
--tftp-path=/srv/tftp
查看帮助¶
在U-Boot中使用¶
复制完成后,在U-Boot中下载:
# 下载内核
=> tftp 0x80800000 zImage
Using ethernet@20b4000 device
TFTP from server 192.168.60.1; our IP address is 192.168.60.200
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
2.1 MiB/s
Bytes transferred = 6543210 (63d00a hex)
# 下载设备树
=> tftp 0x83000000 imx6ull-aes.dtb
Using ethernet@20b4000 device
TFTP from server 192.168.60.1; our IP address is 192.168.60.200
Filename 'imx6ull-aes.dtb'.
Load address: 0x83000000
Loading: #
456 KiB/s
Bytes transferred = 45678 (b26e hex)
# 启动
=> bootz 0x80800000 - 0x83000000
集成到构建流程¶
在build-linux.sh后自动调用:
创建别名(在 ~/.bashrc 中):
使用:
故障排除¶
常见错误¶
错误 1:内核文件不存在¶
现象:
原因:
- 内核尚未编译
- 编译失败
- 使用了错误的输出路径
解决方法:
- 确认内核已编译:
- 如果不存在,执行编译:
- 检查编译是否成功:
应该能看到 zImage、.config、System.map 等文件。
错误 2:设备树文件不存在¶
现象:
原因:
- 设备树未编译
- 设备树名称不匹配
- 内核配置未启用该设备树
解决方法:
- 检查已编译的设备树:
- 查找正确的设备树名称:
- 使用实际的设备树路径:
错误 3:TFTP目录权限不足¶
现象:
或在 TFTP 日志中看到:
原因:
- TFTP 目录权限不足
- 文件权限不足
- TFTP 服务用户无法访问
解决方法:
- 修改目录权限:
- 确保用户 home 目录可进入:
- 修改已复制文件的权限:
错误 4:rsync 未安装¶
现象:
脚本仍然工作,但使用 cp 而不是 rsync。
影响:
- 每次都复制整个文件
- 无进度显示
解决方法:
安装 rsync:
验证安装:
错误 5:相对路径问题¶
现象:
但文件确实存在。
原因:
- 从非项目根目录执行脚本
- 工作目录不正确
解决方法:
- 确保从项目根目录执行:
- 或使用绝对路径:
WSL 特定问题¶
问题 1:Windows 防火墙阻止 TFTP¶
现象:
U-Boot 中 TFTP 超时:
原因:
Windows 防火墙阻止 UDP 69 入站流量。
解决方法:
- 在管理员 PowerShell 中添加规则:
New-NetFirewallRule -DisplayName "WSL TFTP" `
-Direction Inbound `
-Protocol UDP `
-LocalPort 69 `
-Action Allow
- 验证规则:
问题 2:WSL2 NAT 模式网络隔离¶
现象:
- WSL 内部 TFTP 正常
- 开发板无法连接
原因:
WSL2 NAT 模式下,WSL 在独立网段。
解决方法:
- 切换到 mirrored 模式
编辑 C:\Users\<用户名>\.wslconfig:
- 重启 WSL:
- 验证网络模式:
应该能看到开发板网段的 IP。
问题 3:TFTP 服务未启动¶
现象:
或连接被拒绝。
解决方法:
- 检查服务状态:
- 启动服务:
- 验证监听端口:
应该看到:
调试技巧¶
启用详细输出¶
使用 cp -v(rsync 不可用时自动启用):
手动验证复制¶
# 手动复制内核
cp -v out/linux/arch/arm/boot/zImage ~/tftp/
# 验证文件存在
ls -la ~/tftp/zImage
# 验证文件大小
du -h out/linux/arch/arm/boot/zImage
du -h ~/tftp/zImage
测试 TFTP 连接¶
从WSL内部测试:
从开发板测试:
检查路径解析¶
# 在脚本中添加调试输出
echo "Kernel source: ${KERNEL}"
echo "Kernel dest: ${KERNEL_DST}"
echo "Working dir: $(pwd)"
echo "Home: ${HOME}"
设计决策说明¶
为什么使用 rsync 而不是 cp¶
rsync 的优势:
- 增量传输:只传输文件变化的部分
- 进度显示:显示传输进度和速度
- 断点续传:支持中断后继续传输
- 权限保留:更好地保留文件属性
为什么有 cp 回退:
- rsync 可能未安装在最小系统上
- 确保脚本在各种环境都能工作
- cp 是 POSIX 标准,更可移植
为什么需要文件验证¶
复制前验证的好处:
- 提前失败:在执行复制前发现问题
- 清晰错误:明确指出哪个文件找不到
- 节省时间:避免复制一半才发现源文件不存在
示例对比:
# 无验证
cp nonexistent.zImage ~/tftp/
cp: cannot stat 'nonexistent.zImage': No such file or directory
# 有验证
Error: Kernel not found at 'nonexistent.zImage'
为什么自动创建目录¶
设计考虑:
- 简化使用:用户不需要手动创建 TFTP 目录
- 一次配置:首次运行后目录结构就建立好了
- 幂等性:多次运行不会出错
实现方式:
-p 参数确保:
- 父目录不存在时递归创建
- 目录已存在时不报错
为什么支持命令行参数¶
灵活性考虑:
- 不同环境:开发、测试、生产环境路径可能不同
- 不同项目:可以复用脚本到其他项目
- 特殊需求:临时使用不同的文件或目录
默认值优先级:
- 硬编码的默认值
- 环境变量(未来可扩展)
- 命令行参数(最高优先级)
为什么切换到项目根目录¶
路径一致性:
好处:
- 相对路径总是从根目录解析
- 可以从任何位置执行脚本
- 避免路径混乱
示例:
# 从项目根目录执行
./scripts/server_helper/copy_to_tftp.sh
# 从项目子目录执行
cd out/linux
../../scripts/server_helper/copy_to_tftp.sh # 仍然工作
# 从其他位置执行
/home/charliechen/imx-forge/scripts/server_helper/copy_to_tftp.sh # 仍然工作
扩展和定制¶
添加环境变量支持¶
修改脚本:
# 在参数解析前添加
KERNEL="${KERNEL:-${DEFAULT_KERNEL}}"
DTS="${DTS:-${DEFAULT_DTS}}"
TFTP_PATH="${TFTP_PATH:-${DEFAULT_TFTP_PATH}}"
使用方式:
添加更多文件类型¶
修改脚本添加 U-Boot 支持:
# 在默认值中添加
DEFAULT_UBOOT="out/uboot/u-boot.imx"
# 在参数解析中添加
--uboot=*)
UBOOT="${1#*=}"
;;
# 在主执行中添加
if [[ -n "${UBOOT}" ]]; then
UBOOT_DST="${TFTP_PATH}/$(basename "${UBOOT}")"
check_and_copy "${UBOOT}" "${UBOOT_DST}" "U-Boot" || exit 1
fi
添加文件完整性验证¶
添加 md5 校验:
check_and_copy() {
local src="$1"
local dst="$2"
local desc="$3"
if [[ ! -f "${src}" ]]; then
echo "Error: ${desc} not found at '${src}'"
return 1
fi
mkdir -p "$(dirname "${dst}")"
# 计算源文件 MD5
local src_md5=$(md5sum "${src}" | cut -d' ' -f1)
${COPY_CMD} "${src}" "${dst}"
if [[ $? -eq 0 ]]; then
# 验证目标文件 MD5
local dst_md5=$(md5sum "${dst}" | cut -d' ' -f1)
if [[ "${src_md5}" == "${dst_md5}" ]]; then
echo "Success: Copied ${desc} to '${dst}' (verified)"
return 0
else
echo "Error: ${desc} copy verification failed"
return 1
fi
else
echo "Error: Failed to copy ${desc}"
return 1
fi
}
添加自动编译集成¶
创建包装脚本:
#!/bin/bash
# build_and_deploy.sh
set -e
echo "Building kernel..."
./scripts/build_helper/build-linux.sh
echo "Deploying to TFTP..."
./scripts/server_helper/copy_to_tftp.sh
echo "Done! Ready to boot from network."
使用:
添加日志功能¶
记录复制历史:
# 在脚本开头添加
LOG_FILE="${PROJECT_ROOT}/logs/tftp_deploy.log"
mkdir -p "$(dirname "${LOG_FILE}")"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "${LOG_FILE}"
}
# 在复制成功后记录
log_message "Copied ${KERNEL} to ${KERNEL_DST}"
log_message "Copied ${DTS} to ${DTB_DST}"
最佳实践¶
开发工作流¶
推荐的开发流程:
- 修改代码
- 编译内核
- 复制到 TFTP
- 网络启动测试
- 重复
一键化:
# 创建别名
alias rebuild='./scripts/build_helper/build-linux.sh && ./scripts/server_helper/copy_to_tftp.sh'
# 使用
rebuild
版本管理¶
建议:
- 不要将
~/tftp目录加入 git - TFTP 目录是运行时目录,不是源代码
- 在
.gitignore中添加:
安全考虑¶
生产环境注意事项:
- TFTP 无认证,不应暴露在公网
- 限制 TFTP 服务只监听内网接口
- 使用防火墙限制访问来源
- 定期清理 TFTP 目录中的敏感文件
性能优化¶
频繁开发时:
- 使用 rsync 的增量传输
- 考虑使用 NFS 挂载整个 rootfs
- 对于大型项目,使用增量编译
网络优化:
- 使用千兆网络
- 开发板和主机直接连接(避免交换机延迟)
- 调整 MTU 大小
相关文档¶
- WSL2 + TFTP 网络启动踩坑记 - WSL2 环境下 TFTP 配置详解
- build-linux.sh - 内核编译脚本
- logging.sh - 日志工具库
文档版本: 1.0 最后更新: 2026-03-15 脚本路径:
scripts/server_helper/copy_to_tftp.sh