Ubuntu 22.04内核编译启用NFSv2,解决U-Boot网络启动兼容性问题
2026/7/30 6:46:04 网站建设 项目流程

1. 项目概述与问题背景

最近在搞一个嵌入式Linux项目,开发板是常见的ARM Cortex-A系列,用U-Boot来引导。我的开发环境是Ubuntu 22.04 LTS,为了方便调试,一直习惯在主机上开一个NFS服务器,把编译好的内核镜像、设备树和根文件系统放上去,然后让U-Boot通过nfs命令直接下载到开发板的内存里启动。这套流程在之前的Ubuntu 20.04上跑得飞起,但自从我把开发机升级到Ubuntu 22.04之后,就彻底歇菜了。U-Boot总是卡在nfs命令那里,报一些“NFS: server is not responding”或者“Bad file descriptor”之类的错误,折腾了好几天。

问题的根源,其实就藏在Ubuntu 22.04默认使用的Linux内核版本里。从Ubuntu 21.10(Impish Indri)开始,其搭载的Linux内核(5.13及以上)在NFS协议支持上做了一个重要的默认调整:禁用了NFS版本2(NFSv2)。而很多老版本、或者为了追求精简而定制过的U-Boot,其内置的NFS客户端实现,往往只支持NFSv2协议。这就导致了“服务器说我只用v3/v4聊天,客户端却只会说v2方言”的尴尬局面,通信自然无法建立。所以,要解决这个问题,核心操作就是为Ubuntu 22.04的Linux内核重新启用NFSv2服务支持。这通常需要我们切换到自定义编译的内核,或者修改现有内核的配置并重新编译相关模块。

2. 核心需求与方案选型解析

2.1 为什么是NFSv2,而不是让U-Boot升级?

首先得明白,为什么我们非得去折腾主机内核,而不是去升级U-Boot的NFS客户端。对于嵌入式开发而言,U-Boot的版本和配置通常是和具体的硬件平台、芯片原厂提供的BSP包深度绑定的。随意升级U-Boot版本,可能会引入新的驱动兼容性问题、启动流程变化,风险很高。尤其是项目进入中后期,稳定是第一位的。另一方面,为U-Boot添加NFSv3/v4支持并非简单地打开一个配置开关,它涉及到网络传输、RPC调用等多个模块的改动,对于开发者来说,移植和调试的成本远高于在成熟的桌面Linux系统上调整一个内核参数。

因此,在主机端解决问题是成本最低、最可控的方案。Ubuntu 22.04作为开发主机,我们拥有完全的控制权,调整内核配置或切换内核版本相对安全,即使失败也能轻松回退。这个思路也符合嵌入式开发中“尽量在资源丰富、调试方便的主机端解决问题”的原则。

2.2 方案对比:内核降级 vs. 编译新内核模块

面对这个问题,通常有两个主流思路:

  1. 内核降级:将Ubuntu 22.04的内核回退到早期版本(例如5.11或更早),这些版本默认启用了NFSv2。这个方法看似简单,但存在隐患。新版本内核包含了许多安全补丁、硬件驱动更新(特别是对新显卡、新无线网卡的支持)。降级内核可能导致系统不稳定、硬件无法识别,甚至引发安全漏洞。对于作为主力开发机的电脑来说,这不是一个好选择。

  2. 重新编译内核,启用NFSv2:这是更专业、更推荐的方案。我们不需要替换整个内核,而是获取当前系统正在运行的内核源码,修改其配置,专门为NFSv2添加支持,然后只编译安装NFS相关的内核模块。这样做的好处是:

    • 最小化影响:只改动NFS相关的模块,系统核心和其他驱动保持不变,最大程度保证系统稳定性。
    • 保持更新:可以基于当前最新的官方内核源码进行修改,继续享受安全更新。
    • 学习价值:这个过程本身是理解Linux内核配置、编译和模块管理的绝佳实践。

基于以上分析,我选择了第二种方案:为现有Ubuntu 22.04系统,重新编译启用NFSv2支持的内核模块。接下来,我将详细拆解整个操作过程。

3. 环境准备与内核源码获取

3.1 确认当前内核版本与安装依赖

操作的第一步,是弄清楚我们当前在用什么内核,并安装编译所需的所有工具。

打开终端,输入以下命令查看内核版本:

uname -r

例如,输出可能是5.15.0-91-generic。记下这个版本号,我们后续需要获取与之完全匹配的源码包。

接下来,安装编译内核必需的开发工具和库。这是一条比较全面的命令,会安装编译器、头文件、库文件、配置文件工具等。

