news 2026/9/28 9:21:24

OpenHarmony标准系统chip_ckm分区打包与内核ko编译适配指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHarmony标准系统chip_ckm分区打包与内核ko编译适配指南

做OpenHarmony标准系统的设备适配,kernel ko编译和分区打包几乎是每个BSP工程师都绕不开的日常操作。标题这句话压缩了整套流程:先绕过各种配置陷阱,把目标驱动编成可加载的内核模块(ko),再把它塞进chip_ckm分区镜像,最后烧到板子上验证insmod成功。这篇文章适合正在做OpenHarmony标准系统适配、ROM定制或者想搞清楚标准系统镜像分区机制的开发者参考,尤其适合那些已经接触过Linux内核编译、但被分区命名和构建链路整懵的朋友。

为什么一定要单独搞一个chip_ckm分区装ko,而不是像普通Linux发行版那样直接挂在根文件系统里?这跟标准系统的分区职责划分强相关。boot、system、vendor、chip_prod、chip_ckm各有各的定位,把它们搞清楚后,你就明白厂商定制的模块为什么要走这条稍微绕一点的编译打包路径。

1. 项目拆解:为什么要做一个独立的chip_ckm分区镜像

1.1 标准系统的分区职责划分

OpenHarmony标准系统(也就是常说的Full系统,面向带屏幕的富设备)在镜像层面的规划,跟传统Linux发行版差别挺大。拿到一套目标板的刷机包,通常能看到system.img、vendor.img、chip_prod.img、chip_ckm.img、loader.img这些主要镜像。除了system和userdata这种常规分区,最让从Linux BSP转过来的人困惑的,就是chip_prod和chip_ckm这两个分区的边界到底在哪。

从命名和实际职责来看,chip_prod更偏向芯片厂商的生产数据区,放厂商私有配置、专属可执行文件、产品级定制内容;chip_ckm更像芯片通用内核模块区,承载内核模块(ko)、固件(firmware),以及一些跟硬件强相关的二进制文件。实际适配某款SoC时,GPU驱动、WiFi/BT固件、相机相关模块、编解码器这些厂商闭源或改动频繁的部分,都会选择以ko形态放进来,而不是编进zImage。

这种隔离设计的第一层好处是职责单一:系统升级时,chip_ckm可以单独做分区升级,不用动system,也不用整个重新烧boot。第二层好处是内核镜像保持相对稳定,核心内核和厂商驱动解耦后,驱动模块有bug可以直接改ko替换,省掉整包刷机的痛苦。第三层好处是分区内容可以按芯片系列复用,同一款内核在不同板卡上跑,只要替换对应chip_ckm镜像就行。

1.2 内核模块独立分区的几个现实收益

把ko打成独立分区映像,不只是架构上的洁癖,实际收益非常明显。我做过一次对比,驱动以模块方式放chip_ckm后,版本迭代时只需要重编ko、重新打包分区,升级包体积从几百MB压到几十MB,而且现场设备通过升级通道就能完成驱动热更,不需要专业人员连接调试串口重刷boot。

另一个容易被忽略的收益是调试效率。驱动还在开发期时,经常要改参数、加打印、换firmware版本。如果驱动编在内核里,每次改动都要重新编译完整内核镜像,boot分区还得重新打包,一次至少十分钟。把模块独立出来就不一样,ko编译通常几十秒搞定,重新打包chip_ckm镜像也就一两分钟,烧录也快,这种迭代速度对驱动的bring-up阶段非常重要。

还有一点是安全问题。模块以独立文件存在,可以单独做strip、签名、权限控制,甚至可以在发布前用工具检查符号表泄漏。编进内核后,所有符号都在/proc/kallsyms里露着,想隐藏一些厂商私有实现逻辑非常难。所以从商用产品角度看,ko独立分区也是保护知识产权的一种手段。

2. 编译ko的前置准备与内核配置

2.1 内核源码和交叉工具链怎么选最稳

