做OpenHarmony标准系统设备适配时最常遇到的一类需求就是芯片厂商给的Wi-Fi、蓝牙、Sensor、DPU这类驱动往往不是直接编进内核而是以ko的形式交付。这时候你面对的问题很现实——ko是有了但OpenHarmony标准系统的镜像分区里没有一个现成的“模块存放区”只能自己想办法把ko打包进固件。而chip_ckm分区就是干这个用的。很多刚接触OpenHarmony的开发者第一次看到这个分区名都愣一下不知道它是干嘛的更不知道ko怎么放进去、放进去之后系统能不能自动加载。这篇文章就把整条链路完整讲透从为什么要用chip_ckm分区到内核怎么编译、ko怎么写怎么编、分区镜像怎么生成、最终怎么烧录并在系统启动时自动加载。我踩过的坑都会标出来命令和配置文件直接给可复用的版本对着做基本能一次跑通。1. 项目分析与整体思路1.1 chip_ckm分区到底是什么chip_ckm 是 OpenHarmony 标准系统在部分开发板和产品上专门划分出来的一个分区全称可以理解为“chip common kernel modules”的缩写场景主要存放芯片厂商相关的内核模块、固件、配置片段等。这个分区不是所有产品都有但在瑞芯微、HiSilicon 等方案的OpenHarmony适配工程里出镜率很高。这个分区在系统中的挂载位置不同产品略有差异。常见做法是挂载到/vendor/chip_ckm或根目录下的/chip_ckm具体看产品配置里的 mount 节点定义。分区格式一般是 ext4 或 erofs前者方便调试阶段反复读写后者适合量产镜像做只读保护。我从实际开发的角度理解这个分区的价值它把“芯片厂商私有内容”和“系统公共内容”做了隔离。系统升级时如果OTA包只更新系统分区芯片相关的驱动模块可以不动反过来芯片厂商更新驱动固件时也不需要重新刷整个系统。1.2 为什么ko要打包进chip_ckm分区而不是直接编进内核这个问题几乎每个做适配的人都会问一遍。确实把驱动直接编进内核启动时自动加载不用管分区、不用管加载顺序看起来更省事。但实际项目里ko模块化是更常见的选择。主要原因有三点第一芯片厂商的驱动发布节奏和系统版本节奏不同步。开源鸿蒙的内核版本在升级驱动源码可能还停留在某个旧版本上硬编进内核容易因为API变更导致编译失败。ko方式下驱动跟着芯片厂商的发布节奏走系统升级时不需要重新编译驱动。第二二进制交付场景。很多芯片厂商出于源码保密或支持成本的考虑只提供编译好的ko不给源码。这种前提下你根本无法把驱动编进内核只能模块化加载。第三调试效率。在内核里加驱动代码每次都要完整编译内核、生成boot镜像、烧录一个来回至少半小时。ko方式下改完驱动只需要重新编一个ko文件推送到设备上insmod几秒钟就能验证一轮。做驱动调试的都懂这个效率差距意味着什么。1.3 整体编译到打包的链路拆解这一步先建立全局认知后面才不会迷路。完整的流程可以拆成四个阶段准备阶段拉取OpenHarmony标准系统源码和对应内核源码搭好编译环境确认工具链clang/llvm或gcc交叉编译工具链。编译内核阶段完整编译一次内核拿到内核镜像Image、设备树dtb和最重要的 Module.symvers 文件。这个文件在编译ko时是必需依赖它记录了内核导出的符号CRC值。编写并编译ko阶段写驱动源码和Makefile指定内核目录进行交叉编译产出目标ko文件。打包烧录阶段把ko放到工程指定目录通过编译系统生成包含ko的 chip_ckm.img或者手工制作分区镜像再用fastboot烧录到设备。最后通过init脚本或HDF框架实现ko的自动加载。整个链路里最容易出问题的其实不是编译而是打包和加载。编译ko的机制和标准Linux内核模块编译基本一致但OpenHarmony的分区打包有自己的一套配置逻辑很多人在这一步卡住。2. 环境准备与内核源码编译2.1 源码版本和工具链选择OpenHarmony标准系统目前常用的发布版本有3.2、4.0、4.1等。不同版本对应的内核源码路径略有差异但大逻辑一致内核通常放在kernel/linux/linux-5.10这个目录下具体内核版本随芯片平台走瑞芯微的方案大多是5.10内核。工具链选择上OpenHarmony官方推荐使用 clang/llvm 工具链尤其是通过 hb 或 build.sh 构建时默认走的就是 llvm。但如果你有丰富的嵌入式Linux交叉编译经验直接用 gcc 交叉工具链编ko通常也没问题。我自己测试下来只要内核在编译时用的是哪套工具链编ko时尽量保持一致否则很容易出现符号校验失败的问题。如果你走OpenHarmony官方构建流程需要用hb工具。在源码根目录执行hb set hb build -f这条命令会全量构建产品镜像。构建完的内核产物在out/{产品名}/packages/phone/相关目录下。但实际做驱动开发时反复全量构建太费时间我更推荐进入内核目录单独编译。2.2 完整编译内核拿到Module.symvers这一步是ko能否编译成功的关键前导工程。ko本质上是一个“半成品”内核模块它引用了内核导出的大量符号但这些符号的真实地址和校验信息都记录在内核的 Module.symvers 里。没有这个文件ko编译时会报一堆 undefined symbol 或者 CRC 校验失败。进入内核目录cd kernel/linux/linux-5.10先确认当前内核配置。OpenHarmony各产品的内核config文件通常在arch/arm64/configs/下比如rk3568_standard_defconfig。加载配置后编译export PATH$PATH:/path/to/your/toolchain/bin make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- rk3568_standard_defconfig make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)如果你用的是llvm工具链则把 CROSS_COMPILE 部分换成对应的clang调用方式。编译过程要等一段时间但不需要完整编完所有东西——理论上只要生成 vmlinux、Module.symvers 和头文件相关的 build 产物就可以支撑后续的ko编译。不过稳妥起见我建议完整编译一次确保内核配置没有遗漏也为后面排查问题提供参考基准。编译完成后在内核源码根目录检查ls -l vmlinux Module.symversModule.symvers 存在且非空说明内核符号表已经生成。另外还要确认arch/arm64/boot/下生成了 Image 或 Image.gz 以及 dtb 文件。2.3 内核编译阶段容易忽略的细节第一编译ko用的内核目录必须是编译过的源码树。很多人图省事直接把开发板对应的内核源码包拉下来就编ko但那只是一个干净源码目录没有经过完整编译。这种情况下 Module.symvers 不存在编译ko时要么失败要么生成的ko在加载时因为符号CRC不匹配直接被拒。第二注意内核配置里是否开启了模块支持。检查内核configgrep CONFIG_MODULES .config必须保证CONFIG_MODULESy。同时确认CONFIG_MODULE_UNLOADy否则后面调试时ko无法卸载每次改完都要重启设备效率很低。第三如果你在OpenHarmony工程里同时改过内核代码记得先重新编译内核再编ko。内核代码的变动会影响导出符号的CRC值而ko是依赖这个CRC的。跳过内核编译直接编ko大概率会编出一个加载时报“disagrees about version of symbol”的废文件。3. 编写并编译ko模块3.1 一个最小ko驱动示例先从一个最简单的驱动开始验证工具链和编译链路是否通顺。创建一个目录比如my_ckm_drv/里面放两个文件。hello_ckm.c#include linux/init.h #include linux/module.h #include linux/kernel.h static int __init hello_ckm_init(void) { printk(KERN_INFO hello_ckm: module loaded into chip_ckm\n); return 0; } static void __exit hello_ckm_exit(void) { printk(KERN_INFO hello_ckm: module unloaded\n); } module_init(hello_ckm_init); module_exit(hello_ckm_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(OpenHarmony chip_ckm test driver);这个驱动不操作任何硬件只在内核日志里打印一行字用来验证insmod和rmmod流程。3.2 Makefile的两种写法写法一独立目录编译适合驱动源码在OpenHarmony工程之外、独立维护的场景# Makefile KDIR : /path/to/kernel/linux/linux-5.10 ARCH : arm64 CROSS_COMPILE : aarch64-linux-gnu- obj-m : hello_ckm.o all: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) clean执行make后目录下生成hello_ckm.ko。写法二HDF驱动源码树内编译。OpenHarmony的HDF框架驱动通常放在drivers/hdf_core/adapter/或设备相关目录下通过BUILD.gn声明编译。gn写法大致是import(//drivers/hdf_core/adapter/khdf/linux/hdf.gni) hdf_driver(hello_ckm) { sources [ hello_ckm.c ] }这种方式的优势是驱动可以纳入OpenHarmony统一构建体系能通过HDF框架管理加载生命周期。但如果你只是临时验证一个芯片厂商给的ko第一种独立的Makefile方式更轻量。3.3 两种源码形态的对比芯片厂商交付驱动源码时主要有两种形态处理方式不同。第一类是独立Linux模块。源码自带Makefile用内核KDIR做外部模块编译生成独立的ko。这类驱动通常不依赖HDF加载方式就是传统的insmod或modprobe。处理起来最省事编译和打包完全是标准流程。第二类是HDF驱动。源码里包含 HdfDriverEntry 结构体通过 HDF 框架注册和初始化。这类驱动虽然最终也是生成ko但它的加载是由HDF框架调度的需要在ko里实现Init、Release等标准接口。另外HDF驱动的设备描述文件比如hdf_config也要一起打包进系统否则框架匹配不到设备节点。实际项目中很多芯片厂商的Wi-Fi驱动第一版交付时是独立模块后续版本慢慢迁移到HDF框架。两种情况都要能接得住。3.4 编译结果的验证方法编译完成后不要急着打包先在宿主机上做两个检查。检查一确认ko架构正确file hello_ckm.ko输出里应该包含ARM aarch64字样。如果出现x86-64说明交叉编译工具链没生效大概率是Makefile里 ARCH 或 CROSS_COMPILE 变量传递出了问题。检查二查看模块依赖和符号信息arm-none-linux-gnueabihf-readelf -d hello_ckm.ko或者用交叉工具链里的modinfo查看模块信息。重点确认ko里引用的内核符号是否都存在于 Module.symvers 中。可以用nm查看未定义符号aarch64-linux-gnu-nm -u hello_ckm.ko如果列出的符号是内核导出的标准符号比如printk、module_init没问题如果出现一些奇怪的自定义符号多半是驱动依赖了某个未编译进内核的模块的导出符号这种依赖关系后面加载时要特别处理。4. 打包到chip_ckm分区的完整实操4.1 ko文件在工程里的放置位置打包ko进chip_ckm分区首先要想清楚ko在源码工程里放哪个目录。OpenHarmony构建系统的逻辑是最终打包进哪个分区取决于文件放在哪个目录以及产品配置里的文件系统映射关系。我推荐的做法是在 vendor 目录下为产品或芯片方案单独建一个模块目录例如vendor/你的厂商/你的产品/chip_ckm/ ├── build.gn ├── hello_ckm.ko └── etc/ └── hello_ckm.cfg这里的build.gn负责把ko文件拷贝到目标分区的镜像目录里。写法示例import(//build/ohos.gni) ohos_copy(chip_ckm_files) { sources [ hello_ckm.ko, ] install_images [ chip_ckm ] relative_install_dir modules part_name chip_ckm }install_images指定最终打入哪个分区镜像relative_install_dir指定在分区内的子目录。这样生成的ko在分区里的路径就是/chip_ckm/modules/hello_ckm.ko。4.2 产品配置中确认chip_ckm分区存在并挂载如果产品原生的OpenHarmony工程里已经有 chip_ckm 分区那直接在上面操作即可。但有些产品工程的默认分区表里没有这个分区需要自己加。这时要检查三个地方。第一个是分区表配置。通常在device/board/{厂商}/{板型}/config/目录下有一个分区表或json配置。需要确认里面包含 chip_ckm 对应的分区项包括起始地址、大小、格式。第二个是挂载配置。在fstab或产品mount配置里定义 chip_ckm 分区挂载到哪个目录。例如/dev/block/by-name/chip_ckm /chip_ckm ext4 rw,nosuid,nodev,noatime wait第三个是镜像打包大小。分区镜像的实际大小不能超过分区表里定义的大小。在 build 配置里会有对应分区镜像的大小选项通常默认给一个保守的值比如 chip_ckm 分区 32MB 或 64MB。如果放进去的ko比较多需要手动调大。4.3 通过编译系统生成chip_ckm.img在工程根目录执行hb build -f --product-name 你的产品名编译完成后chip_ckm 分区镜像一般生成在out/你的产品/packages/phone/images/chip_ckm.img如果打包配置正确可以用下面命令查看镜像里是否包含ko文件。ext4镜像用 debugfs 查看debugfs -R ls -l /modules chip_ckm.imgerofs镜像可以用dump.erofs工具查看。确认ko在镜像里之后再刷机。如果不想全量编译也可以只打包chip_ckm分区hb build chip_ckm4.4 手工制作chip_ckm.img的方法调试阶段我经常不走完整编译流程直接手工制作分区镜像速度快很多。先把ko文件放到一个临时目录保持目标分区内的目录结构mkdir -p tmp_chip_ckm/modules cp hello_ckm.ko tmp_chip_ckm/modules/然后制作ext4镜像mkfs.ext4 -b 4096 -d tmp_chip_ckm chip_ckm.img 32768最后面的32768是块数量对应32MB大小。格式化后还可以用e2fsck和debugfs验证镜像内容。烧录时用fastbootfastboot flash chip_ckm chip_ckm.img注意手工制作镜像绕过编译系统适合调试不适合量产。量产的镜像必须走编译系统保证每个分区的属性、SELinux上下文、文件owner和权限都对。4.5 启动时自动加载koko放进分区只是第一步要让系统启动时自动加载它还需要配置。根据驱动的类型有两种做法。传统Linux模块可以在init脚本里加insmod命令。OpenHarmony标准系统的init脚本是init.cfg在对应产品的device/board目录下。找到init配置在启动阶段添加{ name: insmod_hello_ckm, cmds: [ insmod /chip_ckm/modules/hello_ckm.ko ] }如果是HDF驱动则不需要手动insmod。HDF框架在启动时会根据驱动配置自动加载ko。你需要确认驱动配置文件如hello_ckm.hdf在/chip_ckm/etc/或/vendor/etc/目录下并能被HDF框架解析到。补充一个关键细节如果你的ko依赖其他ko的符号比如hello_ckm.ko调用了base_drv.ko里导出的函数加载顺序就很重要。可以先加载被依赖的模块再加载依赖方。或者更省事的方式在ko所在目录下执行depmod -b /chip_ckm然后通过modprobe加载让模块依赖关系自动处理。5. 常见问题与排查心得5.1 insmod报Invalid module format这个错误我遇到指数最高绝大多数情况下是内核符号CRC校验失败。ko在编译时记录了它引用的内核符号的CRC值如果运行内核和编译ko时使用的内核不是同一个——哪怕是同版本但config不同——CRC就会对不上。排查思路先看dmesg尾部输出确认具体是哪个符号校验失败。然后确认ko编译时的KDIR路径是不是当前设备运行的镜像对应的内核源码。如果你是手工编ko最稳妥的办法是直接用设备镜像对应发布包里的内核目录和 Module.symvers。另外工具链不一致也可能导致这个问题。内核用clang编ko用gcc编部分场景下会出现ABI不兼容的问题。尽量保持一致。5.2 Exec format error这个报错通常不是格式问题而是架构问题。把x86下编出来的ko推到arm64设备上加载就是这个错误。大部分情况是Makefile里ARCHarm64和CROSS_COMPILE没传进去导致用了宿主机gcc编出了x86的ko。用file hello_ckm.ko看一眼就真相大白了。如果显示是x86-64就回去检查Makefile和编译命令。5.3 打包后ko文件丢失经常出现的情况是分区镜像里确实能看到ko刷机后系统里却没有。首先确认挂载是否成功执行mount | grep chip_ckm如果挂载失败检查fstab里的分区名和fastboot烧录的分区名是否一致。还有一种情况是系统启动时chip_ckm分区没能成功挂载但init脚本里已经尝试去读ko导致失败。建议在init脚本中把ko加载动作放在挂载动作之后或者在加载命令前加一条等待确认挂载的语句。另外OpenHarmony的乱序初始化会导致init脚本的某些服务执行时分区还没挂好。可以用wait chip_ckm之类的命令或者把insmod服务配置成依赖挂载服务。5.4 ko加载后驱动不生效能insmod成功驱动也注册了但对应的设备节点没有生成或者功能异常。这种情况先确认ko里的of_match_table或设备ID是否和实际硬件匹配。很多芯片厂商的ko在适配不同开发板时需要修改设备树里的compatible字符串。这个字符串匹配不上ko加载成功也不会绑定到硬件上。我在实际调试中就遇到过ko的author信息显示是同一家芯片厂商但板子换了设备树里compatible被OpenHarmony产品工程改掉了导致驱动probe不执行。这时候要么改ko里的匹配表有源码的前提下要么在设备树里改回芯片厂商默认的compatible。5.5 SELinux或安全上下文导致的加载失败OpenHarmony标准系统默认开启SELinux。如果SELinux策略不允许从 /chip_ckm 目录加载模块或访问相关文件insmod 会返回Operation not permitted而dmesg里能看到avc denied的日志。这种情况需要在SELinux策略里为 chip_ckm 分区目录添加对应的allow规则。OpenHarmony的SELinux策略文件在device/board/{厂商}/{板型}/security/或系统公共策略目录下需要补充类似下面的规则allow ckm_module_file self:file { read open execute }; allow ckm_module_file self:system { module_load };具体规则要看加载ko的进程domain是什么。调试期内可以先用setenforce 0临时关闭SELinux验证确认是策略问题后再补精确的allow规则。5.6 手工制作镜像后开机挂载报错手工mkfs.ext4制作镜像如果块大小、inode数量这些参数和系统预期不一致可能出现挂载后文件读到一半报I/O错误的情况。我建议用编译系统生成镜像或者严格跟随产品已有分区的mkfs参数。比如先建一个空白分区镜像用tune2fs -l查看批量打包生成的分区参数再手工制作时保持一致的参数。6. 操作经验补充分享在一次实际适配中我把一个触摸屏驱动ko打包进 chip_ckm 分区后遇到了一个很隐蔽的问题ko文件在分区里存在权限也是644但insmod时提示找不到文件。排查了半天发现是挂载分区时用了nodev选项虽然这个选项不影响模块加载但设备节点的创建受到限制而触摸屏驱动恰好依赖/dev/input/eventX节点的生成。去掉nodev选项后问题消失。这个案例说明ko打包进 chip_ckm 后不只是“放进去了”就完事挂载选项、init时序、SELinux上下文、设备树匹配这些周边因素一个都不能漏。另一个经验是关于分区大小的。如果产品默认的 chip_ckm 分区只有8MB或16MB放Wi-Fi驱动固件可能就够了但如果你放了多个芯片模块比如屏幕驱动、TP驱动、音频DSP固件加起来很快就满了。而且mkfs.ext4会有metadata开销实际可用空间比分区大小小不少。建议预留20%到30%的余量不然版本迭代后期加东西特别痛苦。最后说一个实践上比较顺手的组合调试阶段手工编译ko、手工打包镜像烧录确认没问题后再把ko和对应配置合入工程走编译系统出正式包。这样兼顾了迭代速度和交付可靠性。chip_ckm分区的价值在量产维护阶段会体现得更明显——芯片厂商的驱动更新只需要推送一个新的分区镜像不用整个系统OTA。