sudo apt update sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev bc dwarves zstd

注意dwarves包在较新内核编译中是必须的,它包含了pahole工具,用于处理BTF(BPF Type Format)信息,缺少它会导致编译失败。

3.2 获取匹配的内核源码包

Ubuntu提供了非常方便的方式获取与当前运行内核完全一致的源码。我们使用apt命令来获取源码包。

首先,启用源码仓库并安装linux-source包。注意,包名中的版本号需要替换成你uname -r输出结果的主版本号。例如,5.15.0-91-generic的主版本是5.15.0-91,但对应的源码包通常是linux-source-5.15.0

# 启用源码源 sudo sed -i '/^#\sdeb-src/s/^#//' /etc/apt/sources.list sudo apt update # 搜索可用的源码包,找到最匹配的那个 apt search linux-source-5.15 # 安装源码包 (以 5.15.0 为例) sudo apt install linux-source-5.15.0

安装完成后,源码压缩包通常位于/usr/src/目录下,例如linux-source-5.15.0.tar.bz2

将其解压到工作目录:

cd ~ mkdir kernel_build cd kernel_build tar -xaf /usr/src/linux-source-*.tar.bz2

解压后会生成一个类似linux-source-5.15.0的目录,进入它。

3.3 应用当前内核配置并准备编译环境

我们需要基于当前运行内核的配置来开始修改,这样可以确保新编译的模块与现有内核最大兼容。

将当前系统的内核配置复制到源码目录:

cp /boot/config-$(uname -r) .config

接下来,运行make命令来准备编译环境,并更新配置。这里使用olddefconfig目标,它会在保留我们已有配置(.config)的基础上,用新内核源码的默认值来填充新增的配置项,并自动解决依赖关系,非常省心。

make olddefconfig

现在,我们就有了一个与当前运行内核配置基本一致的编译起点。

4. 内核配置修改:启用NFSv2支持

4.1 使用菜单界面修改配置

核心步骤来了,我们需要修改内核配置,启用NFSv2的服务器端支持。使用图形化的menuconfig界面是最直观的方式。

make menuconfig

终端会弹出一个基于ncurses的文本图形界面。

  1. 使用方向键导航,找到File systems菜单,按回车进入。
  2. 向下找到Network File Systems子菜单,回车进入。
  3. 在这里,你会看到一系列NFS相关的选项。我们需要关注的是:
    • NFS server support:确保这个选项是开启的(前面有[*]<*>)。如果没有,按Y键选中它。
    • NFS server support被选中后,其下的子选项才会出现。找到Provide NFSv2 server support。这个选项默认在Ubuntu 22.04的内核配置中是关闭的(前面是[ ]< >)。
  4. 将光标移动到Provide NFSv2 server support上,按Y键将其选中。选中后,括号内会变成一个星号[*]
  5. 为了确保NFS服务能正常工作,建议也检查一下Provide NFSv3 server supportProvide NFSv4 server support是否已启用(通常默认是开启的)。保持它们开启有利于其他客户端连接。
  6. 配置完成后,使用右方向键将焦点切换到< Save >,回车,默认保存到.config文件,再回车确认。然后选择< Exit >退出,多退几次直到退出menuconfig

4.2 验证配置修改结果

退出menuconfig后,我们可以快速验证一下修改是否生效。

grep CONFIG_NFSD_V2 .config

如果输出是CONFIG_NFSD_V2=y,那么恭喜你,NFSv2服务器支持已经成功启用了。如果输出是# CONFIG_NFSD_V2 is not set,则说明没有生效,需要重新进入menuconfig检查。

5. 选择性编译与模块安装

5.1 编译NFS相关内核模块

我们不需要重新编译整个内核,那样太耗时(可能长达数小时)。只需要编译我们修改过的、与NFS服务器相关的模块即可。make命令可以指定编译目标。

首先,编译nfsd.ko等核心NFS服务器模块:

make -j$(nproc) modules_prepare # 准备模块构建环境 make -j$(nproc) M=fs/nfsd # 编译 nfsd 目录下的模块

-j$(nproc)参数表示使用与CPU核心数相同的线程进行并行编译,可以极大加快速度。M=fs/nfsd指定只编译fs/nfsd这个子目录下的模块。

编译过程可能需要几分钟,取决于你的CPU性能。如果顺利,会在fs/nfsd目录下生成一系列.ko文件,其中最重要的就是nfsd.ko

5.2 备份并安装新模块