编译ko最关键的一条原则:编译模块时使用的内核源码版本、配置和工具链,必须与运行目标板上那个内核完全一致。这不是官方文档里随便写的建议,而是血的教训。内核模块和内核之间不是简单的"能编过就行",符号版本CRC校验机制会严格比对模块编译时记录的符号CRC值和运行内核实际符号的CRC值,任何一个不一致,insmod直接报Unknown symbol。

OpenHarmony标准系统的内核源码一般在kernel/linux/linux-5.10目录下,具体版本号以仓库里的kernelrelease文件为准。拿到源码后先确认两件事:一是厂商的BSP patch是否已经合入,二是你手上这份源码是否就是最终烧录进设备的那份。很多人图省事,从网上下一个同版本主线内核就开始编ko,编译能过,但加载必挂,因为厂商patch里的新符号和配置改动都没了。

工具链方面,OpenHarmony标准系统构建链路默认偏向clang,但不少厂商的BSP实际会切到gcc。我个人的经验是:编ko时尽量复用编译内核时的同一套交叉编译工具链,不要混搭。用gcc编的内核模块在clang编的内核上加载,虽然大部分情况没问题,但遇到内联函数、编译器内建符号相关的场景会出现诡异的符号找不到问题。在标准系统工程里,可以先执行./build.sh --product-name 你的产品名 --ccache false跑一次完整构建,从构建日志里找到内核实际使用的编译器路径,然后固定下来。

2.2 内核配置中必须打开的模块相关选项

编译ko之前,内核本身的模块支持必须确认开启。进入内核源码目录后,先加载目标板对应的defconfig,再执行make menuconfig检查几个关键选项。首先要确保CONFIG_MODULES=y,这个不开启,后面编译模块阶段直接报"module support disabled"。其次建议打开CONFIG_MODULE_UNLOAD=y,这样调试阶段反复insmod/rmmod会方便很多。CONFIG_MODVERSIONS这个选项需要重点说明,开启后内核模块会记录每个引用符号的CRC值,运行内核加载模块时做一致性检查。

很多从传统Android BSP转过来的人习惯把这个选项关掉,因为Android内核为了加载预编译模块经常关掉。但在OpenHarmony标准系统上,我建议默认保持开启,尤其是在多模块互相依赖的场景里。开启MODVERSIONS后,模块A依赖模块B导出的符号,如果模块B升级时改了某个函数签名或结构体布局,加载模块A时会立刻报CRC不一致,从源头拦截了潜在的崩溃风险。排查问题的时候,错误信息也比"no symbol version for xxx"清晰得多。

另外要关注CONFIG_MODULE_SIG这个选项。如果目标板的内核开启了模块签名校验,而你手头的ko没有用对应私钥签名,insmod时会报"module verification failed: signature and/or required key missing"。厂商的release内核经常打开这个,但调试版可能关闭。选购或者搭建编译环境前,先用目标板上的内核跑一次zcat /proc/config.gz | grep CONFIG_MODULE_SIG确认状态,避免编了半天模块根本加载不进去。

2.3 树内驱动和树外驱动两种编译方式

ko来源主要分两种:树内驱动和树外驱动。树内驱动是指驱动源码已经放进内核源码树的drivers目录下,比如drivers/gpu/drm/xxx、drivers/net/wireless/xxx,这类驱动在编译内核时,如果对应Kconfig选项设置成<M>,就会自动产出ko文件。编译完成后,在drivers对应目录或net/xxx等目录下能找到.ko文件。

树外驱动是指厂商单独维护一套驱动源码,不在内核树内,例如某些第三方GPU、DSP、安全引擎的驱动。这类模块需要在内核源码树下,用make M=方式单独编译,命令大致是make ARCH=arm64 CROSS_COMPILE=xxx M=/path/to/driver_src modules。编译过程中会调用内核源码树里的modpost工具做符号校验,所以源码树里必须已经完成过一次make modules_prepare。如果跳过这个步骤直接编外部模块,经常报一堆找不到Generated符号的错。

两种方式没有绝对好坏,树内驱动跟随内核版本演进,维护起来省心;树外驱动跟内核源码树解耦,厂商可以独立发版。从打包到chip_ckm的角度,两者最终产物都是ko文件,后续处理流程完全一样,只是树外驱动需要你在编译脚本里显式指定模块路径,容易在交叉工具链上翻车。

