2026年正点原子开发板移植方案——从0开始的Rootfs之路(完结)应用集成:从最小化 Rootfs 到可用系统

张开发
2026/4/5 22:48:23 15 分钟阅读

分享文章

2026年正点原子开发板移植方案——从0开始的Rootfs之路(完结)应用集成:从最小化 Rootfs 到可用系统
2026年正点原子开发板移植方案——从0开始的Rootfs之路完结应用集成从最小化 Rootfs 到可用系统一路走来真不容易现在除开驱动之外的所有imx6ull教程板子已经大完结了笔者计划明天专门做一个入口指南来梳理和整理咱们目前所有IMX6ULL的uboot, 内核和根文件系统制作教程合集。仓库仍然在持续维护驱动正在热烈研究中https://github.com/Awesome-Embedded-Learning-Studio/imx-forge/前言我们到了哪一步经过前面五章的折腾我们已经有了一个可以启动的 Rootfs✅ BusyBox 编译安装完成✅ 目录结构创建完毕✅ NFS 网络挂载成功✅ 开发板可以进入 shell但是当你真正开始使用这个系统时你会发现它真的太干净了——没有vim没有top没有ps -ef连个ping命令都可能没有。想要添加一个自己的程序还得考虑库文件依赖、链接方式、部署位置等等问题。这一章我们就来解决这些问题把一个能启动的 Rootfs 变成一个能用的 Rootfs。添加常用命令和工具BusyBox 自带的命令BusyBox 其实已经提供了很多常用命令只是默认配置可能没有全部启用。要查看当前 BusyBox 支持的命令# 在开发板上/bin/busybox--list或者在主机上查看编译好的 BusyBoxarm-none-linux-gnueabihf-objdump-Tout/busybox/busybox|grephelp_main启用更多 BusyBox 命令如果你发现某个命令没有可以先检查 BusyBox 配置cdthird_party/busyboxmakeO../../out/busybox menuconfigARCHarmCROSS_COMPILEarm-none-linux-gnueabihf-一些常用的可能被禁用的命令配置项说明路径CONFIG_VIvi 编辑器Editor UtilitiesCONFIG_TOPtop 进程查看Process UtilitiesCONFIG_PSps 命令Process UtilitiesCONFIG_PINGping 命令Networking UtilitiesCONFIG_WGETwget 下载工具Networking UtilitiesCONFIG_TFTPtftp 客户端Networking UtilitiesCONFIG_IFCONFIGifconfig 网络配置Networking UtilitiesCONFIG_NETSTATnetstat 网络状态Networking Utilities踩坑经验CONFIG_PING和CONFIG_PING6可能需要额外的依赖才能正常工作比如/etc/resolv.conf和/etc/hosts文件。添加非 BusyBox 程序BusyBox 虽然强大但毕竟是一个瑞士军刀每个命令都是简化版本。如果你需要完整功能的程序就需要单独编译。编译一个简单的 Hello World// hello.c#includestdio.hintmain(intargc,char*argv[]){printf(Hello, i.MX6ULL!\n);printf(Arguments received: %d\n,argc-1);for(inti1;iargc;i){printf( arg[%d] %s\n,i,argv[i]);}return0;}交叉编译# 静态链接推荐无需拷贝库文件arm-none-linux-gnueabihf-gcc-static-ohello hello.c# 或者动态链接arm-none-linux-gnueabihf-gcc-ohello hello.c部署cphello rootfs/nfs/usr/bin/chmodx rootfs/nfs/usr/bin/hello在开发板上测试# Hello, i.MX6ULL!静态链接 vs 动态链接这是一个永恒的话题到底用静态链接还是动态链接静态链接优点无需拷贝库文件部署简单不存在库版本冲突问题程序启动稍快不需要链接时加载库缺点每个程序都包含一份库代码浪费存储空间如果多个程序使用同一个库内存中也存在多份程序体积较大适用场景小型工具程序Rootfs 存储空间有限不确定目标系统有什么库动态链接优点程序体积小多个程序共享同一份库节省内存库可以独立更新理论上的好处缺点需要确保目标系统有所需的库需要考虑库版本兼容性部署时需要拷贝库文件适用场景大型应用程序多个程序使用相同的库Rootfs 存储空间充足如何查看程序使用的库arm-none-linux-gnueabihf-readelf-dhello|grepNEEDED输出示例0x00000001 (NEEDED) Shared library: [libc.so.6] 0x00000001 (NEEDED) Shared library: [libm.so.6]查看程序是静态还是动态链接arm-none-linux-gnueabihf-file hello输出示例hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked # ^^^^^^^^^^^^^^^ # 静态链接 hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked # ^^^^^^^^^^^^^^^^ # 动态链接库文件依赖处理如果你选择动态链接就需要处理库文件依赖问题。查找所需的库文件arm-none-linux-gnueabihf-gcc-ohello hello.c arm-none-linux-gnueabihf-readelf-dhello|grepNEEDED输出示例0x00000001 (NEEDED) Shared library: [libc.so.6] 0x00000001 (NEEDED) Shared library: [ld-linux-armhf.so.3]从交叉编译工具链复制库文件找到工具链的库目录arm-none-linux-gnueabihf-gcc -print-sysroot输出示例/home/charliechen/opt/arm-none-linux-gnueabihf/libc库文件通常在/home/charliechen/opt/arm-none-linux-gnueabihf/libc/lib/复制所需的库# 创建库目录mkdir-prootfs/nfs/lib# 复制库文件注意是软链接指向的实际文件cp/home/charliechen/opt/arm-none-linux-gnueabihf/libc/lib/libc.so.6 rootfs/nfs/lib/cp/home/charliechen/opt/arm-none-linux-gnueabihf/libc/lib/ld-linux-armhf.so.3 rootfs/nfs/lib/注意很多库文件是软链接需要复制实际文件不要复制链接本身。使用cp -L或者cp -a可以自动处理软链接。验证库文件完整性arm-none-linux-gnueabihf-readelf-drootfs/nfs/usr/bin/hello|grepNEEDED然后确认每个NEEDED的库在rootfs/nfs/lib/或rootfs/nfs/usr/lib/下存在。常见的库文件库文件作用程序示例libc.so.6C 标准库几乎所有 C 程序libm.so.6数学库使用数学函数的程序libpthread.so.0线程库多线程程序libdl.so.2动态链接库使用 dlopen 的程序librt.so.1实时扩展库使用 POSIX 实时功能的程序ld-linux-*.so.*动态链接器所有动态链接程序添加自定义应用程序组织应用程序目录结构建议按照以下结构组织应用程序rootfs/nfs/ ├── usr/ │ ├── bin/ # 用户程序 │ ├── sbin/ # 系统管理程序 │ └── lib/ # 应用程序库 ├── opt/ # 可选软件包 │ └── myapp/ │ ├── bin/ # myapp 的可执行文件 │ ├── lib/ # myapp 的库 │ └── etc/ # myapp 的配置 └── home/ # 用户目录 └── myapp/ # 用户程序编写一个实用程序LED 控制工具// led_ctrl.c#includestdio.h#includestdlib.h#includefcntl.h#includeunistd.h#includestring.h#defineLED_PATH/sys/class/leds// 列出所有 LEDvoidlist_leds(){charcmd[128];snprintf(cmd,sizeof(cmd),ls %s 2/dev/null,LED_PATH);system(cmd);}// 控制 LED 状态intset_led(constchar*led_name,constchar*state){charpath[128];intfd;ssize_tret;// 构造亮度控制路径snprintf(path,sizeof(path),%s/%s/brightness,LED_PATH,led_name);fdopen(path,O_WRONLY);if(fd0){perror(open brightness);return-1;}retwrite(fd,state,strlen(state));close(fd);if(ret0){perror(write brightness);return-1;}printf(LED %s set to %s\n,led_name,state);return0;}intmain(intargc,char*argv[]){if(argc2){printf(Usage: %s command [args]\n,argv[0]);printf(Commands:\n);printf( list List all LEDs\n);printf( on led_name Turn on LED\n);printf( off led_name Turn off LED\n);return1;}if(strcmp(argv[1],list)0){list_leds();}elseif(strcmp(argv[1],on)0){if(argc3){fprintf(stderr,Error: LED name required\n);return1;}set_led(argv[2],255);}elseif(strcmp(argv[1],off)0){if(argc3){fprintf(stderr,Error: LED name required\n);return1;}set_led(argv[2],0);}else{fprintf(stderr,Unknown command: %s\n,argv[1]);return1;}return0;}编译arm-none-linux-gnueabihf-gcc-oled_ctrl led_ctrl.ccpled_ctrl rootfs/nfs/usr/bin/chmodx rootfs/nfs/usr/bin/led_ctrl在开发板上使用# 列出所有 LEDled_ctrl list# 打开 LEDled_ctrl on led0# 关闭 LEDled_ctrl off led0Shell 脚本集成Shell 脚本是快速添加功能的利器不需要编译。示例网络配置脚本#!/bin/sh## /usr/bin/netconfig - 简单的网络配置脚本#show_usage(){echoUsage: netconfig command [args]echoCommands:echo status Show network statusecho eth0 ip Configure eth0 IPecho dhcp eth0 Enable DHCP on eth0}net_status(){echo Network Interfaces ifconfig-aechoecho Route Table route}set_static_ip(){localiface$1localip$2if[-z$iface]||[-z$ip];thenechoError: interface and IP requiredreturn1fiechoConfiguring$ifaceto$ip...ifconfig$iface$ipupechoDone.}enable_dhcp(){localiface$1if[-z$iface];thenechoError: interface requiredreturn1fiif[-x/sbin/udhcpc];thenechoStarting DHCP on$iface.../sbin/udhcpc-i$iface-nelseechoError: udhcpc not foundreturn1fi}# 主逻辑case$1instatus)net_status;;eth0)set_static_ip$1$2;;dhcp)enable_dhcp$2;;*)show_usageexit1;;esac部署cpnetconfig rootfs/nfs/usr/bin/chmodx rootfs/nfs/usr/bin/netconfig示例系统监控脚本#!/bin/sh## /usr/bin/sysinfo - 系统信息显示脚本#echoecho System InformationechoechoechoHostname:$(hostname)echoUptime:$(uptime)echoecho Memory Usage freeechoecho CPU Info echoProcessor:$(grepProcessor/proc/cpuinfo|cut-d:-f2)echoHardware:$(grepHardware/proc/cpuinfo|cut-d:-f2)echoecho Mount Points mount|grep-Eproc|sys|tmpfs|nfsechoecho Network ifconfigeth0echoecho Processes psecho系统启动服务配置如果你的程序需要在系统启动时自动运行可以把它添加到启动脚本中。方法 1修改 /etc/init.d/rcS#!/bin/sh## System initialization script#PATH/sbin:/bin:/usr/sbin:/usr/bin:$PATHLD_LIBRARY_PATH$LD_LIBRARY_PATH:/lib:/usr/libexportLD_LIBRARY_PATH# Mount all filesystems specified in fstabmount-a# Create and mount devpts for pseudo-terminal supportmkdir-p/dev/ptsmount-tdevpts devpts /dev/pts# Populate /dev with device nodesmdev-s# Configure loopback interfaceifconfiglo127.0.0.1 up# 自定义启动服务 # 启动应用程序如果需要后台运行使用 # /usr/bin/myapp # 配置网络可选# /usr/bin/netconfig eth0 192.168.60.200# Print welcome messageechoechoWelcome to i.MX6ULL Embedded Linux!echoSystem uptime:$(uptime)echo方法 2创建独立的启动脚本对于复杂的服务可以创建独立的启动脚本#!/bin/sh## /etc/init.d/myapp - My application service#case$1instart)echoStarting myapp.../usr/bin/myapp--daemon;;stop)echoStopping myapp...killallmyapp;;restart)$0stopsleep1$0start;;status)ifpidof myapp/dev/null;thenechomyapp is runningelseechomyapp is not runningfi;;*)echoUsage:$0{start|stop|restart|status}exit1;;esacexit0然后在/etc/init.d/rcS中调用# Start custom services[-x/etc/init.d/myapp]/etc/init.d/myapp start方法 3使用 inittab 自动重启服务如果你希望服务崩溃后自动重启可以使用/etc/inittab的respawn功能# 在 /etc/inittab 中添加::respawn:/usr/bin/myapp这样当myapp退出时init 会自动重启它。完整的 Rootfs 验证启动验证清单系统能否正常启动能否进入 shell/proc、/sys是否正确挂载网络是否正常常用命令是否可用ls, cd, cat, ps 等自定义程序是否能运行库文件依赖是否满足验证脚本创建一个验证脚本verify_rootfs.sh#!/bin/sh## Rootfs 验证脚本#echoechoRootfs VerificationechoechoERRORS0# 检查关键目录echoChecking directories...fordirin/bin /sbin /etc /lib /dev /proc /sys /tmp /usr;doif[-d$dir];thenecho ✓$direxistselseecho ✗$dirmissing!ERRORS$((ERRORS1))fidoneecho# 检查关键设备文件echoChecking device files...fordevin/dev/console /dev/null /dev/zero;doif[-e$dev];thenecho ✓$devexistselseecho ✗$devmissing!ERRORS$((ERRORS1))fidoneecho# 检查虚拟文件系统挂载echoChecking mounted filesystems...forfsin/proc /sys /dev/pts;doifmount|grep-q$fs;thenecho ✓$fsmountedelseecho ✗$fsnot mounted!ERRORS$((ERRORS1))fidoneecho# 检查配置文件echoChecking configuration files...forfilein/etc/inittab /etc/fstab /etc/init.d/rcS;doif[-f$file];thenecho ✓$fileexistselseecho ✗$filemissing!ERRORS$((ERRORS1))fidoneecho# 检查关键命令echoChecking commands...forcmdinshlscatmountps;doifcommand-v$cmd/dev/null21;thenecho ✓$cmdavailableelseecho ✗$cmdnot found!ERRORS$((ERRORS1))fidoneecho# 检查网络echoChecking network...ififconfiglo/dev/null21;thenecho ✓ Loopback interface upelseecho ✗ Loopback interface down!ERRORS$((ERRORS1))fiecho# 总结echoif[$ERRORS-eq0];thenecho✓ All checks passed!elseecho✗$ERRORSerror(s) found!exit1fiecho运行验证chmodx verify_rootfs.shcpverify_rootfs.sh rootfs/nfs/usr/bin/在开发板上运行verify_rootfsRootfs 优化建议减小体积使用 BusyBoxBusyBox 已经包含了大部分常用命令去掉不需要的库只保留程序实际需要的库使用 strip 去除符号表arm-none-linux-gnueabihf-strip rootfs/nfs/usr/bin/myapp启用内核模块把不需要的驱动编译成模块按需加载提高安全性设置正确的文件权限chmod700rootfs/nfs/rootchmod755rootfs/nfs/etc/shadow使用只读 Rootfs在bootargs中添加ro选项把 Rootfs 挂载成只读禁用不需要的服务最小化启动脚本提高启动速度减少启动脚本中的等待并行启动服务如果 init 支持使用更快的文件系统squashfs 只读文件系统总结从零构建可用 Rootfs经过这一系列章节的学习我们完成了BusyBox 编译搭建了 Rootfs 的基础目录结构创建按照 FHS 标准建立了文件系统骨架NFS 网络启动实现了开发板通过网络挂载 Rootfs应用集成添加了自定义程序和脚本系统优化验证并优化了 Rootfs现在你有了一个功能完整、可用的嵌入式 Linux Rootfs。虽然它可能还很简单但你已经掌握了构建和定制 Rootfs 的核心技能。当你需要添加新功能时基本流程是交叉编译你的程序确定是静态还是动态链接如果动态链接复制所需的库文件把程序部署到合适的目录/usr/bin、/usr/sbin等如果需要开机启动添加到启动脚本在开发板上测试验证下一步你自己的 Rootfs到这里Rootfs篇章已经全部完结现在你已经掌握了 Rootfs 构建的基础知识。接下来你可以添加你自己的应用程序把你实际的项目程序集成进来优化启动脚本根据需要定制系统启动流程添加更多功能比如网络服务、图形界面等打包部署把 Rootfs 打包成镜像烧录到 Flash嵌入式 Linux 的世界很大Rootfs 只是其中的一个起点。但正是这个起点支撑起了整个系统的运行。祝你构建出完美的 Rootfs

更多文章