ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hi3559AV100平台ext4根文件系统制作与eMMC烧录全流程

Hi3559AV100平台ext4根文件系统制作与eMMC烧录全流程 1. 项目概述为什么要在Hi3559AV100上折腾ext4和eMMC做嵌入式开发的老哥们应该都有感触海思Hi3559AV100这颗料在IPC、智能安防、边缘算力盒子里出镜率一直很高四核A73加双核A53的大小核架构自带IVE、GPU、NPU算力是真猛。但芯片再强软件系统也得有一套稳定能跑的根文件系统来托底。很多项目做到中期会面临一个很尴尬的局面SDK自带的rootfs要么太臃肿里面一堆用不到的库和demo程序要么太精简连自定义协议栈、算法库、业务进程都没法直接往上塞。这时候就需要我们自己动手基于项目实际需求裁剪制作一个ext4格式的根文件系统镜像再烧录到板载eMMC里让板子上电就能从eMMC启动。这篇内容不是教科书是我自己在Hi3559AV100平台上一路踩坑走出来的实操记录。从根文件系统的目录搭建、busybox编译、动态库移植到ext4镜像的几种制作方式再到eMMC分区规划、fastboot烧录、环境变量配置以及启动失败时的排查思路基本都覆盖了。如果你正好也卡在“不知道从哪一步开始”“分区表怎么定”“烧进去起不来”这几种状态这篇应该能帮你省下不少折腾时间。另外多说一句Hi3559AV100支持从多种介质启动SD卡、SPI Nor、eMMC都行。我这次选定eMMC作为最终量产介质是因为它容量大板载8GB起步、读写速度快、支持HS400模式而且不像SD卡那样容易松动接触不良车规、安防这类长时间通电的场景用起来更稳。2. 制作ext4根文件系统的完整流程2.1 从hi3559 SDK包里挖出一个干净的base rootfs第一步不是急着创建目录而是先到海思SDK里找现成rootfs模板。以Hi3559AV100的SDK为例一般在osdrv/pub/rootfs_glibc目录下会有官方打包好的rootfs压缩包我习惯先把它解压出来tar -xvf rootfs_glibc.tgz解压之后就是一套标准的、能直接chroot进去运行的根文件系统里面/etc、/lib、/dev、/usr等目录都已经建好了glibc库也完整。这套模板最大的价值是省去了手动创建目录节点和补充基础链接库的时间在此基础上做裁剪或扩充都方便。但注意直接把SDK这套rootfs原样做进eMMC虽然能启动却有明显的代价系统起来之后会跑很多SDK自带的H264/H265编码测试demo、音频算法库、VPSS sample程序这些都是你量产固件里根本用不到的东西。所以我建议在解压之后做一次系统性瘦身把大型demo可执行文件、无用测试脚本清掉只保留跟业务相关的库文件。清理的时候我用的是最“土”但最不容易出错的办法——先全部保留编译完自己的业务进程后用arm-linux-gnueabihf-readelf -d或aarch64-linux-gnu-readelf查看每个可执行文件依赖的.so逆推保留哪些库。这种方式比凭经验乱删库要靠谱得多实测删错库导致开不了机的概率会大幅降低。2.2 busybox编译配置静态链接还是动态链接rootfs里的“用户态控制台工具”我用的基本是busybox。虽然SDK提供的是完整的busybox源码包流传在社区里但老三样照样得自己过一遍配置菜单make menuconfig这里面有两个关键选择直接影响镜像大小和排障难度。第一个是CONFIG_STATIC是否静态编译busybox。我的建议是根文件系统里只有一个busybox时优先考虑静态编译这样busybox本体不依赖libc即使将来动态库出了兼容性问题shell和基础工具依然能跑救援的时候非常有用。缺点是静态编译出来的二进制会大一些大概多出200KB左右但对8GB的eMMC来说根本不是事。第二个是选中哪些applet。按量产最小化原则我主要保留以下几组核心shell相关ash、sh、mount、umount、chroot、cp、mv、rm、mkdir、cat、echo、ls系统管理ps、top、kill、insmod、devmem、dmesg、reboot、poweroff网络调试ifconfig、udhcpc、ping、telnetd、nc文件操作vi、tftp、find、awk、sed、grep这里还有个细节海思平台跑业务会常用到devmem比如读写寄存器、查硬件状态所以这一个applet务必保留不然调试芯片寄存器要抓瞎。编译时要注意交叉工具链路径不同版本SDK的工具链前缀不一样有的用arm-linux-gnueabihf-有的用aarch64-linux-gnu-建议先用source /opt/hisi-linux/x86-arm/这种环境变量脚本统一指定再跑make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j8 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- install CONFIG_PREFIX../rootfs把编译产物安装到rootfs目录下busybox会自己在bin目录下生成对应的软链接。2.3 /etc目录和inittab、rcS、fstab的配置细节内核启动后会调用init进程最常见的init就是busybox内置的它会读取/etc/inittab按文件里写的条目启动系统服务。inittab写得不好板子启动时不是卡在这里就是黑屏重启我把自己在项目里实际用的模板贴出来::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::restart:/sbin/init ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r这段配置的意思是系统初始化时先执行rcS脚本随后启动一个shell如果shell进程崩了会自动重新拉起。/etc/init.d/rcS是真正干活的脚本我一般在这个脚本里面做几件固定的事情#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t tmpfs tmpfs /tmp mkdir -p /var/log mkdir -p /dev/pts mount -t devpts devpts /dev/pts mdev -s这里要强调mdev -s这一步是通过busybox的mdev机制自动扫描并创建各设备节点。很多新手做rootfs时喜欢手动在/dev下创建一堆mmcblk0、ttyAMA0节点这种做法又土又容易漏。用mdev维护设备节点TF卡或U盘热插拔时还能自动感应。/etc/fstab也不能忽略mount -a 会依赖它。虽然我的rcS脚本是手动mount关键文件系统但rootfs启动完业务程序会读取fstab里的配置来挂载用户数据分区典型配置是/dev/mmcblk0p5 /userdata ext4 noatime,nodiratime 0 02.4 动态库拷贝与版本一致性检测rootfs如果选用动态编译的业务程序lib目录下的库文件就得保证足够齐全。我用的是“穷举依赖法”对着业务可执行文件用readelf轮询一遍依赖再把搜索结果拷贝到rootfs/lib或rootfs/usr/lib下aarch64-linux-gnu-readelf -d ./sample_vdec | grep NEEDED这一步我踩过的坑就不止一次拷贝了某个.so但没注意它本身还有非标准路径下的依赖库或者软链接指向了不存在的版本号结果系统起来后直接报error while loading shared libraries。所以拷贝完库之后建议顺手在rootfs里用chroot方式验证一遍chroot /path/to/rootfs /bin/sh如果chroot后能正常进入shell并执行业务程序基本说明库依赖没有大问题。很多做嵌入式的兄弟直接拿宿主机环境模拟结果rootfs里缺库都不知道直到烧录完才傻眼。2.5 镜像打包要用make_ext4fs还是mkfs.ext4制作ext4镜像这事社区有几种流派我在这里一次性说透。第一种是SDK自带的方式海思的osdrv里提供了make_ext4fs它在宿主机上就能把一个目录打包成ext4镜像文件。典型用法make_ext4fs -l 512M -s rootfs.img rootfs/这里的-l 512M表示镜像分区大小-s表示生成稀疏镜像sparse格式烧录时更快占用的临时空间也更小。这种方式适合在PC端制好镜像后直接通过fastboot或Hitool工具烧录。第二种是先把rootfs打成tar包在开发板上通过uboot把tar包放到SD卡或通过网络下载到内存再解压到指定eMMC分区。这种方式灵活性更高但因为要在板子上解包调试阶段用起来效率低。建议量产固件还是优先做镜像文件。第三种是直接在开发板上用mkfs.ext4 /dev/mmcblk0p5格式化分区再用cp或rsync把rootfs目录拷进去。注意这种方式的弊端是没法做稀疏镜像每次更新都要整体拷文件比较慢只适合前期验证。我在项目里量产固件统一采用第一种配合稀疏镜像烧录时快得很。3. 烧录eMMC的完整实操3.1 eMMC分区规划boot、kernel、rootfs、userdata怎么划在动手烧录前先把eMMC的分区规划好不然等uboot、kernel、rootfs来回覆盖几次后想反悔就麻烦了。Hi3559AV100的uboot里支持用mmc命令操作eMMC但更推荐的做法是在PC端先规划好分区表再烧录时分别写入。这里给出我在项目里实际使用的8GB eMMC分区方案分区起始扇区大小文件系统用途p11MiB4MiB无bootloader/ubootp25MiB1MiB无env环境变量区p37MiB3MiB无logo分区p410MiB20MiB无内核分区p530MiB512MiBext4根文件系统p6542MiB剩余ext4用户数据/录像存储注意p4起始扇区要留出足够余量因为uboot、env、logo分区实际大小可能有变化建议每个分区起始地址都比上一个大一些防止溢出覆盖。分区偏移量的计算我一般习惯用扇区做单位1MiB 2048扇区规划时直接按扇区递增烧录时计算偏移也方便。3.2 通过uboot网络启动和烧录的详细步骤Hi3559AV100开发阶段我用得最多的烧录方式是通过网络tftp加载镜像到内存再用mmc write写入eMMC指定扇区。流程大概是这样先准备tftp服务器把uboot.bin、uImage、rootfs.img放在tftp目录下。开发板进入uboot菜单后配置网络与服务器地址setenv serverip 192.168.1.100 setenv ipaddr 192.168.1.10 setenv netmask 255.255.255.0 saveenv然后用tftp命令把uImage下载到内存DDR地址。Hi3559AV100上常用的下载地址是0x82000000tftp 0x82000000 uImage_hi3559av100下载完成之后把内核写入eMMC的p4分区。要根据分区表算好内核分区的起始扇区。上表里p4从第10MiB开始即起始扇区是20480一个扇区是512B单次写入块大小通常是512B。mmc write命令的第三个参数是起始扇区第四个参数是要写入的扇区数mmc write 0x0 0x82000000 0x5000 0x5000这里0x5000就是十进制的20480表示从p4分区起始位置写入0x5000个扇区大小约10MB刚好覆盖uImage的实际大小。bootloader写入p1时偏移为0x800扇区命令类似。rootfs.img的体积比较大如果生成的镜像接近512MB用mmc write一次性写入的话内存里怎么放得下实际上我们烧录时不会先整体加载rootfs.img到内存而是用海思专用烧录工具Hitool或fastboot把镜像按块传输或者先用tftp把rootfs.img下载到内存然后分块写入eMMC操作起来更费事。量产阶段直接使用PC端Hitool更省心一次可以按分区表配置把uboot、env、kernel、rootfs全部烧录还能写分区间隙。3.3 fastboot方式烧录rootfs.img另一种更现代的烧录方式是走fastboot协议。海思平台在uboot阶段执行fastboot命令后会枚举USB设备PC端用fastboot工具执行分区烧录。这种方式比tftp还需要串口辅助的情况更简洁烧录速度也快。板子进入uboot后执行fastboot 0PC端依次执行fastboot flash uboot uboot.bin fastboot flash kernel uImage_hi3559av100 fastboot flash rootfs rootfs.imgfastboot会把分区名和镜像对应起来前提是uboot环境变量里已经定义了fastboot_partition分区表例如setenv fastboot_partition uboot:1M:5M,bootargs:1M:5M,logo:3M:7M,kernel:20M:10M,rootfs:512M:30M:ext4,userdata:($userdata_size):542M:ext4 saveenvfastboot里rootfs分区如果带ext4属性烧录时会自动格式化处理实际使用中很方便。3.4 设置bootargs和bootcmd让内核正确挂载rootfs烧录完之后能不能正常启动很大程度取决于bootargs环境变量配置。重点在于告诉内核根文件系统在哪、什么格式。我用的bootargs模板setenv bootargs mem2G consolettyAMA0,115200 root/dev/mmcblk0p5 rw rootfstypeext4 rootdelay1 init/linuxrc saveenv这里的/dev/mmcblk0p5必须和分区表里rootfs所在的分区号严格对应写错一个数字直接卡在kernel panic。init/linuxrc指向根文件系统根目录下的linuxrcbusybox安装后会在rootfs根目录生成这个文件软链指向bin/busybox。bootcmd是uboot启动时自动执行的命令序列我的实现是setenv bootcmd mmc dev 0; mmc read 0x0 0x82000000 0x5000 0x5000; bootm 0x82000000这里mmc read把内核从eMMC p4分区读到内存0x82000000然后用bootm启动内核。每次修改环境变量之后都要saveenv保存否则上电重启后会丢失配置。4. 常见启动失败、分区问题与eMMC细节排查4.1 “No filesystem could mount rootfs”怎么破启动时串口打印一个VFS: Unable to mount root fs via NFS或No filesystem could mount root基本可以断定根文件系统格式或镜像写入有问题。我遇到这种情况时按顺序排查bootargs里rootfstypeext4是否和实际烧录的文件系统格式一致/dev/mmcblk0p5设备节点是否被内核正确创建可以在kernel cmdline里临时加上rootwait等待rootfs镜像是否完整烧录用uboot的mmc read与md命令对比快照内核是否开启了ext4文件系统支持在menuconfig的Filesystem下勾选CONFIG_EXT4_FS有些裁剪过的内核默认没开很多新手忘记排查第4点明明rootfs没问题烧录也成功但内核根本不支持ext4自然挂不上。4.2 分区表设计不当导致启动链断裂分区表问题最典型的特征就是uboot能跑内核起不来或者内核起来了但rootfs挂载不上。我把规划时的注意点整理一下uboot分区不能太小Hitool或fastboot写入时如果镜像体积超过分区大小可能会覆盖后面的env导致环境变量丢失env分区即使不单独划分也要预留足够的冗余空间因为uboot env每次保存都要重新写入整块区域内核分区不能过小要预留后续内核升级的空间建议20MiB起步rootfs分区建议至少给实际rootfs体积的1.3倍余量留出日志和临时文件的空间这些在设计阶段花10分钟想清楚后面调试能省两天时间。4.3 eMMC的HS400模式、烧录速度和写入稳定性Hi3559AV100原生支持eMMC5.1接口会协商到HS400模式。HS400即运行在400MHz DDR模式下的eMMC时序理论带宽很高。实测下来读写速度确实快但要注意的是如果板子布线质量不好HS400模式可能出现偶发读写错误反而不如稳定跑HS200来的省心。接示波器实测时HS400模式的CLK频率为200MHzDDR采样数据线为8位并行关注点主要在时钟质量、信号完整性和数据线眼图。量产阶段如果遇到偶发启动失败先在uboot里把eMMC降到HS200或HS50模式测试排除时序不稳定带来的干扰mmc dev 0 mmc setmode 4另外eMMC和SD卡不同有内部磨损均衡和坏块管理机制。操作系统层的坏块管理比如ext4的坏块表大部分场景派不上用场因为eMMC固件会在内部屏蔽坏块你把eMMC格式化成ext4再标记坏块意义并不大。如果是把U盘或SD卡做文件系统才需要靠文件系统层处理坏块扇区或者靠VFS层的读写反馈来重新映射。4.4 fstrim、discard对eMMC的影响到底有多大很多做嵌入式的人关心eMMC要不要开TRIM/discard。eMMC是NAND Flash介质写入前需要先擦除如果不通知eMMC哪些块可以后台清理时间长了写入速度会明显下降。ext4文件系统在挂载时如果加了discard参数每次删除文件都会下发TRIM命令长期使用性能衰减较小但代价是频繁TRIM命令会占用eMMC控制器开销极端情况下可能影响寿命和瞬时性能。我的实践方案是系统分区挂载时不加discard只在维护脚本里定期执行fstrim另外用户数据分区以视频录像这类大文件有序写入为主也不需要频繁TRIM。如果确认某个分区是频繁读写的小文件可以在/etc/fstab中给对应挂载项加上discard参数/dev/mmcblk0p6 /userdata ext4 defaults,noatime,discard 0 0这种取舍做产品时一定要想清楚——是优先追求长时间吞吐稳定还是优先降低每小时的写入命令数量。4.5 Ubuntu下如何读写ext4分区及eMMC调试建议在开发机上读eMMC/U盘里的ext4分区最简单的方法是直接用Linux系统自带的挂载命令把读卡器或USB转eMMC模块连接到Ubuntu插入后执行lsblk找出设备节点再mount到一个空目录即可。需要提醒的是Windows系统默认不识别ext4要读的话可以用WSL2挂载、DiskGenius或第三方ext4驱动但写入操作有风险建议只在Linux环境做。开发板调试时尽量多用串口日志定位问题再结合dmesg查看内核日志。eMMC内部的垃圾回收、磨损均衡是eMMC控制器的内部行为Linux系统层几乎看不到不要指望通过查看“内部垃圾”来优化性能。要确认eMMC写放大能看到的指标只有读写吞吐、IOPS以及系统日志里的写错误计数。5. 几个提升量产效率的实用建议5.1 做img差分升级而不是整包重复烧录量产阶段如果每次都要烧录整个rootfs镜像生产线效率很受影响。很多项目把根文件系统拆成“系统分区”和“用户数据分区”系统分区只放可执行文件和库用户数据分区放需要动态更新的模型、配置文件。升级时只需更新系统分区里的几个文件或者做增量差分升级而不是反复烧录整个ext4镜像。eMMC这种块设备支持按扇区写入更新一个小分区通常几百毫秒就完成了。5.2 给rootfs增加只读挂载与overlayfs方案如果产品对稳定性和抗掉电要求很高建议把rootfs分区做成只读再通过overlayfs把可写层放到另一个临时分区或tmpfs上。这样即使系统异常断电根文件系统也不会被写坏。Hi3559AV100平台的常规做法是让/etc、/var这类目录落到overlay上层把日志输出到内存或独立的用户数据分区。这个方案的代价是rootfs的修改不再直接持久化同时相关应用需要适配。5.3 用环境变量固化分区参数避免烧录后改配置海思uboot环境变量的好处是可以在烧录后用setenv动态调整启动参数但量产设备上不建议依赖人为改配置。我一般把uboot环境变量里的bootargs、bootcmd一次性固化到镜像里再通过编译脚本生成最终的production镜像。这样同一套eMMC镜像在不同板卡上的行为完全一致排查生产问题也更容易。6. 实操中的心得与踩坑记录其实这个项目从头做到尾最大的体会就是一个词一致。内核要和rootfs的文件系统支持保持一致分区表要和你写的命令保持一致设备树要和你实际外设保持一致哪一环不一致都直接黑屏重启。还有一个小技巧制作完rootfs后尽量在开发板上先做一次fsck.ext4检查让文件系统干净地挂载一次再烧量产镜像可以避免因为宿主机上未安全卸载导致根文件系统脏数据的问题。最后一个提醒注意sync。修改完rootfs后不要马上拔卡断电确保执行了sync或者在脚本里手动调一次sync把文件系统缓存刷到eMMC物理介质上。做产品就是这样细节到位了产线良率才上得去系统也才能安静地跑上一年又一年。
返回列表