3. 内核ko编译实操流程

3.1 先编出一套可用的内核基础产物

正式开始编译前,先把运行环境理清楚。我习惯把交叉工具链路径、架构变量全部export到当前shell,避免每条命令都带一堆前缀。架构统一设成arm64,工具链按厂商提供的GCC路径填写。假设已经进入内核源码目录,第一个动作是清理脏数据并加载目标板的defconfig。

export ARCH=arm64 export CROSS_COMPILE=/opt/toolchains/aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu- make distclean make <product>_defconfig

不同芯片厂商的defconfig名字不一样,有的叫ohos_xxx_defconfig,有的直接沿用Linux主线风格。加载成功后,用make menuconfig手动检查一下需要模块化的驱动是否已经配置成<M>。这一步是标准系统适配最常见的分叉点:驱动若配置成<Y>,会编进zImage而不是生成ko,后续想打包到chip_ckm就无从谈起。

配置确认结束后,直接编译完整内核。这里不建议只编modules不编Image,因为有些根设备依赖和固件生成规则,只有执行完整编译才会触发。完整编译也方便后续用编译产物里的System.map、Module.symvers做模块依赖解析。

make -j$(nproc)

编完后确认Image/zImage和System.map存在。这时内核树内配置成<M>的驱动已经产出ko,散落在各个drivers子目录里。下一步是把它们集中收集统一管理。

3.2 用modules_install整理标准模块目录

散落的ko文件直接拷走虽然也能用,但insmod时若存在模块间依赖,或者想让modprobe按名字加载,就必须维持Linux标准的模块目录结构。Linux模块的规范路径是/lib/modules/$(uname -r)/,下面还有kernel、extra等子目录。内核的Makefile提供了modules_install目标,能自动把当前内核树内所有ko安装到指定rootfs路径下,同时生成软链接和符号表信息。

export INSTALL_MOD_PATH=/tmp/chip_ckm_rootfs make modules_install

执行后,/tmp/chip_ckm_rootfs/lib/modules/5.10.xxx/目录下会出现一整套模块树。这里有个关键点,INSTALL_MOD_PATH的目标目录不能是普通空目录,最好先按照最终分区内容的目录结构规划好,否则后面还得重新搬运。模块安装过程中会自动生成modules.order和modules.builtin这些辅助文件,虽然加载时不是必需,但建议保留。

如果还需要打包厂商的树外驱动ko,就把编译好的外部ko手动拷贝到kernel目录下合适的子目录。我一般习惯放在lib/modules/$(uname -r)/extra/下,这是Linux为第三方模块预留的标准位置。拷贝完先跑depmod生成模块依赖关系。

depmod -b /tmp/chip_ckm_rootfs 5.10.xxx

depmod的-b参数指定根文件系统所在路径,后面跟的版本号必须和模块目录名一致。这个命令会生成modules.dep、modules.alias、modules.symbols等文件,它们的作用是让modprobe知道某个模块放在哪个文件、依赖哪些其他模块、某个符号由哪个模块提供。很多新手打包模块时漏掉这一步,结果设备上执行modprobe时总是提示找不到模块。

3.3 用strip控制ko体积和符号暴露

ko文件编译出来后,默认包含大量调试符号和注释段,体积偏大,如果chip_ckm分区很小,几兆的镜像可能直接放不下。在正式打包前,用交叉工具链里的strip工具做一次裁剪是标准操作。

aarch64-none-linux-gnu-strip --strip-unneeded xxx.ko

--strip-unneeded只移除调试信息和不需要的符号,保留模块加载和导出符号所需的部分,不会破坏ko的可用性。做完后对多模块场景特别有用,比如一个内核目录几百兆,裁剪后可能降到几十兆。不过这里有个权衡:裁剪掉调试符号后,驱动crash时无法从ko里得到符号化调用栈,只能靠内核日志里的PC指针加System.map手撸。调试阶段建议保留符号,发布版本再裁剪。

还有一个经常被忽略的细节:strip工具和目标板内核架构必须一致,用x86主机上的strip去处理arm64的ko会直接报错或者破坏文件格式,这类问题排查起来非常浪费生命。

