
开发板NFS下载一直卡在“loading T”这个问题做嵌入式Linux的兄弟十有八九都遇到过。烧完内核、文件系统结果U-Boot启动时一串乱码似的点号里夹着一个孤零零的T然后屏幕就冻住了按什么键都没反应。我第一次碰到这个现象的时候以为是开发板硬件坏了换了网线、换了交换机、换了电源折腾到半夜最后发现是NFS服务端一个设置的问题。这篇文章就把我当时排查的思路、踩过的坑以及后来总结出一套定位方法完整讲一遍希望能帮到正在被这个T折磨的人。1. 先搞懂那个T到底代表什么1.1 U-Boot打印信息背后的含义U-Boot在通过网络加载内核镜像时会周期性地打印.表示等待重传。当它打印出T时说明有一个数据包被完整接收了但校验或格式检查没通过或者对端的响应没有完全匹配预期的协议流程。这个机制类似TCP的重传只是U-Boot的实现简单得多——它不做滑动窗口也不做拥塞控制只靠超时重传。所以屏幕上会看到一串点隔一段时间冒出一个T本质就是“发包了没回应重传偶尔收到个包但不对标记T”。理解了这一点整个问题就被拆成两个方向要么发送端的包根本没出去要么接收端的回包有问题。1.2 NFS下载在U-Boot里的实际执行链U-Boot的NFS下载流程是这样的主机通过tftp或nfs命令发起请求U-Boot解析NFS协议和服务器端建立mount连接然后通过NFS协议读取文件数据。这个流程比tftp复杂很多因为NFS早期版本v2/v3基于UDP或TCP的RPC调用需要先做portmap/portmapper协商再拿mount端口最后才会真正读取文件。所以当你执行nfs 0x20000000 192.168.1.10:/rootfs/uImage的时候U-Boot并不是直接去那个路径读文件而是分了好几步。任何一步出现问题表现都是卡在loading状态。提示U-Boot的NFS实现和Linux内核的NFS客户端实现差别很大。前者是最简实现不支持authentication不支持NFSv4很多只支持v2或v3端口协商的逻辑也写得很死。这就导致一些在Linux下正常工作的NFS服务U-Boot却连不上。这个环节最坑的地方在于U-Boot根本不告诉你卡在哪一步。它不打印RPC调用的详细过程也不显示mount失败的错误码就给你一个loading加上一串点。所以排错的第一步是把黑盒变成白盒——远端抓包。2. 常见原因全景拆解为什么NFS会一直loading2.1 网络链路问题比想象中更容易忽略很多人第一反应是NFS配置错误但实际排查中发现最频繁“翻车”的反而是最基础的网络链路。开发板和PC通过交换机连接时有些交换机端口开启了STP生成树协议端口从Disable到Forwarding状态需要几十秒。这段时间内开发板的ARP请求全部被交换机丢弃U-Boot自然ping不通服务器。另一个常见问题是MLT/聚合链路。如果服务器网卡配置了bonding或team但开发板直连到这个服务器的某个物理网口流量可能走到另一个网口上导致链路不同。用ethtool看一下服务器网口的实际链路状态再确认是不是有多个IP在同一网卡上。无线网卡才是真正的重灾区。拿笔记本无线连路由器开发板有线插到路由器上这种拓扑十有八九开局就卡。原因在于无线AP默认开启了AP隔离或者客户端隔离有线口和无线口不能互通。我见过一个案例A同学拿USB无线网卡当服务器网卡开发板直连到电脑的有线口然后手动启用Internet共享——共享一开防火墙策略也变了NFS服务端口被拦U-Boot一直在loading折腾了一下午才发现是Windows防火墙的锅。2.2 U-Boot端环境变量与网络参数设置有讲究U-Boot里与NFS相关的环境变量主要是bootargs但bootcmd中的nfs命令解析出的serverip和ipaddr同样关键。排查时至少要把以下变量打出来或确认一遍ipaddr开发板自己的IP。serveripNFS服务器的IP。gatewayip如果不在同一网段必须配网关。netmask子网掩码写错了会直接导致ARP都发不出去。U-Boot环境变量有个特点saveenv之后如果换了开发环境比如从公司网换到了家用路由器IP地址可能还是旧的会造成IP冲突或者无法通信。还有一个隐蔽的问题bootargs里的ipdhcp和U-Boot用nfs命令下载镜像是两回事。nfs命令自己走的是U-Boot的协议栈采用静态配置的IP而ipdhcp是Linux内核启动时用的参数。两者完全可以一个通一个不通。如果你看到U-Boot能ping通服务器但内核启动后NFS挂不上根文件系统那就要单独去看内核网络配置和DHCP服务。2.3 服务器端NFS服务的配置细节服务器端是大多数人排查时的死角因为“感觉服务器应该没问题”但往往问题就出在这里。第一NFS服务没启动或版本不匹配。Ubuntu 22.04默认的NFS是v4U-Boot很多不支持v4需要显式在/etc/nfs.conf里开v3或降级到NFSv2。如果你的U-Boot版本较老支持的NFS版本太旧还要在exports文件里加vers3选项。第二exports路径写错。/etc/exports里写的是/rootfs *(rw,sync,no_root_squash)但U-Boot请求的是/rootfs/uImage这个路径解析是服务器端做的。如果/rootfs其实是一个软链接指向其他目录有些NFS实现会解析到不同的inode导致U-Boot挂载成功但读取文件时返回错误。第三no_root_squash这个参数。U-Boot的NFS客户端默认发送的UID/GID是0也就是root。如果服务器端默认开启root_squash文件属主不是root或者文件权限是600而不是644NFS客户端就会因为没有权限而无法打开文件。经典表现就是mount成功但读取文件列表或打开文件时一直卡住或报错。2.4 内核配置与根文件系统的问题这里的“内核配置”包含两层意思一是U-Boot引导时加载的内核镜像本身有没有打开NFS rootfs相关选项二是内核启动后挂载根文件系统的参数对不对。如果bootargs里设置了root/dev/nfs nfsroot192.168.1.10:/rootfs,内核启动时会通过NFS挂载根文件系统。此时要求内核开启了CONFIG_ROOT_NFSRoot file system on NFS文件系统驱动也要编进去。如果这个选项没开内核日志会提示VFS: Unable to mount root fs via NFS表现为启动到一半就卡死。这类问题有个特征U-Boot下载内核、执行内核都正常内核打印了若干日志后停住。此时切到串口控制台能看到内核那边的NFS错误信息。针对这种情况单独修改U-Boot是无效的必须重新配置编译内核或者改用initramfs方式启动。3. 系统化排查流程从复现到定位再到修复3.1 第一件要做的事抓包遇到loading T我刚学这个的时候是瞎试后来一个大佬教了我一个习惯排查前先抓包。在服务器上执行tcpdump抓UDP和TCP相关端口sudo tcpdump -i eth0 host 192.168.1.100 and 192.168.1.10 -vv执行U-Boot的nfs命令后观察抓到的流量。正常流程能看到类似这样的包ARP request - ARP reply portmap getport request - response mount request - response nfs read request - response如果只看到ARP说明U-Boot还没能建立起基本网络通信如果看到portmap请求但没有响应那大概率是NFS服务没监听你要的版本如果看到mount请求但被拒绝是exports配置的问题。抓包是整个排查流程里性价比最高的一步它能直接告诉你问题边界在哪一端。3.2 排查链路从物理到逻辑逐层递进先做物理层验证。看开发板和交换机的网口LED是否都亮如果是自动协商确认速率是千兆还是百兆。部分老开发板网卡芯片的PMIC供电不稳会导致网口偶尔link up偶尔link downU-Boot的tftp或nfs自然超时。把网线插到电脑上看看是否正常识别排除网线问题。再做网络层验证。在U-Boot环境里执行ping 192.168.1.10确认开发板能ping通服务器。如果ping的结果显示“host not responding”问题在网络配置层面。T出现之前如果能看到多次Retrying...基本就是U-Boot收不到包或收不全包。此时要在服务器端看ARP表arp -n ip neigh show如果ARP表里没有开发板的MAC地址说明ARP请求没到服务器或者服务器回包没到开发板。这一步可以把无线网络、交换机隔离等问题直接暴露。3.3 验证NFS服务端的状态在服务器上执行showmount -e看导出的目录列表showmount -e localhost如果没有输出或提示“mount: RPC: Program not registered”说明NFS服务没有在运行。再检查一下服务是否在监听sudo netstat -tulpn | grep -E 2049|111如果2049端口没有监听服务异常。此时重启NFS服务sudo systemctl restart nfs-kernel-server # Debian/Ubuntu sudo systemctl restart nfs-server # RHEL/CentOS重启后再次测试。如果是老式NFSv3还需要确保rpcbind服务是运行的。提示有的系统在firewalld或ufw里默认阻止NFS服务即使本地能showmount开发板的请求也会被丢弃。排查时要临时加规则或直接关闭防火墙进行测试定位问题后再按需加白名单。3.4 U-Boot侧的命令级别排查U-Boot环境里除了nfs命令还有一个更基本的tftp命令。先用tftp从服务器拉一个小的测试文件tftp 0x20000000 test.bin如果tftp能下载成功说明基础网络是通的问题定位在NFS协议协商环节。如果tftp也是超时那就是链路或者服务器防火墙问题。还有一种情况tftp下载成功但nfs命令还是loading。此时可以试试U-Boot的nfs命令带不同参数比如先只mount不读取nfs 0x20000000 192.168.1.10:/test/boot.bin给U-Boot设置的服务器IP要在同一子网或者配置好网关。老版本U-Boot对NFS实现的要求非常苛刻——它对RPC层的XID绑定是固定的如果一个RPC请求超时后重新发送XID没变服务器端可能出现缓存旧响应的情况。3.5 修复方案汇总不同根因对应的不同解法链路问题改交换机端口模式关掉STP、确认trunk链路是access口而不是tagged口直连PC测试。网络配置问题调整ipaddr、serverip、gatewayip、netmask保证同一网段或确保网关可达。NFS版本问题在/etc/nfs.conf里修改或添加[nfsd] vers3y vers4y vers4.1y如果是基于busybox的NFS客户端还需要确认busybox编译时开启了NFS挂载支持。exports配置问题确保/etc/exports里导出目录存在权限正确/rootfs *(rw,sync,no_root_squash,no_subtree_check)改完/etc/exports后执行exportfs -ra生效。防火墙问题放行或临时关闭sudo ufw disable sudo firewall-cmd --add-servicenfs --permanent sudo firewall-cmd --add-servicemountd --permanent sudo firewall-cmd --add-servicerpc-bind --permanent内核配置问题重新配置内核的Root NFS选项或者改用initramfs。最稳妥的做法是把内核和文件系统都放到SD卡或eMMC上启动先把业务跑通再回头调NFS。4. 实战案例复盘四种典型错误的解决全过程4.1 案例一换网线解决了却是最坑的一次某项目现场开发板之前的启动一直正常某天突然开始loading T。我们看了bootargs、检查了exports、抓了包抓到的包很奇怪——ARP请求能看到但服务器回的ARP包开发板像是没收到。折腾了两个多小时最后用笔记本电脑替代开发板去连接那条网线发现笔记本能正常获取IP但ping的时候丢包率高达50%。一测网线发现那条线的线序有一段是反的——是手工压线的时候压错了。换了根厂线之后一切正常。这个案例说明什么物理层影响传输成功率但不一定直接全断。开发板和PC的网络芯片在收包容错能力上有差异开发板的网卡驱动对错误帧可能直接丢弃而PC可能做了CRC校验后依然正常处理。所以排查时一定要先排除物理链路。4.2 案例二服务器防火墙拦截了NFS流量另一次是开发板能ping通服务器tftp也能正常下载但nfs一直在loading T。抓包看到U-Boot发出的portmap请求包已经到达服务器网卡但服务器没有任何回复。查了一圈发现服务器的firewalld默认drop了来自开发板IP的所有流量。奇怪的是我们前面已经把nfs、mountd、rpc-bind服务都加进来了但RPC服务还在用一些高位动态端口。firewalld的富规则策略里有一句REJECT all排在NFS规则之前这样所有流量到了最后都被丢弃。最后在合适的位置插入了白名单规则或者干脆把网卡划分到trusted zone问题解决。教训云服务器或桌面Linux默认防火墙策略对于嵌入式开发这种便携环境太不友好。开发调试时建议把开发板所用的独立网卡设为受信区或者直接关闭防火墙等部署时再按生产环境加固。4.3 案例三bootargs里路径和实际导出目录对不上U-Boot执行的是nfs 0x20000000 192.168.1.10:/home/user/nfs_root/uImage服务器端exports里导出的是/home/user/nfs_root/ *(rw,sync,no_root_squash)看起来路径没问题但U-Boot提了几次loading T之后又恢复了。抓包抓不到问题重启了服务也没变化。后来登录服务器发现/home/user是一个加密的家目录用户没有登录时home目录是加密挂载状态/home/user/nfs_root实际上不存在。mount请求过去服务器端直接返回错误。这个案例提醒我们不要想当然地认为路径存在。所有NFS导出的目录建议放在一个固定的、非加密的目录下比如/srv/nfs避免后续磁盘挂载、用户目录等行为导致路径中断。4.4 案例四内核没有开NFS rootfs支持这个案例是U-Boot阶段看起来顺利——内核下载成功开始执行但执行到一半开始反复重启或卡死。U-Boot里没有“loading T”但始终进不了根文件系统。观察内核打印发现有类似[ 10.511830] VFS: Unable to mount root fs via NFS. [ 10.516375] List of all partitions:实际上是因为内核配置中的CONFIG_ROOT_NFS没开。常规的嵌入式内核为了缩小体积很多文件系统驱动都做成模块而模块本身又放在NFS文件系统里形成了鸡生蛋的问题。解决方法是重新配置内核开启CONFIG_NFS_FSy CONFIG_ROOT_NFSy CONFIG_NFS_V3y再把相关的网络选项都编入内核而非模块重新编译烧写。4.5 案例五U-Boot版本与NFSv4不兼容这个比较隐蔽。开发板烧了新版的U-Boot之后原来好用的NFS突然开始loading T。我们抓包发现U-Boot发送的portmap请求请求的是NFSv3或NFSv2但服务器配置的是NFSv4-only。新版U-Boot有一个特性默认nfs版本是3但某些板子厂商修改了默认值。服务器的NFS协议可以通过cat /proc/fs/nfsd/versions查看当前的版本支持。如果是NFSv4-only把它改为同时支持v3和v4echo -n 3 /proc/fs/nfsd/versions # 可能需要root权限细微差异在各发行版或者直接改配置文件让服务端重新加载时同时启用v3和v4。步骤如下sudo sed -i s/#RPCNFSDOPTS/RPCNFSDOPTS--nfs-versions 3,4/ /etc/default/nfs-kernel-server sudo systemctl restart nfs-kernel-server改完之后在Linux主机上用mount -t nfs4测试是没用的要用nfs3协议去挂载或直接在U-Boot侧重新nfs。我处理这种问题时一般先在服务器端用mount -t nfs -o vers3挂载一次自己导出的目录能成功就说明server端支持v3。5. 日常开发中规避loading T的一些经验5.1 环境配置检查清单每次搭建新的开发环境时建议按下面这个清单过一遍能省掉很多排查时间服务器固定IP开发板固定IP两者在同一网段。服务器和开发板用同一根网线直连不要过WiFi或有线混合网络。exports导出目录放在固定路径不要软链、不要跨文件系统。exports选项统一使用rw,sync,no_root_squash,no_subtree_check。确认防火墙对U-Boot IP不做任何过滤。内核配置中开启NFS相关支持。开发板连接方式优先用有线无线不做调试用。5.2 换一种启动方式可能更省心NFS确实方便修改文件系统代码后直接刷新不用反复烧SD卡。但对于新手来说它调优的过程比较折磨人尤其是需要反复验证内核的时候。我的建议是内核镜像用tftp加载文件系统用NFS挂载或者把稳定版本的内核固化到本地存储根文件系统用NFS挂载。这样不会因为网络链路问题导致整机无法启动排查范围也更清晰。如果团队多人共用一台NFS服务器建议每个开发人员建独立的导出目录并在exports里限制可访问IP段避免A同学搞乱了环境影响B同学。另外还有一个小细节U-Boot里的bootdelay如果设置成0启动时不会停到命令行直接执行bootcmd。如果bootcmd里的nfs地址或者路径错了你就无法交互式修改启动一直失败看起来也像一直loading T。建议开发阶段把bootdelay设置成3秒以上留出打断的窗口。5.3 多备份多记录NFS的问题看似诡异归根结底不过那几类网络不通、服务没起、版本不兼容、路径权限不对、内核选项不全。把这些根因和对应的检查点写在笔记里下次排查直接按表格逐行过效率会高非常多。我自己平时调试会做一个模板化的检查脚本一键输出服务器端的NFS状态、端口监听、导出列表、防火墙规则三板斧下来基本能定位80%的问题。剩下20%抓包解决。6. 写在最后的实操体会这些NFS问题本身不难难的是当屏幕上一个孤零零的T反复出现时容易让人急躁。我踩过最大的坑就是过分相信经验。某个项目换了新内核之后一直loading因为以前遇到过类似情况我一直在调NFS版本、调exports折腾了整整一天都没解决。后来冷静下来抓包才发现是服务器上装了一个虚拟网卡服务占了服务器IPU-Boot发出去的包被虚拟网卡收到了但没有正确处理。NFS本身是好的问题在更下层。所以我的经验是无论问题多么像NFS配置问题排查时一定要从最底层开始一层一层往上。物理链路、IP连通性、ARP表、NFS服务、exports配置、内核选项这个顺序不要跳。不要凭感觉跳过某一步因为感觉往往是错的。写这篇文章的时候我又复盘了一遍自己遇到的各种loading T场景。如果你现在正盯着开发板的串口屏幕准备第n次重启开发板建议先别急着重启回服务器上抓个包。那个T字背后藏着的答案比你想的要清晰得多。