Firefly-RK3399从Ubuntu 16.04到自定义Rootfs手把手教你编译内核与打包固件在嵌入式开发领域能够自主定制系统镜像是一项极具价值的能力。Firefly-RK3399作为一款性能强大的开发板其开放的架构为开发者提供了深度定制的可能性。本文将带你从基础的Ubuntu 16.04系统出发逐步深入到内核编译、驱动开发、根文件系统定制最终生成完全属于自己的固件镜像。1. 开发环境搭建与准备在开始定制系统之前我们需要准备一个稳定可靠的开发环境。推荐使用Ubuntu 18.04作为宿主机系统因为它提供了良好的兼容性和完善的工具链支持。首先安装必要的编译工具和依赖sudo apt-get update sudo apt-get install -y build-essential lzop libncurses5-dev libssl-dev git对于64位系统还需要安装32位兼容库sudo apt-get install -y libc6:i386接下来获取Rockchip专用的mkbootimg工具git clone https://github.com/neo-technologies/rockchip-mkbootimg.git cd rockchip-mkbootimg make sudo make install关键工具版本检查表工具名称最低版本要求检查命令gcc7.4.0gcc --versionmake4.1make --versiongit2.17git --versionlibncurses5-dev6.1dpkg -l libncurses5-dev提示如果在后续步骤中遇到奇怪的编译错误首先检查工具链版本是否符合要求。2. 获取与配置内核源码Firefly-RK3399的内核源码可以从官方仓库获取git clone https://gitlab.com/TeeFirefly/linux-kernel.git cd linux-kernel获取交叉编译工具链git clone https://gitlab.com/TeeFirefly/prebuilts.git export PATH$PATH:$(pwd)/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin配置内核使用Firefly的默认配置make ARCHarm64 firefly_linux_defconfig对于自定义内核配置可以使用menuconfig界面make ARCHarm64 menuconfig在menuconfig界面中你可以启用或禁用特定内核模块调整系统参数添加实验性功能支持常见配置选项参考配置项推荐设置说明CONFIG_LOCALVERSION自定义设置内核版本标识CONFIG_MODULESY启用模块支持CONFIG_CMDLINE保留默认除非有特殊启动参数需求CONFIG_DEBUG_KERNELN生产环境建议关闭CONFIG_DRM_ROCKCHIPY必须启用以支持显示输出3. 开发与集成自定义驱动让我们以一个简单的Hello World驱动为例演示如何将自定义代码集成到内核中。在drivers目录下创建新目录并添加驱动文件mkdir -p drivers/hello cd drivers/hello创建hello.c驱动文件#include linux/init.h #include linux/module.h static int __init hello_init(void) { printk(KERN_INFO Hello World from Firefly-RK3399!\n); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Goodbye from Firefly-RK3399!\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple Hello World driver);创建对应的Makefileobj-$(CONFIG_HELLO) hello.o创建Kconfig配置文件config HELLO tristate Hello World driver help This is a simple Hello World driver example.然后需要修改上一级目录的Makefile和Kconfig将新驱动包含进构建系统cd .. echo obj-y hello/ Makefile在Kconfig中添加source drivers/hello/Kconfig重新配置内核确保新驱动被启用make ARCHarm64 menuconfig在Device Drivers菜单中找到并启用Hello World driver选项。4. 编译内核与生成镜像完成所有配置后可以开始编译内核make ARCHarm64 rk3399-firefly-linux.img -j$(nproc)编译过程可能会持续10-30分钟取决于你的主机性能。编译完成后会在内核根目录生成以下关键文件kernel.img内核镜像resource.img资源镜像包含设备树等常见问题排查编译失败提示缺少头文件检查是否安装了所有依赖库确认交叉编译工具链路径设置正确生成的镜像无法启动检查是否使用了正确的defconfig确认设备树文件(rk3399-firefly-linux.dts)是否正确模块加载失败检查内核版本是否匹配确认模块签名设置5. 构建自定义根文件系统我们将使用Ubuntu Base 16.04作为基础创建自定义的根文件系统。首先获取基础文件系统wget http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ubuntu-base-16.04.2-base-arm64.tar.gz mkdir rootfs sudo tar -xpf ubuntu-base-16.04.2-base-arm64.tar.gz -C rootfs准备chroot环境sudo cp /usr/bin/qemu-aarch64-static rootfs/usr/bin/ sudo cp -b /etc/resolv.conf rootfs/etc/resolv.conf配置软件源sudo sh -c echo deb http://ports.ubuntu.com/ubuntu-ports xenial main universe rootfs/etc/apt/sources.list进入chroot环境进行定制sudo chroot rootfs /bin/bash在chroot环境中进行基本配置apt update apt upgrade -y apt install -y sudo ssh net-tools vim添加用户并设置密码useradd -m -s /bin/bash firefly passwd firefly usermod -aG sudo firefly passwd root退出chroot环境后创建镜像文件dd if/dev/zero offirefly-rootfs.img bs1M count2048 mkfs.ext4 firefly-rootfs.img mkdir tmp-mount sudo mount firefly-rootfs.img tmp-mount sudo cp -a rootfs/* tmp-mount/ sudo umount tmp-mount优化镜像大小e2fsck -p -f firefly-rootfs.img resize2fs -M firefly-rootfs.img6. 打包完整固件现在我们已经有了所有必要的组件可以打包成完整的update.img固件了。首先准备打包目录结构rockdev/ ├── Image/ │ ├── kernel.img │ ├── resource.img │ ├── MiniLoaderAll.bin │ ├── uboot.img │ ├── trust.img │ └── firefly-rootfs.img └── package-filepackage-file内容示例# NAME Relative path # #HWDEF HWDEF package-file package-file bootloader Image/MiniLoaderAll.bin parameter Image/parameter.txt trust Image/trust.img uboot Image/uboot.img boot Image/kernel.img rootfs Image/firefly-rootfs.img使用RK工具链打包./rkImageMaker -RK3399 Image/MiniLoaderAll.bin Image/trust.img Image/uboot.img Image/kernel.img Image/resource.img firefly-update.img最终生成的firefly-update.img可以通过Rockchip的工具刷写到开发板中。7. 高级定制技巧7.1 优化启动速度通过分析启动过程可以识别并优化耗时环节# 在内核命令行添加initcall_debug和printk.time1 consolettyFIQ0 rootPARTUUID614e0000-0000 rootfstypeext4 initcall_debug printk.time1启动时间优化策略并行初始化确保内核配置启用了CONFIG_HAVE_KERNEL_XZ和CONFIG_HAVE_KERNEL_LZ4等压缩选项减少不必要的服务在根文件系统中禁用不需要的systemd服务预链接库文件减少动态链接时间7.2 安全加固生产环境部署前应考虑以下安全措施禁用root直接登录配置防火墙规则启用SELinux或AppArmor定期更新安全补丁安全加固示例# 在chroot环境中执行 apt install -y fail2ban sed -i s/PermitRootLogin yes/PermitRootLogin no/ /etc/ssh/sshd_config7.3 空间优化对于存储空间有限的场景可以考虑使用更小的基础系统如Debian或Buildroot移除不必要的文档和语言包使用squashfs等压缩文件系统清理无用文件的命令apt-get clean rm -rf /usr/share/doc/* rm -rf /usr/share/man/*8. 调试与问题解决开发过程中难免会遇到各种问题以下是一些实用的调试技巧内核日志查看dmesg | less # 查看内核环形缓冲区 journalctl -k # 使用systemd的系统查看内核日志常见问题及解决方案问题现象可能原因解决方法系统无法启动内核与rootfs不匹配检查内核配置中的rootfs参数驱动加载失败内核版本不一致重新编译驱动或内核网络不可用设备树配置错误检查dts文件中的网络部分显示异常DRM驱动问题更新或重新配置显示驱动系统随机崩溃内存或电源问题检查硬件连接和电源供应性能分析工具perf系统级性能分析perf top -a # 实时监控系统性能strace系统调用跟踪strace -f -o log.txt ./your_programvalgrind内存调试工具valgrind --leak-checkfull ./your_program在实际项目中我们曾遇到一个棘手的问题自定义驱动导致系统随机死锁。通过使用kgdb进行内核调试最终发现是自旋锁使用不当导致的。这个经验告诉我们即使是最简单的驱动也需要严格的测试和验证。