1. 从零开始为什么选择Buildroot来构建根文件系统如果你正在为一个嵌入式Linux项目折腾大概率已经听过Buildroot这个名字。它不像Yocto那样庞大复杂也不像手动交叉编译那样琐碎易错对于需要快速得到一个精简、可定制根文件系统的开发者来说Buildroot是一个“刚刚好”的选择。我最近在一个基于RK3568的项目上就用Buildroot从头搭建了一套系统核心需求很简单让开发板能通过SSH远程登录并且能用SFTP传文件。听起来是基础功能但实际操作中从配置、编译到功能调通每一步都可能遇到意想不到的坑。这篇文章我就把整个构建过程特别是开启SSH和SFTP时遇到的问题及解决办法从头到尾捋一遍希望能帮你省下几个小时甚至几天的调试时间。Buildroot本质上是一个集成的构建框架它通过Kconfig和Linux内核配置界面类似来管理配置然后自动化地下载源码、打补丁、配置、交叉编译最终打包生成包括内核、根文件系统、引导程序在内的完整镜像。它的优势在于“一体化”和“可重复性”。你不需要手动去处理glibc、busybox、openssh这些包之间复杂的依赖关系也不需要记住上次编译某个库时加了什么奇葩的CFLAGS。一次配置多次编译结果一致。这对于需要维护多个产品线或不同硬件版本的项目来说价值巨大。我们这次的目标就是利用Buildroot制作一个包含完整SSH服务支持SFTP的根文件系统。2. 环境搭建与基础配置为RK3568定制我们的系统工欲善其事必先利其器。在开始之前我们需要准备好编译环境和基础配置。我的宿主机是一台Ubuntu 22.04 LTS的机器这是Buildroot官方推荐的环境能避免很多因库版本不兼容导致的问题。2.1 获取Buildroot源码与基础依赖安装首先从Buildroot官网下载最新的长期支持版本LTS。稳定比新潮更重要尤其是在嵌入式领域。wget https://buildroot.org/downloads/buildroot-2024.02.tar.xz tar xf buildroot-2024.02.tar.xz cd buildroot-2024.02接下来安装一些必要的编译工具和库。这一步很关键缺失的依赖会导致后续编译莫名其妙地失败。sudo apt update sudo apt install -y build-essential libncurses5-dev libssl-dev bc flex bison rsync cpio python3 unzip这些包包括了编译器、内核配置需要的ncurses库、处理SSL所需的openssl开发库等。确保它们都被正确安装。2.2 针对RK3568的初始配置RK3568是一颗Arm Cortex-A55架构的四核处理器属于Armv8-A架构。Buildroot内置了对大量硬件平台的支持其中就包括Rockchip的芯片。我们可以从一个最接近的默认配置开始。make rockchip_rk3568_defconfig这条命令会加载configs/rockchip_rk3568_defconfig这个预置的配置文件。它已经为我们设置好了交叉编译工具链比如aarch64-linux-gnu-、目标架构AArch64、内核版本等基础选项。执行后会在根目录生成一个.config文件。现在我们可以进入Buildroot经典的菜单配置界面进行更细致的定制make menuconfig界面和配置Linux内核几乎一模一样。我们需要关注几个关键区域Target options这里确认架构是AArch64 (little endian)ABI选择LP64对于64位Arm系统这是标准。这些在defconfig里应该已经设好了检查一下即可。Toolchain这是重头戏。确保Toolchain type是External toolchain并且选择了正确的工具链例如使用Buildroot自带的Arm AArch64 2023.11工具链。使用外部预编译工具链通常比自己构建更快捷、稳定。同时要确保Enable C support被勾选即使你现在不用保不齐哪个包会依赖它。System configuration在这里设置系统主机名、欢迎语banner、以及最重要的root密码。我强烈建议在这里设置一个强密码而不是留空。留空虽然方便但在生产环境或暴露在网络中时是极大的安全隐患。同时你可以在这里选择初始化的系统管理器对于简单系统BusyBox init就足够了。完成基础配置后先保存退出。但先别急着编译因为我们的核心目标——SSH和SFTP——还没有配置。3. 深度集成OpenSSH不仅仅是勾选一个选项要让目标系统支持SSH登录和SFTP文件传输我们需要集成OpenSSH。在Buildroot里这对应着openssh这个包。但它的配置远不止在menuconfig里找到它并打勾那么简单。3.1 启用OpenSSH服务端与关键配置再次运行make menuconfig导航到Target packages-Networking applications-openssh进入后确保以下选项被选中openssh主包必须选。Install server安装SSH服务端sshd。Enable sftp server这是支持SFTP的关键它会在系统中安装sftp-server这个二进制文件。Enable non-privileged sftp这个选项我建议也勾上。它允许SFTP用户被chroot到一个特定目录增强安全性。对于简单的文件传输场景可以先不勾避免权限配置复杂化。配置好后保存退出。此时如果你直接编译Buildroot会下载、编译并安装OpenSSH到你的根文件系统里。但是这样生成的系统SSH服务默认是不会自动启动的而且可能缺少必要的配置文件和密钥。3.2 处理SSH主机密钥生成一个常见的“坑”OpenSSH服务端启动时需要在/etc/ssh/目录下存在主机密钥如ssh_host_rsa_key,ssh_host_ecdsa_key等。如果这些文件不存在sshd会启动失败。在桌面Linux发行版上通常有一个ssh-keygen服务在第一次启动时生成它们。但在Buildroot构建的只读根文件系统如squashfs或小型系统上我们需要在构建阶段就处理好。Buildroot的openssh包提供了一个机制在构建时生成主机密钥。我们需要在menuconfig中确认这个选项被启用。回到openssh的配置子菜单检查是否存在Generate host keys at build time之类的选项不同版本描述可能略有差异并确保其被选中。这样在编译openssh包时Buildroot会调用ssh-keygen在output/target/etc/ssh/目录下生成密钥对。这些密钥会直接被打包进最终的根文件系统镜像中。注意在构建时生成密钥意味着所有基于此镜像的设备将拥有相同的主机密钥。这在生产环境中是一个安全风险因为客户端可能会遇到“主机密钥变更”的警告如果之前连接过其他同镜像设备。对于开发原型或内部网络这可以接受。对于量产更安全的做法是在设备第一次启动时通过初始化脚本动态生成唯一的密钥或者将密钥存储在独立的分区如/data并在启动时链接过去。3.3 配置SSH服务自动启动默认情况下Buildroot不会为sshd创建自启动脚本。我们需要手动添加。有两种主流方法方法一使用BusyBox的inetd超级守护进程如果你的系统非常精简且并发连接数很少可以考虑使用inetd。在menuconfig中启用Target packages-Networking applications-inetutils或BusyBox中的inetd如果BusyBox编译了该功能。 然后需要手动配置/etc/inetd.conf文件添加一行类似ssh stream tcp nowait root /usr/sbin/sshd sshd -i的配置。这种方法资源占用小但性能和处理能力有限不适合生产环境。方法二使用独立的System V init脚本推荐这是更标准、更可控的方式。我们需要在Buildroot构建后向根文件系统中添加我们自己的启动脚本。创建脚本文件在Buildroot源码目录外或项目目录内创建一个名为S50sshd的文件。#!/bin/sh # S50sshd - Start the OpenSSH server daemon case $1 in start) echo -n Starting sshd: # 检查密钥是否存在如果构建时未生成可以在这里生成 if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then /usr/bin/ssh-keygen -A fi # 启动sshd-D表示前台运行配合放入后台或者直接使用sshd /usr/sbin/sshd echo done. ;; stop) echo -n Stopping sshd: killall sshd echo done. ;; restart|reload) $0 stop sleep 1 $0 start ;; *) echo Usage: $0 {start|stop|restart} exit 1 esac exit 0集成到Buildroot我们需要让Buildroot在构建根文件系统时将这个脚本放到正确的位置/etc/init.d/。这可以通过覆盖层Overlay或后构建脚本Post-build script来实现。使用Overlay更清晰在Buildroot目录下创建一个文件夹例如board/rockchip/rk3568/overlay。在里面创建etc/init.d/S50sshd并将上面的脚本内容放进去。然后在menuconfig中指定这个Overlay路径System configuration-Root filesystem overlay directories。使用后构建脚本在menuconfig的System configuration-Custom scripts to run after creating filesystem images中指定一个脚本。在该脚本里你可以将S50sshd拷贝到${TARGET_DIR}/etc/init.d/并赋予可执行权限chmod x。我更喜欢Overlay的方式因为它逻辑清晰文件归属明确并且可以放置其他定制文件如修改后的/etc/ssh/sshd_config。4. 编译与镜像生成第一次上电前的准备配置妥当后就可以开始编译了。这个过程比较耗时取决于你的网络速度和CPU性能。makemake命令会开始执行一系列任务下载所有选中的软件包源码、验证哈希值、解压、打补丁、配置、编译、安装到output/target目录最后根据配置生成镜像文件。编译过程中可能会遇到一些问题最常见的是下载失败。由于网络原因某些源码包可能无法从原始站点下载。Buildroot维护了一个镜像站列表。你可以在menuconfig中的Build options-Mirrors and Download locations里添加国内的镜像源例如将$(BR2_PRIMARY_SITE)设置为https://mirrors.tuna.tsinghua.edu.cn/buildroot可以显著加速下载。编译成功后你会在output/images/目录下找到生成的镜像。对于RK3568这样的平台通常会有rootfs.ext2或rootfs.ext4: 可挂载的EXT文件系统镜像。rootfs.squashfs: 高度压缩的只读根文件系统镜像常用于节省存储空间。可能还有包含内核和根文件系统的复合镜像如rockchip-kernel.img。我们需要的是根文件系统镜像。你可以将它烧录到开发板的eMMC或SD卡对应分区。烧录工具取决于你的硬件和引导方式可能是rkdeveloptool、upgrade_tool或者直接用dd命令。5. 上电调试与SFTP连接问题排查将镜像烧录到设备上电启动。如果系统配置正确你应该能通过串口看到启动日志并最终获得一个登录提示。用root和你之前设置的密码登录。5.1 验证SSH服务状态首先检查sshd进程是否已经运行ps aux | grep sshd如果看到/usr/sbin/sshd进程说明服务已启动。如果没有检查/etc/init.d/S50sshd脚本是否存在且可执行并尝试手动启动/etc/init.d/S50sshd start。查看系统日志获取更多信息dmesg | tail或cat /var/log/messages。确保开发板的网络是通的可以ping一下网关或宿主机。然后从你的宿主机尝试SSH连接ssh root开发板IP地址输入密码应该就能成功登录了。恭喜SSH部分基本搞定。5.2 SFTP连接失败权限与配置的深水区SSH能登录不代表SFTP就能用。我遇到的最典型的问题是使用Xshell、FileZilla等客户端SFTP连接时登录成功但随即提示“无法显示远程文件夹”或“收到意外文件结束符”连接被关闭。这个问题几乎总是和SFTP子系统的配置以及权限有关。我们来一步步排查。第一步检查sshd_config中的SFTP子系统设置登录到开发板查看/etc/ssh/sshd_config文件cat /etc/ssh/sshd_config | grep -i sftp你应该能看到类似这样的一行Subsystem sftp /usr/libexec/sftp-server或者如果你启用了non-privileged模式可能是Subsystem sftp internal-sftp关键点在于Subsystem指令指定的路径必须绝对正确。Buildroot默认安装的sftp-server路径通常是/usr/libexec/sftp-server或/usr/lib/sftp-server。你可以用find命令确认find / -name sftp-server 2/dev/null如果sshd_config中的路径和实际路径不匹配sshd在收到SFTP请求时就无法启动对应的子系统进程导致连接立即失败。修正sshd_config中的路径然后重启SSH服务/etc/init.d/S50sshd restart。第二步检查sftp-server二进制文件本身的权限与依赖即使路径正确如果sftp-server本身有问题也会失败。权限确保它是可执行的ls -l /usr/libexec/sftp-server。依赖库使用ldd命令检查它依赖的动态库是否都存在ldd /usr/libexec/sftp-server在精简的Buildroot系统中有时会缺失某些库比如libcrypt。如果看到not found你需要回到Buildroot配置在Target packages-Libraries-Crypto下找到并启用对应的库例如libcrypt然后重新编译openssh包make openssh-rebuild或者整个系统。第三步用户目录与权限问题针对internal-sftp或chroot场景如果你使用了internal-sftp并配置了ChrootDirectory那么权限要求非常严格。ChrootDirectory指定的目录例如/home/%u必须root用户所有且权限模式为755或750。该目录下的用户家目录例如/home/username才归相应用户所有。整个路径上的所有目录都不能有写权限给组或其他用户。一个常见的错误是将/home/username直接设置为ChrootDirectory并且该目录属主是username用户这会导致连接失败。正确的做法是ChrootDirectory /home/%u并且确保/home/username目录的属主是root:root权限是755。然后在该目录下用户username可以拥有自己的文件。对于大多数开发场景我建议暂时不要启用复杂的chroot先使用标准的sftp-server子系统确保基础功能畅通。第四步客户端连接测试与日志分析在开发板上让sshd以更详细的日志模式运行有助于诊断。可以手动启动sshd并加上-d调试标志注意这会占用终端killall sshd /usr/sbin/sshd -d -d -d然后在客户端尝试SFTP连接观察开发板终端输出的详细调试信息。这些信息通常会明确指出失败在哪一步比如“无法启动子系统”、“权限拒绝”等。同时在客户端使用命令行sftp工具连接有时比图形客户端能提供更清晰的错误信息sftp -v root开发板IP地址6. 进阶配置与安全加固建议当SSH和SFTP都能正常工作后我们可以考虑做一些进阶配置让系统更安全、更好用。6.1 定制sshd_config提升安全性默认的sshd_config可能不够安全。我们可以通过Overlay提供一个定制版本。一些建议的修改禁用root登录PermitRootLogin no。创建一个普通用户用sudo提权。禁用密码认证改用密钥认证PasswordAuthentication no和PubkeyAuthentication yes。这需要你在客户端生成密钥对并将公钥添加到开发板的~/.ssh/authorized_keys文件中。修改监听端口Port 2222。可以减少一些自动化攻击脚本的骚扰。限制用户或IP使用AllowUsers或AllowGroups。6.2 为SFTP创建专用用户让所有用户都用root进行SFTP既不安全也不便于管理。可以创建一个专门用于文件传输的用户。在Buildroot配置中确保Shadow passwords和System tools里的useradd等工具被选中。通过Overlay在/etc/passwd和/etc/shadow中添加一个用户如sftpuser或者更优雅的方式是在后构建脚本中使用chroot到${TARGET_DIR}环境下用useradd命令添加用户。在sshd_config中可以针对该用户配置internal-sftp和chroot将其文件访问限制在特定目录如/var/sftp/uploads。6.3 处理时区与本地化如果你的开发板需要正确的系统时间需要配置时区。在Buildroot的menuconfig中System configuration-Timezone data选择你所在的时区如Asia/Shanghai。 同时在Target packages-Libraries-Hardware handling下确保rdate或ntp如chrony或ntp被选中以便从网络同步时间。7. 问题复盘与经验总结回顾整个从Buildroot配置到SFTP调通的过程有几个关键点值得再次强调这些都是我踩过坑的地方主机密钥的生成时机务必理解“构建时生成”与“首次启动时生成”的利弊。对于开发板构建时生成很方便对于量产设备务必设计一个首次启动生成唯一密钥的机制可以通过一个初始化脚本/etc/init.d/S01keygen来实现生成后记得禁用或删除该脚本。服务自启动的可靠性不要想当然认为包安装了服务就会自动运行。嵌入式Linux的初始化系统无论是BusyBox init还是System V init都需要明确的脚本来控制服务。确保你的启动脚本如S50sshd被正确放置、有可执行权限并且在/etc/init.d/目录下存在正确的运行级别链接Buildroot的BusyBox init通常会执行/etc/init.d/下所有以S开头的脚本。路径的绝对准确性这是SFTP问题的罪魁祸首之一。sshd_config中的Subsystem路径、AuthorizedKeysFile路径等都必须与实际文件系统路径100%匹配。在精简的根文件系统中路径可能和桌面发行版不同务必用find命令核实。依赖库的完整性使用ldd检查关键二进制文件sshd,sftp-server的依赖。Buildroot的配置是高度可裁剪的你可能无意中裁掉了一个被SSH间接依赖的库。如果遇到“段错误”或“无法执行二进制文件”这类模糊错误首先怀疑动态链接库。充分利用调试输出当遇到连接问题特别是协议层面的问题如SFTP在服务器端开发板以调试模式运行sshdsshd -d和在客户端使用详细输出sftp -v是定位问题最快的方法。不要只看图形客户端那个模糊的错误提示。安全与便利的权衡在开发阶段为了方便可以暂时允许root密码登录、使用简单密码。但在任何打算将设备接入非受信任网络的节点前必须完成安全加固改用密钥登录、禁用root、配置防火墙规则。把这些安全配置也做到你的Buildroot Overlay或后构建脚本里确保每一台出厂设备都是安全的。构建一个带完整网络服务的嵌入式根文件系统就像搭积木Buildroot提供了积木和图纸但最终房子的稳固和功能取决于你对每一块积木软件包的理解和它们之间衔接配置、依赖、启动顺序的处理。希望这篇基于RK3568实践的长文能帮你填平在搭建SSH和SFTP服务时遇到的那些坑让你更顺畅地完成自己的嵌入式项目。