4. 打包进chip_ckm分区的完整操作

4.1 规划chip_ckm的目录结构

chip_ckm分区虽然不是传统的根文件系统,但内部目录结构依然遵循Linux常规布局。最简方案下,分区根目录只需要lib/modules/$(uname -r)/这棵模块树。但实际产品通常会多放几样东西:一是驱动固件文件,比如WiFi的nvram、GPU的固件包,这些必须放在/lib/firmware/或驱动源码指定的路径下;二是厂商的脚本,比如负责按顺序加载模块的insmod脚本;三是芯片相关的配置文件,比如电源管理参数、产品型号校准数据。

目录结构规划时要想清楚一个前提:chip_ckm分区在设备上会挂载到哪个路径。OpenHarmony标准系统里,各分区的挂载点由init配置决定,不同厂商的产品可能挂在/vendor/chip_ckm,也可能挂在/chip_ckm。不是所有驱动都硬编码了从根路径找模块,insmod脚本里尽量不要写死绝对路径。我之前踩过一次坑,脚本写死insmod /vendor/chip_ckm/lib/modules/xxx.ko,结果换了另一套产品配置挂载点变成/chip_ckm,开机直接黑屏。

一个稳妥做法:在打包脚本里用一个变量统一定义挂载根路径,未来挂载点调整只需改一处。模块目录本身用相对路径组织,insmod脚本运行时先cd到模块目录再load,这样对挂载路径的依赖降到最低。

4.2 手动制作镜像并打fspad头

目录内容准备好后,进入分区镜像制作环节。OpenHarmony标准系统的分区镜像和普通Linux烧录镜像有点区别,boot/system多数有特定镜像格式要求,chip_ckm通常以ext4镜像加头部信息的方式打包。构建系统的完整流程比较复杂,我这里给出一个可手动复现的思路,便于调试阶段快速迭代。

先把整理好的整个chip_ckm_rootfs目录制作成ext4镜像:

make_ext4fs -l 64M -s rootfs.img /tmp/chip_ckm_rootfs

-l指定镜像大小,需要和板级分区表中chip_ckm的size匹配,-s表示精简镜像,大小会按实际内容收缩。如果本机没有make_ext4fs,可以用新版构建系统里自带的mkfs.ext4替代,但注意部分工具生成的镜像不支持稀疏特性,烧录后文件系统挂载会报错。

ext4镜像做完后,还需要加上标准系统要求的fspad(fastboot sparse padding)首部。具体执行哪个工具,不同版本和厂商封装不一样,常见的是mkimage.fspad。一条典型的打包命令如下:

mkimage.fspad -s 4096 -o chip_ckm.img rootfs.img

这一步的4096是与扇区对齐相关的参数,多数平台都是4K对齐。如果你的板级构建脚本里已经有chip_ckm的打包规则,直接复用脚本里的工具和参数,不要自己瞎猜。最稳的办法还是打开一次完整构建日志,摘出构建系统实际使用的打包命令,手动迭代时照着批量替换目录和镜像名就行。

4.3 挂进分区表和解决容量不够的调整方案

chip_ckm.img制作完成后,烧录前还要确认分区表里chip_ckm的size和偏移没有冲突。如果镜像大小超过分区size,fastboot烧写会写越界,轻则chip_ckm挂载失败,重则破坏相邻分区。标准系统的分区表配置一般在板级工程的配置文件中,常见的名字有partition.xml、partition_ns.json,具体以你手上的工程为准。

调整分区大小的操作套路是:先在分区表配置里把chip_ckm的size字段改大,比如从32M改成64M,然后确认前后相邻分区的偏移值同步调整。这里特别提醒,不要单独改chip_ckm一个分区的大小,却忽略相邻分区的start_addr,否则整个烧录链路会直接错乱。修改分区表后,需要重新打包完整烧录镜像,因为loader里嵌入了分区表信息,只换chip_ckm.img是不够的。

