如果你已经照着系列第一篇把开发环境搭起来了,现在终于到了真正打开SDK包的时刻。这次要说清楚的,就是HI3556V200这套Linux+LiteOS双系统方案里的SDK,一共给了我们什么、为什么要这样规划、怎么把它编译运行起来。很多人刚接触这类芯片SDK时的第一反应是懵:目录多、层级深、编译脚本套编译脚本,尤其是带双系统的型号,平台目录下既有A53的Linux,又有M7的LiteOS,不把结构吃透很难动工。这篇就想从一个学习和落地的角度,把SDK的完整面貌拆开,把双系统之间的协作方式讲透,再把编译、部署、调试这条主链路过一遍,适合刚接触该系列芯片、准备做产品开发或学习异构双系统方案的人参考。
1. 拿到SDK包之后,先看懂顶层布局
1.1 双系统到底是什么方案
HI3556V200这颗芯片的特点是异构多核:一边是一个高算力的A53核,另一边是一个轻量实时核(M7),这两个核分别跑Linux和LiteOS,构成“Linux + LiteOS”的双系统方案。这样说可能还不够直观,我换一种描述方式:Linux负责所有重负载任务——网络协议栈、文件系统、视频编解码、AI推理、业务逻辑,这些东西生态成熟、开发效率高;而LiteOS负责对时间敏感的控制类任务,比如传感器触发、电机控制、外设实时响应,这些任务对确定性要求极高,Linux的调度延迟和中断延迟很难保证。
这种设计很像一个公司里既要有正式员工团队处理日常复杂业务,也要有值班人员随时响应急事。正式团队能力强但响应时间有波动,值班人员能力单一但反应迅速。芯片内部也按这个思路分工,A53相当于“正式团队”,M7内核就是“值班人员”。
对开发者来说,双系统方案带来的学习曲线确实比单Linux系统陡,但一旦理解了系统间的协作方式,后面再接触其他异构多核平台的SDK就会快很多。因为底层思路都是相通的:两个系统各自处理擅长的部分,再通过一套约定好的机制交换数据和事件。
1.2 SDK包目录逐层拆解
拿到SDK压缩包后,先别急着动手编译。我建议把顶层目录完整看一遍,再决定从哪一步开始。这类SDK虽然版本不同,但顶层布局通常是有规律可循的,一般会包含以下几个部分:
- docs:全量开发文档,包括芯片数据手册、双系统架构说明、MPP媒体平台API手册、烧写指南等。这个目录决定了你能走多稳,建议花半天时间先把《软件开发指南》通读一遍。
- scripts:环境配置脚本、打包脚本、烧写辅助脚本。很多环境问题其实是脚本没跑对导致的。
- tools:交叉编译工具链、烧写工具、调试工具、镜像制作工具。有些版本SDK会直接放工具链,有些需要自己下载,后面细说。
- smp:双系统平台源码,这是核心目录,里面通常分成两个大块:A53 Linux侧和M7 LiteOS侧。
- osal:操作系统抽象层,封装了不同系统间的公共接口,比如内存操作、锁、消息队列,方便上层代码跨系统复用。
在smp目录下,你会发现A53 Linux侧和M7 LiteOS侧的源码是分开的,这也是理解双系统SDK的关键。一个典型布局大致是这样的:
| 路径 | 职责 | 主要关注点 |
|---|---|---|
| smp/a53_linux/boot | u-boot源码 | 启动第一阶段,决定两个系统镜像如何加载 |
| smp/a53_linux/kernel | Linux内核源码 | 内核配置、设备树、驱动编译 |
| smp/a53_linux/rootfs | 根文件系统相关内容 | 启动后能看到的用户空间文件 |
| smp/a53_linux/mpp | 媒体处理平台及sample | 视频采集、编码、显示等业务demo |
| smp/m3_liteos/code | LiteOS内核及任务代码 | 小核实时控制逻辑 |
| smp/m3_liteos/drv | LiteOS侧外设驱动 | 小核专用外设、共享内存驱动 |
| smp/m3_liteos/pub | LiteOS编译产物 | 最终生成的bin文件 |
看到这个布局,你应该就明白了:所谓双系统SDK,本质上是把两套完整的软件栈放到同一个包里面,通过构建脚本统一组织,再配合原厂提供的启动方案让它们协同工作。这里我踩过的一个小坑是,有人拿到包后只看a53_linux,完全忽略m3_liteos那边的文档和代码,等到需要调试小核时就无从下手了。小核虽然“轻量”,但它的启动流程、驱动初始化、内存约束都有自己的一套逻辑,绝对不能当成附属品来看待。
2. 双系统的核心联系:M7 LiteOS与A53 Linux怎么协作
2.1 为什么不是只跑Linux
我在实际项目里被问过很多次:这颗芯片的A53性能已经够用了,为什么非要再搞一个LiteOS小核?这背后其实是实时性需求的硬约束。Linux作为通用操作系统,为了保证公平调度和复杂生态兼容,牺牲了时间确定性。即使打了实时补丁,中断响应和任务切换仍然存在微秒到十微秒级别的不确定性。对视频逐帧触发、对步进电机脉冲控制这类场景,这种不确定性会导致肉眼可见的卡顿或者干脆超出控制精度要求。
LiteOS跑在M7上之所以合适,是因为它本身是专为轻量嵌入式设计的实时内核,任务调度、中断响应路径短,行为可预期。再加上M7核是独立的小核,不跟A53争缓存和中断资源,两边各干各的,天然隔离。这也是异构双系统相对“单系统多核方案”的优势:不会因为大核上的偶发高负载影响小核的实时任务。
当然,多一个系统也不是没有代价。它意味着你需要维护两套代码、两套编译工具链、两套调试手段,还要规划好两个系统之间的通信和内存分配。所以双系统不是炫技,而是产品定义需要。你在决定“要不要启用小核”之前,先要问自己:当前业务里有没有对时间确定性要求很高的任务?如果没有,可能单Linux就够用了;如果有,再认真啃双系统方案。这个判断很重要,它会直接影响你要不要在小核上写代码。
2.2 双系统通信与内存协作机制
两个系统既然跑在同一颗芯片上,它们之间的协作就必须有一套通信机制。常见方案是“共享内存 + mailbox中断”,HI3556V200 SDK里也沿用了这种思路。
共享内存,就是一块物理内存,两个系统都能访问,A53往里面写数据,M7从里面读数据。但只有共享内存还不够,如果A53写完数据后M7不知道,M7就得一直轮询,既费电又增加延迟。所以还需要一个“门铃”。这个门铃就是mailbox,它本质上是一个带中断能力的寄存器,一边写入了消息之后触发一个硬件中断通知另一边来取。两边约定好:你写完共享内存就按一下门铃,我收到中断就知道数据来了,立刻来处理。
SDK里一般会把这套机制封装成接口。比如LiteOS侧有消息接收接口,Linux侧有对应的驱动节点,开发者的业务代码只需要调用接口,不需要直接操作寄存器。但有个地方要注意:两家系统访问的物理内存地址必须映射正确。A53 Linux侧看到的内存地址经过MMU映射,M7侧可能物理地址直接访问,两边如果对内存布局的理解不一致,就会出现“我写你看不到,你写我也不敢读”的问题。
这块内存怎么规划?通常会在uboot启动阶段固定分配:一部分DDR给Linux,一部分给LiteOS,再专门划出一块共享内存区域。这套布局文档里会有明确描述。我建议你在动手改业务代码前,把这张内存关系图画出来贴在工位旁边,上面标清楚哪个地址范围属于Linux、哪个属于LiteOS、共享缓存区在哪、mailbox寄存器在哪。画完这张图,很多问题都好定位了。
2.3 开发接口与SDK里的关联代码
SDK为了让开发者不纠结底层寄存器操作,通常会在两个系统侧各自提供一套封装好的接口。LiteOS侧会有类似消息收发、signal通知的函数;Linux侧会有对应的字符设备驱动或内核接口,应用层通过标准文件操作或者ioctl来收发消息。
第一次接触这类接口时,我建议不要先去看业务代码,而是先在SDK里找双系统通信相关的示例程序。这类SDK基本都会带几个小demo:LiteOS侧定时往共享内存里写数据,Linux侧读出来打印;或者Linux侧发一条控制命令,LiteOS侧收到后翻转一个GPIO、点亮一颗LED。这种demo看起来简单,但它把整个数据链路完整串通了:内存分配、mailbox中断触发、消息解析、回调执行。把这个demo跑通,就等于拿到了双系统通信的“最小可行样板”。
从代码角度讲,你可以在Linux侧源码里搜mailbox、mbox、remoteproc这类关键词,在LiteOS侧搜ipc、mailbox、message这类关键词。找到对应文件后,把发送路径和接收路径分别画出来,这在后面排查“消息丢失”“消息乱序”的时候特别有用。我自己在调试时曾经遇到一个诡异现象:Linux侧调用send接口返回成功,但LiteOS侧收不到。最后排查发现是共享内存缓存一致性问题——Linux侧CPU缓存了数据没有刷到物理内存,M7读的时候自然读到旧值。这类问题的排查思路完全依赖你对通信机制底层原理的理解,而不是查API文档就能解决。
3. 编译环境搭建与全量编译实操
3.1 工具链与编译前环境准备
编译双系统SDK前,先把环境准备好,这一步最容易被轻视。我见过不少人在环境没配好的情况下强行编译,结果报一堆看不懂的错误,然后怀疑SDK有问题。其实大部分编译问题都出在工具链和依赖库上。
先说宿主机,建议使用干净且较新的Ubuntu 64位系统,并保证有充足的磁盘空间和内存,双系统全量编译对配置有一定要求,老旧的虚拟机配置很容易接近资源上限。SDK中的源码解压后建议放到英文路径下,不要放在带空格的目录里,这是做嵌入式开发的常识。
交叉编译工具链方面,A53架构Linux侧一般用aarch64工具链,LiteOS侧可能用arm工具链。SDK的scripts目录里通常会提供工具链安装脚本,或者文档会写明工具链版本和下载方式。我这里强调一点:工具链版本必须与SDK匹配,并不是越新越好。我之前曾尝试用高版本工具链编译内核,结果出现莫名其妙的段错误和启动异常,后来换回SDK配套版本就一切正常了。如果你不想浪费时间,就老老实实用原厂验证过的工具链。
需要配置的环境变量包括工具链的路径、交叉编译前缀、目标架构等。一个典型的做法是跑SDK提供的环境配置脚本,比如:
# 解压SDK后进入顶层目录 ./sdk.unpack # 根据SDK实际提供的脚本名称,安装与配置交叉工具链 cd scripts ./toolchain_install.sh source /etc/profile有两点要提醒:第一,工具链安装后一定要确认是否已加入PATH,简单跑一下aarch64-linux-gnu-gcc -v验证;第二,不同版本SDK的脚本名可能差异较大,以包内README或docs目录下的说明为准。
3.2 SDK编译入口与完整命令走读
这类双系统SDK一般会在smp目录下提供统一的编译入口,通过Makefile或者脚本控制。整体编译顺序往往和系统依赖有关系:LiteOS先编译出小核固件,Linux这边负责加载和通信,再编译内核、设备树、rootfs、媒体平台库和示例程序。
如果SDK支持一键全量编译,通常会有类似这样的命令:
cd smp make all不过在实际生产环境里,我更推荐分步编译,特别是第一次接触这个平台的时候。分步编译的好处是定位问题精准,哪一步报错就知道是哪一块源码出了问题,而全量编译一旦中间失败,排查链路很长。一个典型的分步顺序是:
# 1. 先编译LiteOS侧,确认小核固件能生成 make liteos # 2. 再编译Linux内核与设备树 make linux make dtb # 3. 编译用户空间依赖的媒体处理平台库 make mpp # 4. 制作根文件系统 make rootfs编译时通常还会涉及配置项的选择,比如debug/release、是否启用某些功能模块、目标芯片型号变体等。SDK一般都有默认配置,初次编译建议先保持默认,跑通流程后再按需裁剪。千万不要一上来就乱改配置,否则出了问题很难判断是SDK本身的问题还是配置引入的问题。
这里我还要提一个非常实用的小技巧:把编译日志完整保存下来。使用make all 2>&1 | tee build_all.log这样的方式记录日志,一旦后续编译失败或者运行出问题,可以直接回头查日志。嵌入式开发最怕的就是“当时能编译过,换了环境就挂”,留日志是规范化操作里显得麻烦但极其值得的一步。
3.3 产物说明与常见编译流程
编译完成后,你会在对应目录的pub或out目录里看到最终产物。我把常见镜像整理成一个表,方便对照:
| 镜像文件 | 说明 | 主要用途 |
|---|---|---|
| u-boot bin文件 | 引导程序 | 系统上电后的第一阶段启动 |
| LiteOS bin文件 | 小核完整固件 | 加载到M7指定内存区域运行 |
| Linux内核uImage/Image | A53主系统内核 | 大核系统核心 |
| 设备树dtb文件 | 硬件配置描述 | 告诉内核有哪些设备、内存布局如何 |
| rootfs镜像 | 根文件系统 | Linux启动后的用户空间文件 |
| MPP库及sample可执行文件 | 媒体处理库和示例程序 | 快速验证视频通路、编码器等功能 |
对刚入门的人来说,我第一次全量编译的时候rootfs构建过程耗时最长,因为大量开源组件会被拉取和编译,如果网络状况不好,失败概率会提升。SDK有时会附带预编译好的rootfs,或者提供精简的最小根文件系统。第一次练习时,建议尽量复用自带的rootfs,不要一上来就自己裁剪,先把整体流程跑顺,后面再考虑定制。
还有一点是关于文件格式和换行符的,Linux侧源码请尽量在Linux系统下修改编辑,不要用Windows下的编辑器改了再传回来。很多莫名其妙的编译报错,最后发现是换行符或者BOM头的问题,这种问题最烦人,因为它不在你的业务逻辑里,却在编译阶段给你当头一棒。
4. 双系统启动流程与镜像部署细节
4.1 启动顺序与内存规划
双系统的启动流程跟单系统有本质不同。单系统启动简单,bootloader加载内核,内核起来挂rootfs就结束了。双系统则需要bootloader一次性考虑两个核的启动需求。
大致流程是这样的:上电后,u-boot先运行,完成DDR初始化和基本外设初始化,然后加载LiteOS镜像到M7对应的内存地址,再加载Linux内核和设备树到A53对应的内存地址。LiteOS启动非常快,往往在Linux还在解压内核的时候,小核已经把实时控制任务跑起来了。之后Linux完成启动,两个系统通过之前说的mailbox机制互相握手,建立通信链路。
内存规划是这里面最需要谨慎对待的部分。A53 Linux侧和M7 LiteOS侧都要占用DDR空间,还必须留出共享内存区域。Linux启动参数里通常会通过mem=或者设备树里配置可用内存范围,目的就是不让Linux误占M7的领地。如果你改了Linux的可用内存范围,却没有同步调整设备树和uboot里对应的地址参数,轻则M7代码加载失败,重则两个系统互相踩内存,运行起来到处都是不稳定现象。
我在实际调试中给一个建议:不要凭感觉改内存布局。原厂SDK默认的内存分配方案是经过验证的,初学阶段完全不需要动。等理解清楚整个链路,再根据产品需求优化。改内存布局要准备对应全部改动,否则会造成问题无法定位。
4.2 烧写与部署方案
拿到编译产物后,要么烧写到开发板,要么通过网络加载运行。三种常见部署手段各有优劣:
| 部署方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SD卡烧写 | 操作简单,离线部署 | 频繁插拔,速度一般 | 评估板验证、现场部署 |
| 网络下载 | 不用反复烧写,调试迭代快 | 依赖网络环境和dhcp/tftp配置 | 日常开发调试 |
| 烧写工具写入flash | 接近量产方案 | 工具配置繁琐,烧写耗时 | 最终产品化 |
日常开发阶段,我最推荐网络下载加NFS挂载rootfs的组合。每次修改代码后,直接把内核或LiteOS镜像通过网口下载到内存,不需要反复烧写flash,速度很快;rootfs放到服务器上,开发板通过网络挂载,文件修改即时生效。这套流程熟悉之后,开发效率明显提升。
NFS方式还有个别名叫做“网络文件系统”,它的核心思路是开发板不把rootfs放到本地flash,而是启动时通过网络把远端目录当成本地根目录来用。这样你交叉编译出来的应用程序,直接拷到服务器共享目录,开发板上就能执行,省去了打包进rootfs镜像再烧写的整个环节。
4.3 跑通sample验证双系统
部署完成后,第一步不是写业务代码,而是把SDK自带的示例程序跑起来。对于双系统平台,重点跑两类sample:一类是Linux侧的视频/媒体样例,用来验证大核系统基本功能;另一类是双系统通信样例,用来验证小核和大核之间的消息通路。
跑sample前先打开两个串口会话,一个看Linux日志,一个看LiteOS日志。启动后,Linux侧通过dmesg确认mailbox驱动加载成功,LiteOS侧串口能看到系统启动完成和任务创建日志。接着运行通信demo,让LiteOS周期性上报某个计数或者状态,Linux侧收到后打印出来。如果这个链路通了,说明双系统的基本通信没有大问题,可以放心往上加业务。
有一点容易忽略:M7侧的串口打印和A53 Linux的串口打印往往是两个独立串口。初学阶段,务必把两根串口线都接好,两个窗口都打开,不然你只盯着Linux日志,永远不知道LiteOS那边发生了什么。我第一次调试时就是只在Linux侧打印消息,结果LiteOS挂了自己却浑然不觉,浪费了半天时间。
5. 常见问题与调试心得
5.1 编译报错与排查
前几年这个系列的SDK比较常见的编译问题大概有这么几类。第一类,找不到交叉编译器或者编译器版本不对,报错信息往往是“command not found”或者“unsupported GNU version”相关提示。处理思路很简单,先确认工具链是否已正确安装,再检查Makefile指定的编译器前缀是否匹配。
第二类,源码编译到某个模块时缺依赖库,比如编译rootfs时网络拉取组件失败。这种问题大概率是网络环境的因素,解决办法是配置好环境,或者用SDK自带的预编译产物。
第三类,编译过程中文件系统报错,比如磁盘空间不足、目录权限错误。这类问题看似简单,但在一群人共享一台服务器时经常发生,把SDK放到自己有完全读写权限的目录下可以省掉很多权限麻烦。
我把一个相对通用的排查顺序列在这里,供参考:
- 确认SDK解压完整性,没有缺文件或者解压失败
- 确认工具链版本和架构匹配(aarch64还是arm)
- 确认所有依赖库和脚本顺利执行
- 确认磁盘空间与文件权限
- 查看第一个报错点,而不是中间的某一段
- 对照docs目录里的版本说明和常见问题清单
我第一次全量编译失败,就是因为在旧版本SDK上强行用了新工具链,编译内核时各种报错。后来换了原厂配套版本一切都正常了。这个案例说明,嵌入式编译环境的“匹配”比“新”更重要。
5.2 双系统运行类问题
双系统运行时最容易碰到的一类问题是“Linux起来了,但LiteOS消息一直收不到”。排查这类问题有几个固定方向:先确认LiteOS镜像有没有真的加载运行起来,再确认mailbox中断有没有触发,再确认共享内存地址是否两边一致。
常见的隐形坑有两个。第一个是缓存一致性问题,刚才已经提到过,Linux侧写共享内存之前需要做cache flush操作,不然M7读到的是旧数据。第二个是内存地址配置不一致,比如设备树里给mailbox分配的中断号、寄存器地址与LiteOS侧编译时用的地址不一致,导致消息完全无法送达。
这两个问题都很难通过单纯看日志定位,需要像侦探一样对照源码和文档逐项排查。我的做法是先在LiteOS侧写一个简单任务:周期性地往共享内存固定偏移写一个递增数字,然后Linux侧每隔一段时间去读这个地址,如果数字在涨,说明内存链路至少是通的,再通过门铃通知链路排查问题。把大问题拆成一个个小模块逐个验证,这样定位效率最高。
5.3 调试手段与小技巧
最后分享一些我自己沉淀下来比较实用的调试手段。
先看Linux侧,内核日志dmesg是最基本的手段,但别光看最后的报错,要看整个启动过程中和mbox、双系统相关的注册信息。设备树里的节点内容也可以通过ls /proc/device-tree/或者直接查看系统下的设备目录来确认。
再看LiteOS侧,这类小核系统的日志手段相对简陋,串口打印是首选。在代码里多埋几个阶段打印点,特别是初始化函数的最开始和最末尾、消息接收回调的入口和出口,这样一旦跑飞,至少能定位到“跑到了哪一步之后才出问题”。有的SDK还支持系统异常时把崩溃信息dump出来,记得先把这个功能打开,别等出问题的时候才发现没有使能。
还有一个技巧跳脱出具体日志:通过观察指示灯或GPIO电平判断系统运行状态。比如LiteOS定时翻转某个GPIO,用示波器或逻辑分析仪看一下这个引脚有没有波形,就能最快判断小核是否还活着。这比打开串口看日志更快,尤其在系统已经明显异常时,硬件手段往往能提供关键线索。
我在调试双系统通信时,有这样一个固定顺序:先验证小核独立运行正常,再验证大核独立运行正常,最后再联调通信链路。每一步都用最简单的方式验证一个点,不要指望一步到位把所有问题都解决。这个习惯帮我避开了无数复杂的排查事件,也推荐给刚接触双系统的同学。
最后说一点个人体会。这颗芯片的双系统SDK,最值得花时间的地方不是编译流程,而是先把两个系统的内存布局和通信链路画出来。我第一次接触时直接闷头编译,把sample全跑了一遍,但遇到问题还是抓瞎。后来把文档里关于内存和mailbox的章节反复看了两三遍,对照代码逐行读,再回来看双系统部署就清晰多了。另外建议准备一块能快速打印串口日志的开发板,同时打开两个串口窗口,一边看Linux日志一边看LiteOS日志,这样调试效率会高很多。希望这篇拆解能帮你少走一些弯路。