在安装新模块之前,强烈建议备份旧模块,这是系统维护的好习惯。

  1. 备份旧模块

    sudo cp -r /lib/modules/$(uname -r) /lib/modules/$(uname -r).backup
  2. 安装新编译的模块

    sudo make M=fs/nfsd modules_install

    这个命令会将新编译的.ko文件安装到/lib/modules/$(uname -r)/kernel/fs/nfsd/目录下,覆盖原有的模块。

  3. 更新模块依赖关系: 安装新模块后,需要更新modules.dep文件,以便modprobe等工具能正确解析模块依赖。

    sudo depmod -a

5.3 重启NFS服务或重启系统

新的内核模块已经就位,但正在运行的nfs-server服务可能还在使用旧的内存中的模块。我们需要重启它。

  1. 重启nfs-server服务

    sudo systemctl restart nfs-server

    或者,如果你的系统使用的是nfs-kernel-server这个服务名:

    sudo systemctl restart nfs-kernel-server
  2. 验证NFS服务状态与版本

    sudo systemctl status nfs-server sudo cat /proc/fs/nfsd/versions

    查看状态确保服务运行正常。查看/proc/fs/nfsd/versions文件,输出中应该包含+2 -3 +4 +4.1 +4.2之类的信息,其中的+2就表示NFSv2已启用。这是最关键的成功标志。

如果重启服务后NFSv2仍未显示启用,或者遇到其他问题,最彻底的方法是重启整个系统,让内核在启动时加载全新的、我们编译的模块。

sudo reboot

重启后,再次检查/proc/fs/nfsd/versions确认+2存在。

6. 配置与测试NFS共享

6.1 配置NFS共享目录

启用内核支持只是第一步,我们还需要正确配置NFS服务器,指定共享哪个目录给开发板。编辑NFS的配置文件/etc/exports

sudo nano /etc/exports

在文件末尾添加一行配置,格式如下:

/home/your_username/embedded_rootfs *(rw,sync,no_subtree_check,no_root_squash)
  • /home/your_username/embedded_rootfs:这是你要共享的本地目录路径,请替换为你的实际路径。这个目录里应该存放着你的内核镜像(如zImage)、设备树文件(.dtb)和根文件系统。
  • *:表示允许所有IP地址的客户端访问。在生产环境或开放网络中,这是极其危险的,务必替换为具体的开发板IP或网段,例如192.168.1.0/24
  • rw:读写权限。
  • sync:同步写入,数据更安全。
  • no_subtree_check:禁用子树检查,可以提高性能,在单一目录共享时更安全。
  • no_root_squash重要!这个选项允许客户端的root用户保持root权限访问共享目录。对于U-Boot下载文件到内存这个操作是必须的,否则可能会因权限不足而失败。

保存并退出编辑器后,让NFS服务器重新加载配置:

sudo exportfs -ra

6.2 在U-Boot中进行NFS下载测试

现在,一切准备就绪。给开发板上电,在U-Boot命令行下进行测试。

  1. 首先,确保开发板和Ubuntu主机在同一个局域网,并且能互相ping通。在U-Boot中设置好开发板的IP、服务器IP和网关。

    setenv ipaddr 192.168.1.100 # 开发板IP setenv serverip 192.168.1.50 # Ubuntu主机IP setenv gatewayip 192.168.1.1 saveenv
  2. 使用nfs命令尝试下载一个文件到内存。例如,下载内核镜像:

    nfs ${loadaddr} ${serverip}:/home/your_username/embedded_rootfs/zImage
    • ${loadaddr}:是开发板内存中的一个地址,例如0x82000000
    • ${serverip}:/path/to/file:NFS服务器的路径。

如果配置一切正确,你应该能看到类似下面的输出,表示文件正在传输,并且有速度显示:

Using ethernet@fe300000 device TFTP from server 192.168.1.50; our IP address is 192.168.1.100 Filename '/home/your_username/embedded_rootfs/zImage'. Load address: 0x82000000 Loading: ################################################## 2.3 MiB/s done Bytes transferred = 7832160 (778260 hex)

看到“done”和传输的字节数,就表示NFSv2协议通信成功,文件下载无误。之前困扰你的“server not responding”错误应该已经消失了。

7. 常见问题排查与深度优化

7.1 问题速查表

即使按照步骤操作,也可能遇到一些问题。这里列出几个常见的坑和解决办法:

