首页 / 资讯中心 / 文章详情

RISC-V开发板自制轻量NAS:从引导到应用全流程实践

RISC-V开发板自制轻量NAS:从引导到应用全流程实践 ★ FEATURED ARTICLE
手里这块双核RISC-V开发板已经吃灰半年了1GHz的应用级处理器、1GB内存、板载千兆网口、一个SATA接口。当路由器算力过剩当通用开发板又不知道跑什么典型的“高不成低不就”。直到家里一台旧笔记本退役留下了整块硬盘的照片、工作文档和电影资源才意识到自己真正缺的是一个NAS。盯了两天成品NAS的价格再看了看这块板子的功耗决定自己动手——从板级引导开始一步步把它做成一个基于RISC-V的现代嵌入式轻量化NAS系统。这篇文章记录的是完整的实现过程从ROM启动、OpenSBI、U-Boot这套板级引导链路到内核裁剪、根文件系统构建再到Samba、NFS这些现代网络栈服务最后是性能调优和踩坑实录。适合手里有RISC-V开发板还不知道能干点啥的人也适合想搭轻量NAS但不想上x86大机箱的朋友。全文不讲虚的所有步骤都是实际验证过的方案。1. 项目概述与整体设计1.1 为什么选RISC-V做NAS先说个实话RISC-V做NAS目前不是最省事的方案x86或者ARM平台资料多、生态成熟随便一个树莓派都能跑成品NAS系统。但RISC-V有它不可替代的价值这也是我坚持用它的原因。RISC-V的指令集是开放的没有授权壁垒这意味着从SoC设计到编译器、操作系统整个工具链都是透明的。对于一个想搞懂“系统是怎么跑起来”的人来说x86那套固件链复杂到让人头皮发麻ARM的Trusted Firmware也不简单。RISC-V的引导链条非常清晰ROM加载固化固件OpenSBI运行在M模式提供运行时服务U-Boot跑在S模式加载内核内核跑在U模式。每一层干什么都非常明确没有历史包袱也没有各种兼容性黑魔法。功耗和成本上RISC-V应用级处理器也有明显优势。我这块板子满载也就7瓦左右待机4瓦出头挂一块3.5寸机械硬盘整机功耗稳定在10瓦内。如果换成x86平台光是CPU和芯片组的待机功耗就远超这个数。对于7x24小时开机的NAS应用场景一年下来的电费差距非常可观。生态方面2024年到2025年RISC-V的Linux主线支持已经相当成熟了OpenSBI、U-Boot、Buildroot、Yocto这些基础组件都有完善的支持。我用的还是长期支持版内核SATA、USB3.0、千兆网卡这些外设驱动都能直接编译进去不需要一堆out-of-tree补丁。说句公道话两年前的RISC-V开发板还真不适合做这种活现在时机到了。1.2 系统架构与核心选型原则整个系统的软件架构可以分成四层从上到下分别是应用层Samba、NFS、rsync、SSH、smartd监控系统层Buildroot构建的根文件系统glibc运行库内核层Linux 6.x LTS裁剪掉用不到的驱动和子系统固件层OpenSBI U-Boot负责硬件初始化和引导内核这个分层方式跟x86服务器的引导流程本质是一回事但RISC-V的每个环节都更精简。选型的时候我给自己定了三个原则能编译进内核就不做模块。嵌入式设备追求稳定模块加载机制在调试阶段有用但运行阶段反而增加故障点。我直接把SATA、网络、文件系统全部编进内核不要initramfs那套东西启动更快更可靠。能用一个服务解决就不装两个。局域网共享主要用SMB协议NFS只作为备选方案部署。像DLNA这种媒体服务我干脆不装一个SMB共享搞定所有设备访问减少内存占用。一切以低功耗高可靠优先。不做复杂的软RAID5不用ZFS这种吃内存的文件系统数据安全靠基础的RAID1加定期备份实现。完整组件选型对比可以参考这个表组件选型备选方案选择理由引导固件OpenSBI U-Boot只有U-Boot的SPL方案标准S模式引导便于调试内核Linux 6.x LTS最新主线版LTS稳定驱动支持够用根文件系统Buildroot glibcmusl libcglibc兼容性好Samba等软件行为更稳数据文件系统ext4btrfs、f2fs老牌可靠CPU占用低网络共享SambaNFS、FTPWindows/Linux/macOS/手机全兼容定时任务crondBusyBox自带systemd timersystemd在Buildroot里太臃肿这套架构跑起来的最终效果是一台功能完整的轻量化NAS内存占用控制在300MB以内CPU平时负载几乎为0只有传输文件的时候才会短时间拉高。2. 板级引导与系统移植2.1 RISC-V引导链路M模式、S模式与U模式很多第一次接触RISC-V的人都会被引导流程绕晕这里我用最直白的方式拆解。RISC-V定义了三种特权模式机器模式M mode、监督模式S mode、用户模式U mode。类比一下M模式相当于CPU的“上帝模式”能操作所有寄存器S模式相当于现代操作系统的内核模式U模式就是普通应用程序跑的受限模式。开发板上电后固化在ROM里的代码BootROM先执行它把SPI Flash或者SD卡里的固件加载到内存然后跳转执行。这一步里OpenSBI承担了M模式固件的角色它负责初始化时钟、内存控制器、串口这些基础硬件然后降级到S模式把控制权交给U-Boot。U-Boot在S模式运行负责更复杂的初始化网卡、存储控制器加载设备树和内核镜像最终把控制权交给Linux内核内核跑在S模式用户进程跑在U模式。这条链路里最关键的一个设计点在于运行时服务。内核在S模式运行一旦需要操作M模式才允许访问的资源比如定时器、IPI中断就得通过ECALL指令陷入OpenSBI由OpenSBI代为执行。所以OpenSBI不仅仅是“引导一下就算完事”的固件它在整个系统运行期间都要常驻内存。这意味着OpenSBI在编译时的配置会直接影响内核能否正常启动。2.2 U-Boot编译与设备树适配U-Boot的编译过程本身不复杂但有些细节直接决定成败。首先是交叉编译工具链。我用的riscv64-linux-gnu-工具链版本12.2以上太老的版本在编译较新的U-Boot时会报隐式函数声明的错误。编译命令如下export CROSS_COMPILEriscv64-linux-gnu- make ARCHriscv 开发板_defconfig make ARCHriscv -j$(nproc)U-Boot的defconfig是按具体开发板命名的选择对应配置后编译产物里会有u-boot.bin和u-boot.itb。RISC-V平台这里推荐使用FIT image.itb格式因为FIT可以将OpenSBI、U-Boot本身和设备树打包成一个镜像烧写和管理都更方便。U-Boot的配置里有一项CONFIG_FIT会自动依赖OpenSBI的二进制文件第一次编译时就需要提前把opensbi.bin放到指定目录。设备树DTB是整个引导链中承上启下的环节。U-Boot通过设备树了解硬件资源内核也需要设备树才能匹配驱动。实际工作中最常遇到的问题是设备树里的网卡PHY地址不对或者SATA控制器节点被遗漏导致内核启动后找不到设备。排查方法很朴素用命令对比/proc/device-tree下的节点内容跟原理图逐一核对ls /proc/device-tree/ cat /proc/device-tree/model我在适配过程中就遇到过U-Boot里网卡能识别、但内核启动后连不上网的问题最后定位到是设备树里PHY的reset GPIO没有正确配置。改一行设备树源码重新编译问题就解决了。这个经验是常规文档里不会有的但实际项目里非常常见。2.3 内核裁剪与启动参数内核裁剪是嵌入式系统里最体现功夫的一步。裁剪的目的是降低内存占用、加快启动速度同时不影响功能。我以最小化起步按需添加驱动最终内核镜像压缩后不到6MB启动时间从上电到SSH可用大概9秒。具体操作是用menuconfig图形界面逐项检查make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- menuconfig以下几个配置组是必须保证的核心CPU功能SMP多核、GENERIC_TIMER、RISCV_SBI存储控制器SATA_AHCI、AHCI_PLATFORM以及对应的PCIe/GPIO控制器驱动网络NET_CORE、ETHERNET千兆网卡驱动具体驱动取决于SoC集成方案文件系统EXT4_FS、VFAT兼容性考虑、PROC_FS、SYSFS内核网络服务IPV4、IPV6可选Samba不需要IPV6但我开了备用、PACKET方便之后跑tcpdump内核启动参数方面我使用了非常精简的配置consolettyS0,115200 root/dev/sda2 rw noinitrd这里root/dev/sda2指向根文件系统所在分区。如果系统盘和数据盘使用的都是SATA接口设备名可能会因为枚举顺序变化而漂移稳妥的做法是在内核启动参数里用rootPARTUUIDxxxx这种方式替代设备名。我最初没注意这个问题有一次开机后系统起了但挂载不到根分区排查半天发现是SATA枚举顺序变了sda变成sdb了。用PARTUUID后彻底解决。3. 存储层设计与根文件系统构建3.1 存储设备规划系统与数据分离NAS的本质是存储存储架构怎么设计直接决定后续的稳定性和可维护性。我把存储分成了两层系统层和数据层。系统层用一张16GB的SD卡只放boot分区和根文件系统。数据层用一块1TB的机械硬盘通过SATA接口接入独立分区存放所有共享数据。这样设计的好处是系统崩溃、SD卡损坏或者系统需要重刷数据盘完全不受影响拔下来插到任何Linux机器上都能读取。分区规划如下SD卡/dev/mmcblk0 mmcblk0p1 64MB FAT32 /boot 存放U-Boot镜像、设备树、内核 mmcblk0p2 15GB ext4 / 根文件系统 SATA硬盘/dev/sda sda1 500MB ext4 预留RAID元数据 sda2 剩余 ext4 /data 数据主分区数据盘我特意划分了一个500MB的小分区这个分区不挂载专门用于存放mdadm的RAID元数据。如果后面要加第二块盘组RAID1元数据和数据分离可以降低降级恢复的复杂度。这个思路是从服务器存储方案里借鉴过来的在嵌入式环境虽然有点“杀鸡用牛刀”但成本几乎为零收益却很实在。3.2 文件系统选型与挂载参数文件系统选型我主要对比了ext4、btrfs和f2fs三个方案。f2fs是为NAND闪存设计的SD卡上用很合适但机械硬盘上表现一般btrfs功能强快照、校验、压缩但对CPU和内存的消耗明显高于ext4在1GB内存的RISC-V平台上有点勉强。最终选择ext4理由是它在机械硬盘上的顺序读写性能最好CPU占用最低而且内核支持最成熟。挂载参数直接影响运行性能和磁盘寿命这里给出的配置经过实测/dev/sda2 /data ext4 defaults,noatime,nodiratime,datawriteback 0 2三个关键参数的说明noatime/nodiratime关闭访问时间更新减少每次读文件的写操作。NAS场景下大量读操作来自媒体播放关掉atime能显著减少不必要的磁盘写入。datawritebackext4的写模式之一不会像ordered模式那样强制日志先落盘换取更高的写入性能。代价是系统突然断电时可能出现文件数据损坏但文件系统结构完好的情况。考虑到嵌入式NAS的UPS保护场景这个取舍可以接受。如果对数据安全特别敏感建议保留默认的ordered模式。commit60日志提交间隔改成60秒减少高频小写入也延长机械硬盘寿命。这个参数尤其适合家庭NAS的备份和媒体存储场景。SD卡上的根文件系统挂载参数略有不同我额外加了discard参数配合闪存介质的TRIM命令可以减缓SD卡磨损。不过ext4的discard在部分SD卡控制器上会引起卡顿如果遇到这个问题可以改成nodiscard并定期手动执行fstrim。3.3 Buildroot构建根文件系统根文件系统我直接用Buildroot生成。为什么不手动搭因为手动构建glibc和几百个软件包的依赖关系是一个无底洞Buildroot一次性解决编译、依赖、安装、交叉工具链集成的问题生成的就是一个可直接烧写的SD卡镜像。构建步骤并不复杂make buildroot_defconfig make menuconfigmenuconfig里需要调整的关键选项Target options → Target Architecture选择RISC-V 64Toolchain → C library选择glibc不用musl。我实测下来musl编译Samba时会有兼容性坑某些版本编译能过但运行时报段错误glibc虽然体积大一些但行为更接近桌面/服务器环境调试成本低。System configuration → root password设置初始密码同时开启root自动登录串口方便调试。Target packages勾选samba4、nfs-utils、mdadm、rsync、dropbearSSH、ttcp或iperf3性能测试用、smartmontools硬盘健康监控。构建时间取决于机器性能四核i5大概需要30到40分钟。产出物在output/images/目录下其中sdcard.img就是可以直接dd进SD卡的完整系统镜像。dd ifoutput/images/sdcard.img of/dev/mmcblk0 bs4M statusprogressBuildroot生成的系统默认不包含包管理器这是有意为之嵌入式系统不需要在线安装软件所有需要的软件在构建阶段就锁定版本。好处是系统具备可复现性任何时刻出问题都可以用同一版本重新构建定位而不是像发行版那样升级软件包后行为漂移。3.4 数据保护轻量RAID与监控NAS上的数据保护是个绕不开的话题。我的方案是mdadm软件RAID1镜像模式两个同样的数据盘互为备份。有人会问为什么不用ZFS或者Btrfs RAID原因还是那个RISC-V平台的CPU算力有限ZFS的压缩和校验功能吃内存也吃CPU1GB内存跑起来会捉襟见肘。RAID1的机制最简单原理是逐字节镜像CPU开销几乎可以忽略。创建RAID的命令如下mdadm --create /dev/md0 --level1 --raid-devices2 /dev/sda2 /dev/sdb2在文件系统之上再做RAID而不是把RAID做成整块盘这样就可以把系统分区和RAID卷放在同一块物理盘上布局更灵活。实际部署时我顺手测了一下RAID1的写入性能机械硬盘单盘顺序写大概150MB/sRAID1后写入反而略有下降这是镜像写机制的固有问题但换来的是任意一块盘损坏数据都不丢失。RAID不是备份它解决的是硬件故障场景解决不了误删除或者勒索病毒。所以我还部署了smartd做硬盘健康监控并配置了一个每日运行的rsync任务把关键目录增量备份到另外一台机器上。smartctl --scan systemctl enable smartd这些措施叠加起来整套系统对所有常见的数据丢失场景都有对应的处理方案。4. 现代网络栈与NAS服务实现4.1 内核网络栈与网卡驱动确认NAS的核心服务是网络文件共享网络栈的性能决定了整个NAS的体验。启动内核后第一步就是确认网卡驱动正常工作并且链路的协商速率是千兆全双工。ethtool eth0 ip link set eth0 up ip addr add 192.168.1.100/24 dev eth0ethtool输出里Speed: 1000Mb/s和Duplex: Full是我希望看到的内容。如果协商速率掉到100Mb/s多数情况是网线问题或者对端交换机端口协商异常可以先换网线排除。现代网络栈的性能关键点在内核的NAPI机制和中断处理。之前的网络驱动接收数据包时采用纯中断模式高吞吐场景下CPU被中断风暴打满性能上不去。现在主流驱动都实现NAPINew API在中断触发后切换到轮询模式批量收包大幅减少中断次数。我在内核配置里确认了NET_NAPI和相关驱动的NAPI支持都编译进去了。在嵌入式平台DMA内存也需要关注。有些网卡驱动依赖内核预留的DMA缓冲区如果CMAContiguous Memory Allocator配置得太小高吞吐时会出现收包丢包。我遇到的典型现象是iperf3测速初段正常几秒后吞吐骤降统计接收丢包率飙升。后来在内核命令行里额外保留了SMP内存区域并增大了CMA容量问题才彻底解决。coherent_pool2M cma64M这个配置对于1GB内存的设备完全够用不影响系统可用内存。4.2 Samba服务搭建与调优在局域网共享场景里SMB协议是兼容性最好的方案Windows资源管理器直连、macOS访达直连、手机上的文件管理器也大多支持SMB。Samba的搭建在Buildroot阶段已经完成主要工作是编辑/etc/samba/smb.conf配置文件。这是我最终使用的核心配置[global] workgroup WORKGROUP server min protocol SMB3 server max protocol SMB3 encrypt passwords yes socket options TCP_NODELAY read raw yes write raw yes aio read size 1 aio write size 1 use sendfile yes strict allocate no [media] path /data/media browseable yes read only no valid users nas create mask 0664 directory mask 0775几个关键参数的取舍说明protocol设为SMB3旧版协议SMB1/CIFS有很多已知安全漏洞SMB3提供加密和更好的性能。代价是Windows XP这类老系统无法访问现在应该没有人在意这个了。socket options TCP_NODELAY禁用Nagle算法减少小包交互延迟。这个参数在局域网点播视频时感知非常明显拖动进度条后响应更快。aio read/write size设为1开启异步IOSamba对磁盘的读写不会阻塞网络响应多客户端并发访问时体验更好。在单核或双核CPU上这个参数尤其重要。use sendfile yes让内核直接负责数据搬运减少用户态和内核态之间的拷贝读吞吐量能提升10%到15%。strict allocate no写入大文件时不预分配磁盘空间省掉一次FSYNC写入性能更好但碎片率略高。对媒体文件这种大文件顺序写场景弊端可以忽略。Samba用户和系统用户的关联也需要单独处理。我用smbpasswd -a nas命令为nas用户设置了独立的SMB密码这个密码可以跟系统登录密码不同日常共享访问时不会暴露SSH登录凭据。4.3 NFS与Rsync按需选择局域网内如果是纯Linux设备互访NFSv4比SMB效率更高延迟更低协议开销更小。我的做法是Samba做主力共享NFS只对局域网内的Linux开发机开放特定目录。NFS配置极其简单编辑/etc/exports增加一行/data/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)注意我加了sync而不是async。NFS默认行为是写操作先落内存缓存async模式虽然性能好但掉电时数据可能不一致。在嵌入式环境多花一点性能换一致性更稳妥。rsync则是解决备份问题的我写了一个简单的cron任务0 3 * * * rsync -avz --delete /data/ nas-backup:/backup/每天凌晨3点增量备份到另一台机器--delete保证备份目录跟源目录严格同步不会残留已删除的旧文件。这套方案跑了一个多月数据一致性和可靠性都符合预期。4.4 网络与存储整体性能实测所有服务搭建完成后我做了一轮完整性能测试顺便检验整个系统的瓶颈在哪里。网络层用iperf3测TCP吞吐iperf3 -c 192.168.1.100 -t 60结果稳定在939Mbps基本跑满千兆网卡的线速。CPU占用大约40%到50%对双核处理器来说已经是很好的成绩。这里能看到RISC-V核心的计算能力在千兆网络处理上没有任何问题。文件传输层面通过SMB拷贝一个大文件dd if/dev/zero of/data/testfile bs1M count4096 # 先在目标盘生成4GB测试文件Windows端拷贝这个4GB文件实测写入速度105MB/s读速度112MB/s。考虑到底层是RAID1镜像写加上SMB协议开销这个数据在机械硬盘方案的NAS里算是正常偏上的水平。CPU和内存占用方面top -b -n 1传输过程中Samba进程CPU占用约30%内存常驻约180MB系统总内存占用稳定在280MB以下。整机功耗用功率计实测空闲5W传输峰值11W挂机一个月电费不到3块钱。5. 常见问题与性能优化实录5.1 引导启动阶段的问题这套系统前后折腾了三个周末引导启动阶段踩的坑最多也最有代表性。第一个问题是SD卡启动后串口完全没有输出。一开始怀疑是镜像烧坏了重新dd了一遍还是白屏。排查了很久最后发现是U-Boot源码编译时默认的串口配置和开发板实际使用的串口号不一致。RISC-V开发板的调试串口在设备树里通过stdout-path属性指定U-Boot也读取这个属性但如果U-Boot内置的CONFIG_CONS_INDEX没有对应修改就会出现串口无输出的现象。解决方法很简单在defconfig里显式指定正确的串口index重新编译U-Boot。第二个问题很经典内核启动到一半panic报Kernel panic - not syncing: VFS: Unable to mount root fs。这是根文件系统没挂载上。我刚踩完设备名漂移的坑又踩了第二个坑设备树里SATA控制器的reg地址在U-Boot阶段能识别但内核阶段PHY初始化超时导致硬盘没有正常上线。用rootwait这个内核参数可以解决启动时设备未就绪的问题让它等设备出现再挂载治标。治本是核对设备树里PHY的供电和复位时序配置我最终选择了治本。5.2 存储性能问题的定位Samba写入速度一开始只有40MB/s远低于预期机械硬盘再怎么慢也不至于这个水平。排查过程是这样的先用dd测试裸盘写入速度排除文件系统问题。dd if/dev/zero of/data/test bs1M count2048 convfdatasync裸盘写入有130MB/s说明问题不在存储层在Samba协议栈。看CPU占用发现Samba进程在传输时单核心几乎打满推断是加解密或者数据拷贝的开销过大。检查发现smb.conf里没有开启use sendfile而且SMB3加密默认开启。嵌入式CPU做加密计算的性能有限我最终在安全性要求不高的家庭局域网环境关闭了强制加密同时打开sendfile速度直接翻倍到接近110MB/s。注意关闭SMB加密需要自己评估风险。我这个NAS只允许在家庭路由器的内网网段访问并且通过防火墙限制外网访问才能这样设置。5.3 网络吞吐上不去的瓶颈iperf3测速只有300Mbps的时候问题的根源不在应用层而在DMA内存不足这个前面提到过。另一个容易被忽略的点是中断和CPU亲和性。默认情况下所有中断都落在CPU0上高吞吐时CPU0被打满CPU1空闲。解决方法是把网卡中断手动绑定到多个CPU核心上echo 2 /proc/irq/$(grep eth0 /proc/interrupts | awk {print $1} | tr -d :)/smp_affinityecho 2表示第二个CPU核心CPU1如果有四核就依次设置4和8。这个操作在RISC-V平台的嵌入式环境同样适用实测几百Mbps级吞吐时效果明显千兆线速下收益不大因为单核已经接近处理极限了。网络调优还有一个常被忽略的细节TCP接收窗口。嵌入式系统默认的rmem_max可能只有几十KB这在高延迟链路上会严重限制吞吐。局域网延迟足够低影响不大但如果是跨网段访问内网NAS就需要调大sysctl -w net.core.rmem_max41943045.4 稳定运行与日常维护系统搭建完成只是第一步长时间稳定运行才是NAS的核心价值。我整理了实际的维护经验和监控方法。散热方面RISC-V开发板本身发热不大但3.5寸机械硬盘长时间运行温度会到45℃以上。我给硬盘加了一个USB供电的小风扇温度稳定在38℃机械硬盘在35℃到40℃之间寿命最长。SD卡一定要选工业级的我之前用消费级SD卡连续写入跑了一周后文件系统损坏换工业级和高耐久卡后稳定运行至今。日常监控我用了一个很朴素的组合crontab每5分钟记录一次CPU温度和负载配合smartctl检查硬盘健康状态。*/5 * * * * echo $(date) $(cat /sys/class/thermal/thermal_zone0/temp) /var/log/temp.logSSH密钥登录是必须配置的密码登录在公网可达的环境里迟早被爆破。我在/etc/dropbear/authorized_keys里放了客户端公钥关闭密码登录同时在防火墙层面只允许内网IP访问SSH和SMB端口。日志方面Samba的日志级别默认是0日常运行很安静。排查问题时可以先提升到2再复现一次问题注意排查完要改回来否则日志会把SD卡空间吃光。最后分享一点个人感想。这个项目做完之后我对嵌入式系统“从零到一”的理解比看十本书都有用。很多东西不亲手走一遍你永远不会知道U-Boot的FIT image里封装的OpenSBI和普通bin文件有什么区别也不会理解为什么设备树里一个GPIO属性就能让整块网卡不工作。RISC-V平台的资料比x86少但正是因为它少逼着你去看源码、看文档、追根究底反而把整个链路学了个通透。现在这块板子安安静静躺在电视柜里每天凌晨做备份平时给全家提供视频和照片共享7瓦的功耗换来一整年的安心。后续我打算给它加一块SSD做缓存层如果大家感兴趣我到时候再写一篇缓存方案的详细记录。
阅读完成 · 觉得有帮助?
咨询建站