如果只是ko太大,还有一种不需要改分区表的手法:对ko排序裁剪,削掉内置的debug段,或者把不常用的模块拆出来放到vendor分区。但从职责划分看,这属于临时妥协,后续模块持续增大还是要调整分区表。建议在首次规划产品时,就把chip_ckm的size预留到驱动包实际体积的1.5倍以上,免得后面反复调表。

4.4 开机自动加载ko的初始化手段

分区烧进去只是第一步,模块要真正生效,还得让系统启动时自动加载。标准系统的init框架兼容类似Android的rc语法,可以在产品init配置文件里增加一个oneshot服务,启动时执行加载脚本。

以最常见的insmod方案为例:写一个shell脚本,按依赖顺序逐个insmod,然后在init rc文件里增加服务声明。顺序上建议放post-fs阶段之后,因为模块依赖的某些设备节点或字符设备,要等内核基础设备初始化完成后才存在。脚本内容大致如下:

#!/system/bin/sh MODDIR=$(dirname $0)/lib/modules/$(uname -r) insmod $MODDIR/kernel/drivers/xxx/base.ko insmod $MODDIR/kernel/drivers/xxx/device.ko sleep 0.2 # 等待固件就绪 insmod $MODDIR/extra/vendor_driver.ko

注意最后一行前面为什么要sleep 0.2。有些驱动probe时会向固件请求接口,而固件加载是异步进行的,立刻insmod有可能因为firmware还没就绪而上报EPROBE_DEFER错误。加一个短暂等待就能把这个窗口绕过去,比在驱动里死等要省事得多。

如果模块依赖关系比较复杂,更推荐用modprobe配合depmod生成的modules.dep来加载。modprobe会自动解析依赖并按序加载,只是对rootfs的完整性要求更高,modules.dep和modules.alias不能丢。两种方式我都用过,调试阶段用insmod加显式顺序更可控;发布版本用modprobe更健壮。

5. 烧录验证与常见问题排查

5.1 烧录验证三板斧

chip_ckm.img准备好后,烧录验证流程并不复杂。板子进入fastboot模式后,执行分区烧写:

fastboot flash chip_ckm chip_ckm.img fastboot reboot

如果用的是升级包OTA通道,按你产品的升级工具走就行,但开发阶段还是fastboot最直接。重启后,先确认分区挂载正常,再检查模块是否被系统加载。三个关键排查命令:

mount | grep chip_ckm lsmod dmesg | grep -iE "chip_ckm|firmware|xxx_driver"

lsmod看不到模块,看dmesg;dmesg没报错但模块没加载,查init脚本有没有执行;脚本执行了但modprobe找不到模块,查depmod产物和uname -r的版本号是否一致。这套排查顺序基本能覆盖90%的问题。

5.2 常见问题速查表

我在chip_ckm分区适配里踩过的坑不少,整理成一张速查表,按出现概率排序:

现象大概率原因解决思路
insmod报Exec format error架构不匹配或内核版本配置不一致确认ko用arm64交叉工具链编译,编译内核源码与设备内核源码一致
insmod报Unknown symbol依赖模块未先加载,或MODVERSIONS CRC不一致先加载依赖模块,确认模块源码和内核源码同步
insmod报module verification failed内核开启了模块签名验证关闭CONFIG_MODULE_SIG或用私有钥签名ko
modprobe找不到模块depmod未执行或模块目录与uname -r不符重新跑depmod -b,检查模块目录名和kernelrelease
镜像烧录后分区挂载失败镜像大小超过分区表size或fspad头不对检查分区表,重新用构建系统的打包工具生成镜像
驱动probe失败但模块已加载firmware文件缺失或路径不对确认firmware放在/lib/firmware指定路径,检查dmesg固件加载报错
模块加载后设备节点不出现设备树里对应节点状态disabled检查设备树status和compatible配置
strip后ko加载crashstrip破坏了必要符号用--strip-unneeded而不是--strip-all

表里第三条值得多说两句,厂商发布的内核镜像如果开了强制模块签名,modprobe会直接拒绝加载未签名模块,这是很多自制ko加载失败的头号原因。不要试图硬破解,找厂商要签名工具链或者让开发版关闭签名校验才是正道。

5.3 我的几个避坑心得

