TFTPNFS部署
337.3.10 TFTP 与 NFS 部署
本节没有编写 C 程序、驱动源码或设备树源码,因此标题下不添加臆造的 C 代码。视频实际操作的是 Ubuntu 虚拟机中的两个服务:
U-Boot 阶段:开发板 ──TFTP──> 虚拟机的 TFTP 目录(下载 zImage 和 DTB)
Linux 阶段:开发板 ──NFS ──> 虚拟机导出的根文件系统目录(挂载为 /)
服务端软件安装完成,只代表 Ubuntu 具备提供服务的能力;要让开发板真正启动,还必须继续完成网卡、IP、U-Boot 环境变量、内核启动参数以及根文件系统内容的配置。视频末尾也明确说明下一步才设置双网卡,因此本节不能被理解成“网络启动的全部工作已经完成”。
一、TFTP 和 NFS 的环境搭建
本课中的 Ubuntu 虚拟机是服务端,开发板是客户端。TFTP 和 NFS 虽然运行在同一台虚拟机上,但职责完全不同:
| 项目 | TFTP | NFS |
|---|---|---|
| 本课软件包 | tftp-hpa、tftpd-hpa | nfs-kernel-server、rpcbind |
| 服务端目录 | 视频为 /home/wfeng/tftpfile | 视频为 /home/wfeng/nfs |
| 开发板访问阶段 | U-Boot 阶段 | Linux 内核及系统运行阶段 |
| 访问对象 | 单个文件,如 zImage、DTB | 一整套目录树,即根文件系统 |
| 是否等同于“挂载” | 否,TFTP 是文件传输 | 是,Linux 将导出目录挂载为文件系统 |
视频终端中的软件版本包括 tftpd-hpa 5.2+20150808-1ubuntu4、nfs-kernel-server 1:1.3.4-2.5ubuntu3.7 和 systemd 245.4-4ubuntu3.23,说明演示环境属于 Ubuntu 20.04(Focal)这一代。新版本 Ubuntu 的服务管理或默认配置可能变化,复现时应先查看本机版本:
# 查看 Ubuntu 发行版信息;不要只凭桌面外观判断版本。
lsb_release -a
# 查看两个服务端软件包是否已经安装以及具体版本。
dpkg -l tftpd-hpa nfs-kernel-server rpcbind
本课通过 MobaXterm 的 SSH 窗口操作虚拟机;SSH 连接地址在画面中是 192.168.31.230,它只是当时用于管理虚拟机的地址,不等于上一课开发板网络中的 TFTP/NFS 服务地址 192.168.2.220。双网卡环境中,一张网卡可以处于管理/上网网段,另一张处于开发板直连网段,同一台虚拟机拥有多个 IP 很正常。
1.1 TFTP 搭建
TFTP 部署的目标是让开发板 U-Boot 作为客户端,从 Ubuntu 虚拟机的指定目录下载内核和设备树。视频把步骤概括为:创建目录、设置权限、安装软件、修改配置、重启服务。
这里安装的两个包作用不同:
tftpd-hpa中的字母d表示 daemon,提供 TFTP 服务端;这是开发板下载文件所必需的部分。tftp-hpa提供 TFTP 客户端,便于在 Ubuntu 本机或其他 Linux 主机测试服务;它不是服务端进程本身。
TFTP 没有用户登录和传输加密,应只在隔离、可信的开发网络中使用。服务器目录中不要放密码、私钥或无关文件。
1.1.1 配置 TFTP
1.1.1.1 创建服务目录
视频以用户 wfeng 登录,先回到主目录,再创建 tftpfile:
# 回到当前登录用户的主目录;视频中展开后是 /home/wfeng。
cd ~
# 创建用于存放内核和 DTB 的目录;mkdir 才是创建目录的命令。
mkdir tftpfile
# 列出目录,确认 tftpfile 已经存在;ls 只负责查看,不会创建文件或目录。
ls
目录名可以调整,但后面的 TFTP_DIRECTORY 必须写成同一个目录的绝对路径。~ 依赖当前用户和 shell 展开,系统服务配置中应写 /home/用户名/tftpfile 或其他明确路径。
视频随后执行:
# 这是视频原操作:让所有本机用户都拥有读、写、进入该目录的权限。
sudo chmod 777 tftpfile
777 不是笼统的“最高权限”,而是三组八进制权限:
- 第一个
7 = 4 + 2 + 1:目录所有者可读、可写、可进入; - 第二个
7:同组用户拥有相同权限; - 第三个
7:其他所有本机用户也拥有相同权限; - 对目录而言,执行位
x表示可以进入或穿过目录,不是“执行这个文件夹”。
给开发目录 777 是课程为了避免权限问题采用的宽松做法,不是 TFTP 下载的必要条件。开发板只执行下载(RRQ)时,TFTP 服务用户只需要能穿过父目录和读取目标文件;更合理的典型权限是目录 755、文件 644:
# 允许所有用户进入并读取目录清单,只有目录所有者可以增删文件。
chmod 755 "$HOME/tftpfile"
# zImage 和 DTB 放入目录后,使其可被 tftp 服务用户读取。
chmod 644 "$HOME/tftpfile/zImage" "$HOME/tftpfile/topeet_emmc_hdmi.dtb"
# 逐级检查 /home、用户主目录和 tftpfile 是否允许 tftp 用户穿过。
namei -l "$HOME/tftpfile/zImage"
若用户主目录权限为 700 或 750,即使 tftpfile 本身是 755,服务用户仍可能无法穿过父目录。可以有针对性地调整访问控制,或把服务目录放到 /srv/tftp、/var/lib/tftpboot 等不受个人主目录权限影响的位置,不应为省事把整个主目录改成 777。
1.1.1.2 安装服务端与客户端
视频安装命令为:
# 更新软件包索引;视频画面未执行,但新建环境通常应先更新索引。
sudo apt-get update
# 安装测试客户端 tftp-hpa 和服务端 tftpd-hpa。
sudo apt-get install tftp-hpa tftpd-hpa
安装输出中出现:
tftpd user (tftp) already exists, doing nothing.
tftpd-hpa directory (/home/wfeng/tftpfile) already exists, doing nothing.
这说明当前虚拟机曾配置过 tftp 用户和服务目录,安装程序只是发现它们已存在;不能据此认为一台全新虚拟机也会自动采用 /home/wfeng/tftpfile。用户名、目录和配置必须按自己的实际环境检查。
1.1.1.3 修改 /etc/default/tftpd-hpa
视频使用 Vim 打开配置文件:
# 以管理员权限编辑 tftpd-hpa 的发行版默认参数文件。
sudo vim /etc/default/tftpd-hpa
刚打开时,视频中的文件内容为:
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/wfeng/tftpfile"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
四个配置项的含义是:
| 配置项 | 含义 | 注意事项 |
|---|---|---|
TFTP_USERNAME | 服务端降低权限后使用的本地账户 | 通常由软件包创建为 tftp,不要改成普通登录用户来规避权限问题 |
TFTP_DIRECTORY | 允许客户端访问的服务端根目录 | 必须是实际存在的绝对路径;视频路径只适用于用户 wfeng |
TFTP_ADDRESS | 监听地址和端口 | :69 表示在可用地址上监听标准 TFTP 初始请求端口 UDP 69 |
TFTP_OPTIONS | 传给 in.tftpd 的额外选项 | 每个选项必须按命令行规则书写,不能随意拼接连字符 |
课件截图把最后一项写为:
TFTP_OPTIONS="-l-c-s"
视频实际编辑时还短暂出现过 -l-c--s,画面叠字指出“这里我多打了一个 -”。即使去掉多出的一个连字符,-l-c-s 也不是规范的短选项写法。三个独立选项应写为 -l -c -s,或者按短选项规则合并为 -lcs;不能在同一个参数内部重复插入选项前缀。
三个选项的真实作用是:
-l/--listen:让in.tftpd以独立监听模式运行;Ubuntu 的服务启动脚本通常已经负责传入监听参数,手工重复指定没有必要。-c/--create:允许远端客户端上传并新建文件;它与 U-Boot 下载已有zImage、DTB 无关。-s/--secure:启动后把指定目录作为受限根目录,客户端只需请求根目录下的相对文件名。
对本课“开发板只下载已有文件”的用途,保留发行版原有配置更清楚:
TFTP_OPTIONS="--secure"
只有确实要让客户端上传并新建文件时,才考虑:
TFTP_OPTIONS="--secure --create"
同时还要给 tftp 服务用户最小必要写权限,并限制可访问网络。--create 不会修复普通文件权限问题,反而会扩大未经认证的写入面。Debian 的 tftpd-hpa 维护说明也明确指出,只有需要上传不存在的新文件时才添加 --create;各选项的定义见 Debian 官方 in.tftpd(8) 手册和 tftpd-hpa 软件包说明。
在 Vim 中修改后,先按 Esc 退出插入模式,再输入 :wq 并回车,表示保存并退出。wq 是 Vim 命令,不是 Linux shell 命令。
1.1.1.4 重启并验证 TFTP
视频使用兼容命令重启服务:
# 视频原命令;在 Ubuntu 20.04 中最终由 systemd 管理服务。
sudo service tftpd-hpa restart
命令立即回到提示符且没有输出,不足以证明配置正确。尤其是选项拼写错误时,必须主动检查状态和日志:
# 重启服务;现代 Ubuntu 推荐直接使用 systemctl。
sudo systemctl restart tftpd-hpa
# 查看服务是否处于 active (running);--no-pager 防止进入翻页器。
sudo systemctl status tftpd-hpa --no-pager
# 若启动失败,查看最近 30 行该服务日志。
sudo journalctl -u tftpd-hpa -n 30 --no-pager
# 确认有进程监听 UDP 69;-l 显示监听,-u 只看 UDP,-n 不解析名称,-p 显示进程。
sudo ss -lunp | grep ':69'
把 zImage 和 DTB 放入 TFTP_DIRECTORY 后,还应从客户端实际下载一次。若同机测试成功而 U-Boot 失败,继续检查虚拟机板卡侧网卡、开发板和服务器 IP、文件名大小写、防火墙以及 U-Boot 选择的以太网口。TFTP 初始请求使用 UDP 69,后续数据会话使用服务器选出的传输端口,不能简单地把它当成只存在一条 TCP 69 连接。
1.2 NFS 搭建
NFS 服务端把 Ubuntu 中的一个目录导出给网络客户端。本课程后续会让 Linux 内核把这个目录挂载为根目录 /,所以该目录最终必须包含 /bin、/etc、/lib、/sbin、/usr、linuxrc 或 init 等完整根文件系统内容。创建一个空的 nfs 目录只完成了容器准备,空目录本身不能启动 Linux 用户空间。
1.2.1 安装应用
视频安装命令为:
# 安装内核态 NFS 服务端配套工具和 RPC 端口映射服务。
sudo apt-get install nfs-kernel-server rpcbind
nfs-kernel-server提供 NFS 服务端管理工具、导出表处理及相关 systemd 服务;实际文件服务由 Linux 内核的 NFS server 支持完成。rpcbind维护 RPC 程序号与网络端口的映射,NFSv3 和mountd等服务通常会用到。NFSv4 对它的依赖方式不同,但本课程的旧内核/根文件系统环境保留该包更有兼容性。
安装过程中,画面曾出现:
Job for nfs-server.service canceled.
Creating config file /etc/exports with new version
软件包仍继续完成安装。安装时服务尚没有有效导出项,或者依赖服务正在重新配置,都可能使首次自动启动没有完成;因此不能只看安装命令返回,还要在写好 /etc/exports 后重新加载导出表并检查服务状态。
Ubuntu 官方文档给出的服务端必需包是 nfs-kernel-server,并说明导出目录应写入 /etc/exports。不同 Ubuntu 版本的 NFS 配置文件和 systemd 单元可能不同,详见 Ubuntu Server 官方 NFS 文档。
1.2.2 创建 NFS 文件(实际是目录)
原笔记把这一节误写成“使用 ls 创建 NFS 文件”和“使用 make 命令”。视频实际执行的是:
# 回到当前用户主目录,避免把 nfs 建到不确定的当前路径。
cd ~
# 创建名为 nfs 的目录;命令是 mkdir,不是 make、make r 或 ls。
mkdir nfs
# 查看主目录,确认 nfs 目录已出现。
ls
nfs 在这里是目录名,不是一个名为 nfs 的普通文件,也不是 NFS 服务的配置文件。目录叫什么可以自行决定,关键是 /etc/exports 和后续 U-Boot 的 nfsroot= 参数必须指向同一个实际绝对路径。
正式作为网络根文件系统前,还需把已经制作好的根文件系统完整复制或解包到这里,并保留所有者、权限、符号链接和特殊文件。随意使用普通图形复制可能丢失这些元数据;本节没有演示根文件系统部署命令,因此不能凭空补写某个压缩包名称或复制流程。
1.2.3 修改文件
视频编辑的是 NFS 导出表文件,而不是“打开文件夹”:
# 以管理员权限编辑 NFS 服务端导出规则。
sudo vim /etc/exports
视频要求在文件末尾新增且不要以 # 开头:
/home/wfeng/nfs *(rw,sync,no_root_squash)
这行的语法是“服务端目录 + 客户端范围 + 导出选项”:
| 字段 | 含义 | 风险或限制 |
|---|---|---|
/home/wfeng/nfs | 服务端要导出的绝对目录 | 必须按自己的用户名和实际目录修改 |
* | 允许任意可到达服务器的客户端申请挂载 | 范围过宽,只适合隔离实验网,不应直接用于办公网或公网 |
rw | 允许客户端读写 | 开发板 rootfs 需要写 /var、/tmp 等目录时通常需要 |
sync | 数据提交到稳定存储后再回复写请求 | 更重视一致性,性能可能低于 async |
no_root_squash | 不把客户端 UID 0 映射为匿名用户 | 客户端 root 可按服务器 root 身份改动导出目录,风险很高 |
# 在 /etc/exports 中表示注释起始符。若在配置行前加 #,这一整行不会生效。客户端说明与圆括号之间不能留空格;官方 exports(5) 的语法要求选项列表紧跟对应客户端,例如 192.168.2.120(rw,sync),不能写成 192.168.2.120 (rw,sync)。
课程写法同时使用 * 和 no_root_squash,意味着网络中任意能访问 NFS 服务的主机都可能以 root 权限修改这套根文件系统。为了让开发板的 root 用户能够维护网络根目录,实验环境可能确实需要 no_root_squash,但应至少把客户端限制为开发板实际 IP。例如沿用上一课开发板地址时,可以写成:
/home/wfeng/nfs 192.168.2.120(rw,sync,no_subtree_check,no_root_squash)
这里的 192.168.2.120 必须替换成开发板在 Linux 阶段真正使用的地址;若地址会变化,可限制为专用板卡子网,而不是无条件使用 *。新增的 no_subtree_check 关闭对子目录导出的子树一致性检查,适合文件频繁重命名的开发根目录,但它不是视频原配置,是否使用应按自己的导出布局决定。
Ubuntu 官方文档特别提醒:no_root_squash 允许客户端 root 修改 root 所有的服务端文件,在多用户环境中可能造成安全问题;还建议把允许的主机声明写得尽可能具体。参阅 Ubuntu Server NFS 配置说明和 Ubuntu exports(5) 官方手册。
保存 /etc/exports 后,视频执行旧式 init 脚本:
# 视频原命令;Ubuntu 20.04 的脚本最终转交给 systemd。
sudo /etc/init.d/nfs-kernel-server restart
画面返回:
Restarting nfs-kernel-server (via systemctl): nfs-kernel-server.service.
这正说明 /etc/init.d/ 命令只是兼容入口。更清晰的现代操作和验证流程是:
# 重新读取 /etc/exports,并详细显示重新导出的项目;不必为每次改规则都先重启整台虚拟机。
sudo exportfs -rav
# 重启 NFS 服务,使相关守护进程重新读取配置。
sudo systemctl restart nfs-kernel-server
# 检查服务是否成功运行;不要把“命令执行完”当成“服务一定正常”。
sudo systemctl status nfs-kernel-server --no-pager
# 显示当前实际生效的导出目录及其选项。
sudo exportfs -v
# 查看失败或警告信息,尤其关注 exports 语法、目录不存在和权限错误。
sudo journalctl -u nfs-kernel-server -n 50 --no-pager
Ubuntu 的 exportfs 手册说明,修改 /etc/exports 后可以执行 exportfs -ra,也可以重启 NFS 服务;exportfs -v 则用于查看有效选项。参阅 Ubuntu Focal exportfs(8) 手册。
1.2.3.1 从配置完成到可启动还缺什么
完成本课命令后,应逐项确认:
-
tftpd-hpa与nfs-kernel-server都是 active 状态; - TFTP 根目录里已经放入文件名完全匹配的
zImage和 DTB,服务用户能够读取; -
sudo exportfs -v能看到实际 NFS 导出规则,而不只是/etc/exports中有一行文字; - NFS 导出目录中已经部署完整根文件系统,并保留权限、所有者和符号链接;
- 虚拟机板卡侧网卡与开发板互通,TFTP/NFS 绑定或防火墙规则允许该接口;
- U-Boot 的
serverip、ipaddr、TFTP 文件名和装载地址正确; - Linux 启动参数中的
root=/dev/nfs、nfsroot=、ip=与这里的服务器地址和导出目录一致; - 内核已经内建早期网卡、IP 自动配置和 NFS 根文件系统支持。
常见故障可按阶段判断:
| 现象 | 优先检查 |
|---|---|
systemctl restart tftpd-hpa 失败 | TFTP_OPTIONS 拼写、目录是否存在、UDP 69 是否被占用、日志 |
U-Boot 报 TFTP error: File not found | 文件名大小写、TFTP 根目录、文件读权限、是否误带服务端绝对路径 |
| U-Boot TFTP 一直超时 | 物理链路、网卡模式、IP/掩码、防火墙、服务监听地址 |
exportfs -ra 报语法错误 | 客户端与括号之间的空格、路径、逗号和括号是否正确 |
| TFTP 成功但内核无法挂载 NFS 根 | bootargs、NFS 导出范围、rpcbind/NFS 服务、内核网卡和 NFS 支持 |
NFS 挂载后出现 Permission denied | 服务端路径权限、UID/GID、root_squash、父目录执行权限 |
| 已挂载但用户空间启动失败 | 根文件系统内容、init/linuxrc、动态链接器、库、权限和架构是否匹配 |
最后要记住:本课搭建的是虚拟机服务端,不是通过这几条命令自动“搭建上网卡”。视频最后提到的双网卡模式、开发板串口、U-Boot 网络参数和启动命令属于后续步骤。只有服务端、网络链路、引导参数和根文件系统四部分全部匹配,上一课展示的“TFTP 下载内核与 DTB + NFS 挂载根文件系统”才能稳定复现。