1. 项目概览与部署方案选型
1.1 为什么需要“从TFTP启动”这套玩法
第一次拿到RK3588开发板时,很多人第一反应都是找一张SD卡,把镜像写好、插上、上电,然后盯着串口输出等系统起来。单板调试阶段这套流程确实无脑可靠,我自己一开始也是这么干的。但一旦你进入连续改内核、换设备树、反复刷rootfs的节奏,SD卡和U盘方案的瓶颈就暴露得很彻底:写卡本身要时间,写错了要重来,多块板卡并行测试时更是要一块块轮流伺候。等你经历过三四天“改一次配置插一次卡”的循环,就会明白为什么量产调试和系统集成岗位的人,几乎都会把网络化部署放在优先位置。
TFTP在这条链路里扮演的是“最早期引导”的角色。它足够简单、足够轻量,RFC 1350定义了最基础的传输逻辑,U-Boot内部直接内置了tftp命令支持,不需要在板卡上预先安装任何客户端软件,也不用折腾串口驱动的兼容性。用TFTP把内核镜像和设备树拉进内存之后,再通过NFS把根文件系统挂载上来,整个RK3588就能在没有任何本地存储介质的情况下完成启动,这就是典型的无盘网络启动。这套思路在数据中心和嵌入式产线里已经跑了很多年,稳定性没有问题,难点只在于配置细节是否到位。
这篇内容面向的读者,主要是手里有RK3588开发板、需要在openEuler 24.03上做系统适配或应用开发的工程师,也包括那些被“一板一卡”折磨到想上网络化部署的测试和运维同学。我会把从宿主机准备、U-Boot配置、TFTP引导、NFS根文件系统挂载到系统固化落地的完整链路拆开讲清楚,所有命令都是我在实际环境中跑过并验证的,你可以直接参考复现,不需要再靠零散帖子拼图。
1.2 硬件平台与适用场景
先明确一下硬件基线。RK3588是瑞芯微面向高性能AIoT和边缘计算场景推出的8核SoC,4颗Cortex-A76大核加4颗Cortex-A55小核,GPU是Mali-G610 MP4,自带6 TOPS算力的NPU。这颗芯片的接口丰富程度决定了它的玩法上限:双千兆网口(部分板卡)、PCIe 3.0、多路MIPI CSI/DSI、USB 3.0、SATA、HDMI 2.1等等。我手头的板卡是标准的EVB布局,双网口、带NVMe插槽和eMMC,整个调试过程以太网口作为TFTP/NFS入口。
适用这套部署方案的场景非常集中。第一种是内核和驱动开发调试,每次改动后不需要重新烧写存储介质,重启板卡就能加载新内核;第二种是多板卡联调和产线测试,镜像集中维护在服务器上,所有板卡同一套配置拉起,想换版本只改服务器上的文件;第三种是系统集成验证,比如在openEuler 24.03上验证Docker容器、AI推理框架、视频编解码库的兼容性,需要频繁重置系统环境。在这些场景里,网络化部署节省的是最容易被忽视却最昂贵的时间成本。
需要提一下的是,SD卡方案并没有被完全替代。TFTP+NFS更适合开发和测试阶段,如果你的产品已经定型、需要现场独立运行,最后还是一样要把系统固化到eMMC或NVMe上。我在这篇内容的第4节末尾会专门讲“从网络引导过渡到本地固化”的做法,这样一套流程走下来,开发和生产两个阶段都能覆盖。
2. 核心原理与部署链路拆解
2.1 RK3588上电到Linux的完整引导链路
理解原理比机械敲命令重要得多。RK3588的启动流程大致是这样的:芯片内部的BootROM上电后先加载固化在芯片里的引导代码,然后根据启动引脚的电平配置决定从哪个介质读取下一级引导程序,常见的有eMMC、SD卡、SPI NOR Flash等。在传统烧卡方案里,SPL(Secondary Program Loader)和U-Boot本体存放在SD卡或eMMC的特定偏移位置,BootROM按地址直接读取。而在网络化部署方案里,SPL和U-Boot仍然要放在本地介质上,因为TFTP还不能在没有IP协议栈的时候工作。
U-Boot跑起来以后,网络引导才正式登场。你需要在U-Boot环境变量里给板卡配置IP地址和服务器IP地址,然后通过tftp命令从服务器拉取两个关键文件:内核镜像Image和设备树dtb文件。这里有一个容易踩坑的细节:RK3588是64位ARM处理器,内核镜像必须以Image格式存在,用booti命令引导,而不是32位平台的uImage或bootm命令。很多从老平台转过来的工程师习惯性地用bootm加载uImage,结果直接卡死在“Wrong Image Format”,排查半天发现是格式不匹配。
内核和设备树被加载到内存地址后,U-Boot会读取bootargs环境变量,把启动参数传给内核。启动参数里最关键的一项是指定根文件系统的来源。如果root=/dev/nfs,内核就会在网络协议栈初始化完成后,通过NFS协议从服务器挂载根目录;如果root=/dev/mmcblk0p3,则从eMMC分区挂载。网络部署时还需要额外传递ip参数、nfsroot参数,告诉内核从哪里获取IP、从哪里挂载文件系统。这一串参数的含义我在第4节实操里会逐个拆开。
2.2 根文件系统挂载方式:NFS还是本地盘
网络化部署并不等于永远挂NFS。这里有两种玩法,我建议你根据自己当前阶段来选择。
第一种是纯NFS运行模式。整个rootfs放在宿主机目录,RK3588启动后通过NFS远程挂载,所有读写操作都落在服务器的磁盘上。这个模式的优点在于“改完立即生效”:你在服务器上修改rootfs里的任何一个文件,板卡重启后就是新状态,非常适合调试阶段反复调整库文件、驱动模块和启动脚本。缺点也明显,就是网络链路一旦抖动,系统可能直接卡死,I/O性能也受限于网口带宽和宿主机磁盘性能。
第二种是网络引导+本地固化模式。先用TFTP拉内核、NFS挂rootfs,把openEuler 24.03跑起来作为安装环境,然后通过dd或tar把整个rootfs拷贝到板载eMMC或NVMe分区,再调整U-Boot启动参数指向本地分区。第二阶段的部署里,TFTP的角色就变成了“安装引导器”,而不是“运行时依赖”。这样做的好处是前期不依赖本地介质反复烧写,最终运行环境又是本地高速存储,性能与可维护性兼得。
从稳定性角度说,我实际更推荐第二种模式作为最终交付形态。NFS远程挂载在实验室场景很爽,但一旦进入7x24小时运行,网络栈和NFS服务任何一个小故障都会导致文件系统异常,排查成本远高于本地存储。标题里说的“完美运行”,本质上就是要把系统从“能从网络拉起来”推进到“拉起来之后能稳定跑业务”。
2.3 为什么选openEuler 24.03而不是Ubuntu或Debian
RK3588的社区镜像一抓一大把,Ubuntu、Debian、Buildroot都有,为什么偏要选openEuler 24.03?我自己上手之后最大的感受是:openEuler是一个真正面向服务器场景打磨过的发行版,它在安全加固、服务管理、软件包更新策略上更贴合企业级项目的验收要求。
openEuler 24.03 LTS版本的内核基线比较新,aarch64架构的支持已经非常成熟,对RK3588这种ARMv8.2平台的基础指令集、虚拟化扩展、以及部分外设驱动都有良好的兼容性。系统默认使用systemd管理服务,服务单元文件的写法与主流发行版一致,不会出现“语法怪癖”。软件仓库里针对aarch64的ARM架构包覆盖度很高,从GCC工具链、Python运行环境到Docker、Nginx、MySQL这些常用组件都能直接安装,不需要从头源码编译。
另外,如果你的项目后续有国产化适配或者信创验收的需求,openEuler的生态和政策意义就体现出来了。当然,我不会把话说死,Ubuntu在RK3588上的社区资料确实更丰富,部分外设调试案例只能找到Ubuntu的参考,所以第5节里我会给出一套通用的外设适配方法,保证你在openEuler上也能照着主线的思路解决问题。
3. TFTP服务器与启动资源准备
3.1 宿主机搭建TFTP和NFS服务
先从宿主机开始准备。我的宿主机系统是Ubuntu 22.04,但实际上只要是Linux发行版,步骤基本一致,CentOS、openEuler本身作为服务器端也没问题。TFTP服务我选用的是tftpd-hpa,它是目前Linux下最常用的TFTP守护进程,配置简单,行为稳定。
安装命令非常简单:
sudo apt update sudo apt install tftpd-hpa nfs-kernel-server -y安装完成后,编辑TFTP配置文件/etc/default/tftpd-hpa,把服务根目录改成一个专门的目录,并确保TFTP_DIRECTORY不存在权限陷阱:
TFTP_USERNAME="tftp" TFTP_DIRECTORY="/srv/tftp" TFTP_ADDRESS="0.0.0.0:69" TFTP_OPTIONS="--secure --create"这里--secure表示只能访问TFTP根目录内的文件,可以防路径穿越;--create允许客户端上传文件,对某些调试场景有用,不需要上传可以去掉。修改完配置后重启服务:
sudo systemctl restart tftpd-hpa sudo systemctl enable tftpd-hpaNFS服务的配置在/etc/exports里。假设rootfs目录是/srv/openeuler/rootfs,加入下面一行:
/srv/openeuler/rootfs *(rw,sync,no_root_squash,no_subtree_check)然后重新导出目录:
sudo exportfs -ra sudo systemctl restart nfs-kernel-server注意:
no_root_squash这个参数意味着客户端root用户对NFS目录拥有完整root权限,这在嵌入式调试场景是常态,但如果宿主机处于多用户共享环境,要谨慎评估安全性。正式生产环境建议按客户端IP做访问控制,不要用*通配。
3.2 内核、设备树与rootfs的获取与整理
TFTP服务器NFS服务器的架子搭好了,接下来就是把“引导三件套”准备齐:内核镜像、设备树文件、根文件系统。
内核和设备树从哪里来?如果你的板卡是瑞芯微官方EVB或者常见的第三方开发板,一般都可以在板卡厂商提供的BSP包里找到。以Rockchip官方BSP为例,内核代码编译完成后,产物arch/arm64/boot/Image就是我们要的内核镜像,设备树文件通常在arch/arm64/boot/dts/rockchip/目录下,比如rk3588-evb1-v10.dtb、rk3588s-rock-5b.dtb这些,板卡型号不同文件名不同,用自己的板卡对应文件名。
也有一种更省事的方式:如果你之前已经刷过某个基于Rockchip BSP的Ubuntu或Debian镜像,直接从镜像的boot分区里把Image和.dtb文件拷贝出来用。我实测过,这种方式拿到的内核同样是BSP内核,对openEuler rootfs没有兼容性问题,省去了自己搭建交叉编译环境的麻烦。不过如果你需要修改内核配置或增加驱动模块,还是建议自己编译。
rootfs的准备相对麻烦一点。openEuler官方提供的是安装镜像(ISO)和容器镜像,不直接给一个打包好的嵌入式rootfs目录。我这里分享一条我验证过多次的路径:先下载openEuler 24.03的aarch64容器镜像,然后通过docker导出为一个完整的rootfs目录。
大致思路是这样的:
# 拉取aarch64的openEuler 24.03容器镜像 docker pull openeuler/openeuler:24.03-lts # 从容器导出rootfs目录 docker run --name openeuler-rootfs openeuler/openeuler:24.03-lts /bin/true docker export openeuler-rootfs | tar -x -C /srv/openeuler/rootfs docker rm openeuler-rootfs容器镜像的rootfs里缺少系统运行需要的一些基础配置,比如/etc/fstab、/etc/hostname、/etc/resolv.conf,这些在后续首次启动的时候要补上。另外,为了让系统能被正常管理,还需要在rootfs里安装systemd等基础软件包。如果你不方便用docker,也可以直接下载openEuler官方ISO,用losetup挂载后从squashfs或安装介质里提取rootfs,但操作量会大不少。
准备目录的时候就顺手把后续要用的目录结构建好:
sudo mkdir -p /srv/tftp sudo mkdir -p /srv/openeuler/rootfs sudo cp Image /srv/tftp/ sudo cp rk3588-evb1-v10.dtb /srv/tftp/设备树文件我习惯统一命名为rk3588.dtb,后续U-Boot配置里引用名字越短越不容易手误。内核镜像就保持Image这个名字,简单明了。
3.3 目录结构与权限校验
配置完以后,建议先做一轮自检,不要在板卡上电之后才发现服务器端有问题。
第一步,验证TFTP服务是否正常读取文件。在宿主机上执行:
tftp 127.0.0.1 tftp> get Image tftp> quit如果当前目录出现了Image文件,说明TFTP服务正常。如果没有生成文件,检查tftpd-hpa进程状态、监听端口和目录权限。TFTP运行用户是tftp,目录/srv/tftp至少要允许tftp用户有读权限,最简单的方式是sudo chmod -R 755 /srv/tftp。
第二步,验证NFS挂载。在一台Linux机器上执行:
sudo mkdir -p /mnt/openeuler-rootfs sudo mount -t nfs 127.0.0.1:/srv/openeuler/rootfs /mnt/openeuler-rootfs ls /mnt/openeuler-rootfs能列出rootfs内容,说明NFS导出正常。如果出现权限拒绝,重点检查/etc/exports里no_root_squash有没有生效,必要时重启服务再exportfs -ra。
第三步,检查rootfs的基本结构。一个可启动的rootfs目录至少要有/bin、/sbin、/etc、/lib、/usr、/var这些目录。如果发现某些基础命令缺失,可以在准备阶段就通过chroot进入rootfs安装。chroot前需要把必要的系统目录挂载进去:
sudo mount --bind /dev /srv/openeuler/rootfs/dev sudo mount --bind /proc /srv/openeuler/rootfs/proc sudo mount --bind /sys /srv/openeuler/rootfs/sys sudo chroot /srv/openeuler/rootfs /bin/bash在chroot环境里可以安装systemd、配置账号密码,但要注意chroot环境没跑内核,只能完成文件系统层面的准备工作,不能测试外设。
4. 实操全流程:从TFTP引导openEuler 24.03
4.1 U-Boot环境变量与网络参数设置
服务器端准备完毕,现在进入板卡侧。板卡上电前,先把串口线接好,建议用USB转串口模块接UART调试口,波特率通常是1500000(1.5Mbps)或115200,具体看板卡出厂设定。我的这块EVB板用1500000波特率,如果你用常用的SecureCRT或minicom连接,进U-Boot后看到的输出基本一致。
板卡上电后,在串口终端里按任意键打断自动启动流程,进入U-Boot命令行。首先确认板卡的网络是否正常。如果你不确定自己的网卡在U-Boot里有没有驱动,可以直接执行:
dhcp如果网络环境里有DHCP服务器,U-Boot会自动拿到IP地址;没有DHCP的话,就要手动设置。内网没有DHCP的情况下,我的习惯是固定一组静态IP:
setenv ipaddr 192.168.1.100 setenv serverip 192.168.1.200 setenv netmask 255.255.255.0 setenv gatewayip 192.168.1.1注意:ipaddr是板卡自己的IP,serverip是宿主机TFTP服务器的IP。这两个地址一定不要搞反,不然tftp命令会一直往错误的地址发起请求。我见过太多次“tftp超时”的排查记录,最后发现是serverip配成了板卡自己的IP。
配置网络后,先用ping验证链路。U-Boot的ping命令和Linux里的用法一样:
ping 192.168.1.200如果host 192.168.1.200 is alive,说明二层三层链路通畅,可以进行下一步。如果ping不通,先查网线、查服务器防火墙,再检查U-Boot的网卡驱动是否正常加载。
4.2 引导内核并挂载NFS根文件系统
网络通了,接下来就是真正的TFTP引导动作。推荐在U-Boot环境变量里把整个引导流程写好,而不是每次都手动敲命令,否则板卡一重启一切归零。
先加载内核和设备树到内存:
setenv kernel_addr_r 0x02080000 setenv fdt_addr_r 0x12000000 setenv bootargs 'root=/dev/nfs nfsroot=192.168.1.200:/srv/openeuler/rootfs,v3,tcp rw ip=dhcp console=ttyS0,1500000n8 earlycon'这里我给每个参数做个注释:
root=/dev/nfs:告诉内核根文件系统走NFS。nfsroot=192.168.1.200:/srv/openeuler/rootfs,v3,tcp:NFS服务器地址和导出目录,v3协议和tcp传输方式兼容性最好。如果你的NFS服务器只开了NFSv4,要把v3改成v4。ip=dhcp:让内核启动后通过DHCP获取IP,配合NFS挂载使用。如果你的网络没有DHCP,这里要写成ip=192.168.1.100:192.168.1.200:192.168.1.1:255.255.255.0::eth0:off这样的静态格式。console=ttyS0,1500000n8:串口控制台参数,波特率要和U-Boot保持一致。earlycon:提前输出内核早期启动日志,出问题时非常好用。
然后执行加载:
tftp $kernel_addr_r Image tftp $fdt_addr_r rk3588.dtb如果TFTP服务器工作正常,你会看到类似Bytes transferred = 31457280的输出。接下来用booti命令正式启动:
booti $kernel_addr_r - $fdt_addr_rbooti后面的-表示没有ramdisk,直接使用内核镜像和设备树。
正常启动的情况下,串口会刷出一大串内核日志,最后进入systemd初始化流程。这里有一个常见的坑:如果内核日志在VFS: Cannot open root device "nfs"或Unable to mount root fs报错卡住,大概率是NFS参数写成或者内核里没有编译NFS客户端支持。前者回头检查bootargs,后者需要重新编译内核开启CONFIG_ROOT_NFS和CONFIG_NFS_FS。
每次手动敲这些命令太麻烦,我建议把引导命令写入默认环境变量:
setenv bootcmd 'tftp $kernel_addr_r Image; tftp $fdt_addr_r rk3588.dtb; booti $kernel_addr_r - $fdt_addr_r' saveenv以后板卡上电自动走TFTP引导,省去重复输入。
4.3 openEuler首次启动的系统初始化
openEuler rootfs从NFS挂载后,系统能跑起来,但还不能算“完美运行”,因为它缺了很多仅靠rootfs里静态文件无法自动生成的配置。首次进入系统后,需要按顺序做几件事。
第一步是确认网络接口状态。openEuler 24.03使用systemd-networkd或NetworkManager管理网络,默认可能没有启用。通过串口登录root(密码在准备rootfs时通过chroot设置过),执行ip addr看看网卡有没有拿到IP。如果没有IP,手动启用DHCP:
nmcli device status nmcli device set eth0 managed yes nmcli device connect eth0如果系统里装的是NetworkManager,上述命令就够用。如果用systemd-networkd,则要创建/etc/systemd/network/20-wired.network文件,并启用systemd-networkd服务。第5节里我会专门讲静态IP配置。
第二步是修复基础系统配置。把下面这些文件补全:
/etc/hostname:写入你想要的主机名。/etc/resolv.conf:写入DNS服务器地址。/etc/fstab:NFS启动阶段fstab尽量不要挂载本地磁盘,否则会因找不到设备而报错。
第三步是安装基础工具包。容器镜像导出的rootfs往往连passwd、useradd这些基础命令都不全,先确认能执行dnf:
dnf install -y passwd sudo vim tar net-tools iproute openssh-server然后设置root密码:
passwd root这样串口和后续SSH登录就有保障了。
注意:NFS rootfs模式下,系统每次重启都会回到你在服务器上做的配置状态,并不会持久化运行时产生的会话配置。这在调试阶段是特性,不是bug,别把它当成系统坏了。
5. 部署后的系统配置与应用落地
5.1 静态IP、SSH与基础环境管理
网络启动模式下,IP地址经常会在DHCP和静态配置之间摇摆,对于一个要长期运行的RK3588节点来说,静态IP是基本诉求。
openEuler 24.03上我推荐用nmcli配置静态IP。先查看网络连接的名称:
nmcli con show假设有线网络连接名是eth0,执行:
nmcli con mod eth0 ipv4.addresses 192.168.1.100/24 nmcli con mod eth0 ipv4.gateway 192.168.1.1 nmcli con mod eth0 ipv4.dns "223.5.5.5 114.114.114.114" nmcli con mod eth0 ipv4.method manual nmcli con up eth0配置完成后,用ip addr确认IP已经生效。如果连接名不是eth0,先通过nmcli device查清楚,避免改错连接。
SSH是开发调试的刚需。一次把SSH服务配置到位:
systemctl enable sshd systemctl start sshd然后检查/etc/ssh/sshd_config里PermitRootLogin是否允许root登录。开发环境为了方便可以改成yes,生产环境还是建议创建普通用户。修改完之后重启sshd。
再顺手把开发工具链装齐:
dnf install -y gcc gcc-c++ make git cmake python3 python3-pip到这一步,RK3588就算是一个标准的ARM64开发节点了,后续编译、调试、跑脚本都没问题。
5.2 Docker社区版与AI推理环境(YOLOv8等)
openEuler 24.03上装Docker社区版的步骤不难,但有几个细节要注意。openEuler自带的软件源里可能没有docker-ce包,我们需要用Docker官方源或镜像源。
官方源的安装方式:
dnf install -y dnf-utils dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo dnf install -y docker-ce docker-ce-cli containerd.io systemctl enable docker systemctl start docker注意上面使用的是CentOS的源路径,因为openEuler的rpm包格式和CentOS兼容,实测可以正常安装。如果你的网络访问docker官方源不方便,就去找国内镜像源替换URL,这个大家都懂的。
Docker起来以后,给AI推理环境铺路。RK3588的NPU算力是6 TOPS,很多人想在openEuler上跑YOLOv8。这里的核心逻辑是先装Rockchip的NPU工具链(RKNN-Toolkit2),再把YOLOv8模型转换成RKNN格式。具体转换流程我在这里不展开,因为涉及Python环境依赖和模型文件准备,但有一点必须提醒:RKNN-Toolkit2的Python版本兼容性很重要,官方推荐Python 3.8到3.10,openEuler 24.03自带的Python 3.11在某些版本可能踩坑,建议用虚拟环境装。
如果不需要NPU加速,只是用CPU跑YOLOv8做功能验证,那就简单多了:
pip3 install ultralytics实测RK3588的A76大核跑YOLOv8n模型,CPU推理速度大概几百毫秒一帧,功能验证够用,实时推理还是得上RKNN转换后的NPU模型。
另外,很多人在RK3588上做视频处理时会用到MPP和RGA这两个Rockchip的硬件库。openEuler上如果找不到预编译包,就从Rockchip的官方repo拉源码编译,MPP负责视频编解码,RGA负责图像格式转换和缩放,这两个库是Rockchip多媒体方案的底层基石,YOLOv8图像预处理、摄像头视频流处理都依赖它们。
5.3 外设适配与调优:摄像头、音频与ADB调试
“完美运行”的一个重要维度是外设都能正常工作。RK3588的典型外设配置包括MIPI CSI摄像头、I2S音频编解码器(比如ES8388)、USB外设等,这些在openEuler上的适配思路有共通性。
摄像头这块,RK3588的视频通路依赖Media Controller框架和V4L2子设备。内核里需要确认有没有打开对应传感器驱动和ISP驱动,比如CONFIG_VIDEO_ROCKCHIP_ISP、CONFIG_VIDEO_ROCKCHIP_CIF。设备树里要正确配置传感器的I2C地址、复位引脚、供电时序。在openEuler上做摄像头适配时,最常见的问题不是驱动缺失,而是设备树里regulator和gpio的配置与板卡实际硬件不匹配,导致传感器上电后无法通过I2C探测到。排查方法是用i2cdetect扫描对应I2C总线地址,确认是否有设备应答。
ES8388这类音频芯片的调试也类似。先确认设备树里i2c节点、音频codec节点、sound卡节点都存在,然后检查内核日志里有没有codec注册成功的消息。如果录音或播放没有声音,重点检查alsa-utils的软件混音通道(amixer)默认是否处于静音状态,这个坑在RK3588上非常普遍,很多工程师调了半天硬件,结果只是amixer cset name='Playback Path' SPK没设置。
ADB连接RK3588板卡也是一个高频需求,尤其做安卓调试的人。不过在openEuler这种纯Linux系统下,ADB的角色不同。如果系统里跑了ADB服务端,可以通过adb connect远程连接其他安卓设备,RK3588上常见的用法是把板卡当作ADB主机去调试外接安卓设备。在openEuler上装adb很简单:
dnf install -y android-tools然后通过USB连接安卓设备,执行adb devices就能看到设备列表。
外设适配的核心原则是:先驱动层确认、再应用层验证。驱动有没有注册成功看内核日志,应用层能不能用看设备节点和工具输出,从下往上排查,比陷入“设备不工作”的模糊状态要高效得多。
6. 常见问题与故障排查手记
6.1 TFTP网络引导阶段的“翻车”现场
TFTP引导阶段踩过的坑,我基本都能背下来了,这里挑几个高频的列出来。
超时:U-Boot执行tftp后一直等待到超时。先排除网络物理链路,再看宿主机防火墙有没有放行UDP 69端口。很多Linux发行版默认防火墙策略不允许外部访问tftp,执行sudo ufw allow 69/udp或者sudo iptables -I INPUT -p udp --dport 69 -j ACCEPT。还有一个容易被忽视的问题是U-Boot网卡驱动异常,观察U-Boot启动日志里有没有网卡初始化失败信息。
文件不存在:tftp返回File not found。检查文件是否在TFTP根目录,文件名大小写是否完全一致,Image和image是不同文件。另外一个细节是tftpd-hpa对软链接的支持有坑,如果你的/srv/tftp/Image是通过软链接指向别处,某些tftpd版本传输大文件会失败,建议用真实文件而不是链接。
传输中断:大内核镜像传了一半就断。TFTP基于UDP,一般传输大文件需要开启blksize和windowsize选项,tftpd-hpa默认支持。如果中断频繁,检查服务器端和板卡之间的网络有没有丢包,用ping -s 1472测一下MTU是否过大。另外,U-Boot内核加载地址kernel_addr_r要保证内存区域没有被占用,RK3588上0x02080000一般是安全的。
6.2 NFS根文件系统的挂载故障
内核启动后停在挂载根文件系统这一步,是另一个重灾区。
最常见的报错是VFS: Unable to mount root fs via NFS。排查思路从bootargs入手。先确认nfsroot后面的服务器IP和目录路径格式没有写错,注意冒号和斜杠的位置。再确认内核里有没有编译NFS客户端和root over NFS支持,检查CONFIG_NFS_FS=y、CONFIG_ROOT_NFS=y、CONFIG_NFS_V3=y这三个配置是否开启。
第二个常见问题是NFS挂载成功但系统启动后网络不通,表现为systemd一直等到网络超时。这种情况多半是bootargs里的ip=参数配置不对,DHCP模式下可能拿到了IP但网关配置不对。建议在bootargs里显式指定IP,不要依赖DHCP,减少一个变量。
第三个问题在宿主机侧。NFS服务启动异常或者导出目录权限不对,客户端往往表现为挂载超时。宿主机执行showmount -e 192.168.1.200看能不能列出导出目录,不行就检查NFS服务状态和/etc/exports语法。
6.3 启动成功后的驱动与性能问题
系统能进到openEuler登录提示符,不等于所有硬件都正常。跑一遍dmesg | grep -i error,看看有没有驱动报错。如果发现某些外设找不到,比如摄像头、声卡、NPU设备节点缺失,大概率是设备树和内核驱动不匹配。
性能问题这块,RK3588跑openEuler时需要注意CPU调频策略。openEuler默认的cpufreq调控器可能是ondemand或schedutil,对实时性要求高的场景建议切换为performance:
echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor echo performance > /sys/devices/system/cpu/cpu4/cpufreq/scaling_governorRK3588的大小核架构,0-3号是A55小核,4-7号是A76大核,按需分别设置。
还有一类性能问题是NFS模式下的I/O延迟。如果应用对磁盘读写敏感,NFS挂载肯定不如本地NVMe。这种场景下就要执行第4节说的固化流程,把rootfs拷贝到eMMC或NVMe,切换成本地启动。拷贝命令大致如下:
mkdir -p /mnt/nvme mount /dev/nvme0n1p2 /mnt/nvme tar --numeric-owner -cf - / | tar --numeric-owner -xf - -C /mnt/nvme拷贝完成后,把U-Boot的bootargs改成root=/dev/nvme0n1p2 rootwait,再把bootcmd改成从本地介质加载内核,或者直接用ext4load命令从eMMC/NVMe分区加载Image和dtb。
6.4 一条老经验:用脚本固化平均部署时间
最后分享一个缩短整个部署时间的经验。
网络化部署的最高境界不是手敲命令,而是自动化。把下面这套流程固化成脚本放在服务器上:
- 服务器端脚本一键生成TFTP和NFS目录结构。
- 新内核或设备树编译完成后自动拷贝到
/srv/tftp。 - rootfs更新用docker重新导出并覆盖旧目录。
- 板卡端U-Boot环境变量预先配置好,上电自动启动。
这套流程跑通之后,一块RK3588板卡从裸机到进入openEuler命令行的时间能压缩到一两分钟以内,而且是完全无人值守的。相比之下,传统烧卡方式就算一切顺利也要五到十分钟,失败重来更是翻倍。对于需要同时维护几十块板卡的实验室或产线,这个效率差距是非常可观的。
我在实际部署中还发现一个细节:U-Boot环境变量里最好预留一个boot_net命令和一个boot_local命令,分别对应网络启动和本地启动。调试阶段用boot_net,系统固化完成后再切到boot_local,切换只需一句setenv bootcmd run boot_local; saveenv,非常顺手。
这套“TFTP引导启动、NFS加载rootfs、本地固化收尾”的组合拳,是我目前在RK3588上部署openEuler 24.03最顺手的一套流程。开发和调试阶段的灵活性、最终运行环境的稳定性,两头都没耽误。如果你手头正好有RK3588的板卡,不妨照着走一遍,遇到细节卡住的地方,大多数问题翻一翻这一节的排查清单就能找到答案。