问题现象可能原因排查步骤与解决方案
make menuconfig时找不到NFSD_V2选项1.NFS server support未启用。
2. 内核源码版本或配置有误。
1. 确保在menuconfig中,-> File systems -> Network File Systems下,[*] NFS server support已被选中。
2. 选中后,再进入其子选项查找。
编译模块时出现大量错误1. 依赖包未安装全(如缺少dwarves)。
2. 内核源码与运行内核版本不匹配。
3..config文件配置冲突。
1. 确认已安装build-essential libncurses-dev bison flex libssl-dev libelf-dev bc dwarves
2. 用 `apt install linux-source-$(uname -r
/proc/fs/nfsd/versions中没有+21. 新模块未正确加载。
2. NFS服务未使用新模块重启。
1. 运行sudo modprobe -r nfsd然后sudo modprobe nfsd手动重新加载模块。
2.最有效的方法:重启系统
3. 检查 `lsmod
U-Boot能ping通主机,但nfs命令超时1. 主机防火墙阻止了NFS端口(2049)。
2./etc/exports配置错误或未生效。
3. 共享目录权限问题。
1. 临时关闭防火墙测试:sudo ufw disable(Ubuntu) 或sudo systemctl stop firewalld(其他发行版)。测试后记得重新配置规则并开启
2. 运行sudo exportfs -v查看当前生效的共享规则。
3. 确保共享目录对“其他人”有读权限:chmod o+r /path/to/shared
U-Boot nfs命令报错“Bad file descriptor”1. 文件路径在U-Boot中格式错误。
2. NFS服务器端文件不存在。
3.仍可能是NFSv2未启用
1. U-Boot的NFS路径是服务器端的绝对路径,且不应包含主机名后的冒号(:)。正确格式:${serverip}:/absolute/path
2. 在主机上确认文件是否存在且路径正确。
3. 再次确认主机已启用NFSv2。

7.2 防火墙与安全配置要点

在开发环境中,为了方便我们常常会直接关闭防火墙,但这并不安全。更好的做法是针对性开放NFS所需的端口。NFS服务依赖于rpcbind和几个动态端口。一个相对简单的配置是固定NFS服务的端口并放行。

  1. 固定NFS端口(可选但推荐):编辑/etc/default/nfs-kernel-server,添加或修改:

    RPCMOUNTDOPTS="--manage-gids --port 32767" STATDOPTS="--port 32765 --outgoing-port 32766"

    编辑/etc/sysctl.conf,添加:

    fs.nfs.nfs_callback_tcpport = 32764

    执行sudo sysctl -p生效。然后重启NFS服务。

  2. 配置UFW防火墙规则

    sudo ufw allow from 192.168.1.0/24 to any port 111 # rpcbind sudo ufw allow from 192.168.1.0/24 to any port 2049 # nfs sudo ufw allow from 192.168.1.0/24 to any port 32764:32767/tcp # 上一步固定的端口 sudo ufw allow from 192.168.1.0/24 to any port 32764:32767/udp

    192.168.1.0/24替换为你的实际开发网段。

7.3 内核模块编译的进阶技巧

如果你经常需要折腾内核模块,可以了解一下dkms(Dynamic Kernel Module Support)。它可以帮你管理第三方内核模块,在系统内核自动升级后,自动重新编译模块。但对于我们这种修改官方内核自带模块的情况,手动管理更直接。不过,你可以考虑将修改后的内核配置片段保存下来。

  1. 保存配置差异:在修改配置后,可以使用命令生成一个只包含你修改部分的配置文件。

    make listnewconfig | grep -E “^CONFIG_” > my_nfsv2_config_fragment

    但这对于menuconfig的修改不太精确。更稳妥的方法是,在修改前备份.config.old,修改后用diff工具对比。

    cp .config .config.new # ... 进行 menuconfig 修改 ... diff -u .config.old .config > enable_nfsv2.patch

    这样得到的enable_nfsv2.patch文件,在未来获取新内核源码后,可以尝试用patch命令打上,快速重现配置。

  2. 只编译单个模块:如果你确定只动了nfsd,其实可以更精确地只编译它,但依赖模块可能会被遗漏。make M=fs/nfsd是最稳妥的模块级编译指令。如果想挑战一下,可以尝试make fs/nfsd/nfsd.ko,但需要自行解决依赖,不推荐新手。

整个流程走下来,从发现问题、分析根因,到动手修改内核配置、编译模块,最后成功让U-Boot通过NFSv2下载文件,不仅解决了眼前的问题,更深入理解了Linux内核模块的运作机制和NFS服务的配置细节。这种“问题驱动”的学习方式,往往比单纯看文档收获大得多。下次再遇到类似的内核功能需求,你就知道该如何入手了。嵌入式开发就是这样,大部分时间都在解决环境和工具链的问题,而这些问题恰恰是能力成长的阶梯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询