整个流程走下来,我最想提醒后来者的不是某个命令怎么写,而是流程意识。编译ko、打包chip_ckm、烧录验证这三个环节,每一环的错误都会在下一环被放大。编译时忽略版本一致性,打包阶段看不出来,烧录后insmod才爆雷;打包时漏了depmod,分区内容也看起来完整,直到modprobe才失败。所以建议从一开始就建立一个自动化打包脚本,把内核版本抓取、模块整理、depmod、strip、镜像制作整合成一条流水线,每次改动驱动后一条命令完成从源码到镜像的构建。

另外,调试阶段不建议使用发行版那种裁剪过的模块,保留调试符号至少能让你在crash日志里看到驱动内部函数名。发布前再统一做strip优化体积,这样既保证开发效率,也保证最终产物干净。

最后分享一个小细节:每次编译前用make clean之前,记得备份Module.symvers文件。这个文件记录了所有已编译模块导出的符号CRC值,树外模块编译时会读取它做符号校验。如果丢了它,外部模块经常报出一堆missing symbol的错,而且这类错误非常误导排查方向。备份一份也就几百KB,却能让下一次编译流程顺畅不少。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 9:21:23

中线交易系统实战:信号布局、加码持股与分批卖出全攻略

做交易这些年&#xff0c;我最深的体会是&#xff1a;大多数人亏钱&#xff0c;不是不懂技术&#xff0c;而是没有一个贯穿始终的执行框架。你可能也遇到过这种情况——某个均线金叉出现了&#xff0c;不敢买&#xff1b;涨了一截&#xff0c;忍不住追进去&#xff1b;追进去之…

作者头像 李华
网站建设 2026/9/28 9:21:12

跨境电商售后配件管理:从成本拆解到供应链方案与实操流程

做了五年多跨境售后运营&#xff0c;我最大的体会是&#xff1a;真正吃掉利润的往往不是产品成本&#xff0c;而是售后环节里那些看不见的配件损耗、反复空运补发的运费、海外仓里积压到过期的备件库存。很多卖家把精力全砸在选品和广告上&#xff0c;直到毛利率被售后成本拖到…

作者头像 李华
网站建设 2026/9/28 9:20:30

ROS快速入门基础教程:环境安装、工作空间与turtlesim通信实验

ROS 可以说是目前机器人领域绕不开的一套基础设施。不管你是做机械臂、做小车、做SLAM导航&#xff0c;还是做室内服务机器人&#xff0c;只要想真正上手机器人开发&#xff0c;几乎都要跟它打交道。这篇ROS快速入门基础使用教程&#xff08;1&#xff09;&#xff0c;我会从安…

作者头像 李华
网站建设 2026/9/28 9:19:08

NSGA-III与Matlab实现:梯级水火联合多目标调度全流程解析

“NSGA-III”和“Matlab”放在同一个项目里时&#xff0c;通常意味着一个典型的多目标优化落地问题。我最近把基于NSGA-III优化算法的梯级水电和火电机组联合多目标调度完整跑了一遍&#xff0c;整个项目用Matlab实现了从建模、算法设计到结果分析的全部流程。这个事情的吸引力…

作者头像 李华
网站建设 2026/9/28 9:18:56

Doris查询缓存调优实战:从重复查询烧掉集群到命中率95%

1. 重复查询是怎么把集群资源烧掉的&#xff1a;一个常见的性能事故场景接手公司数据平台性能优化之后&#xff0c;我做的第一件事不是调参数&#xff0c;而是拉审计日志。原因很简单&#xff1a;不看实际查询&#xff0c;永远不知道集群资源到底被什么吃掉了。当时线上跑的是一…

作者头像 李华
网站建设 2026/9/28 9:18:44

Stormworks微控组件全攻略:翻译对照与逻辑搭建实战

我玩Stormworks也有七八百个小时了&#xff0c;从最早瞎折腾气压传感器到现在用微控组件写逻辑&#xff0c;最大的门槛其实不是物理引擎有多真实&#xff0c;而是那个微控制器面板里密密麻麻的英文组件名。国内玩家圈里管这个叫“微控”&#xff0c;你要是没个四六级水平&#…

作者头像 李华