拿到 HI3556V200 的板子第一件让我头疼的事不是上电而是面对那个动辄几个 G 的 SDK 压缩包。没接触过海思平台的人第一次解压完大概率是一种“我是谁我在哪”的状态目录一层套一层release note 写得像流水账osdrv、mpp、smp、tools 这些缩写看得人脑袋大。这篇继续双系统学习系列上一篇把开发环境和板级启动流程过了一遍这一篇我打算专门把 SDK 的底细翻一翻说说 Linux LiteOS 双系统这套方案在 SDK 层面到底怎么组织的、哪些目录必须看、哪些文件要先改以及实际编译部署时最容易踩的坑。这篇文章适合两类人一是刚拿到 HI3556V200 开发板、正准备从零开始跑 SDK 例程的嵌入式开发者二是对海思平台的 Linux LiteOS 双系统协同工作机制感兴趣、想搞明白 SDK 目录和镜像之间关联的读者。重点会放在 SDK 的整体架构、双系统镜像构建链路、MPP 核心模块的编译与加载、以及我实际调试中遇到的几个典型问题上内容偏实战尽量不讲空泛的概念。1. SDK 的整体定位与双系统设计思路1.1 HI3556V200 双系统到底是怎么个“双”法说双系统之前得先明确一个共识HI3556V200 的 Linux LiteOS 双系统不是像电脑那样开机让你选启动哪个操作系统而是两个系统同时在工作只是分工不同。芯片内部有多核主核或主核组跑 Linux负责网络协议栈、文件系统、应用业务逻辑这些复杂但不强调硬实时的任务另一个核跑 LiteOS负责 ISP 中断响应、Sensor 帧率控制、云台控制这类对时序要求极高的实时控制。两个核各自跑各自的操作系统会引出两个关键问题镜像怎么协调启动核间数据怎么同步SDK 恰恰就是围绕这两个问题来组织的。启动层面上LiteOS 的镜像并不是独立烧在另一个分区里由 BootROM 直接拉起来的而是由 Linux 启动后期通过核间加载机制把它放进对核并触发启动。这种组织方式有个直接的好处可以用一套烧写流程管理 Linux rootfs 和 LiteOS 镜像量产部署时不用单独处理两套启动链。我在 SDK 的 osdrv 目录里看到了对应的镜像打包脚本把 liteos 镜像放在 rootfs 的指定路径下再由系统启动脚本加载到对核整个时序链条很清楚。核间数据方面SDK 提供的是共享内存加中断通知的组合方案。LiteOS 侧和 Linux 侧各自维护一块物理内存映射区一侧写入数据后通过硬件中断maildrop 机制通知另一侧。为了不让两个系统把内存踩坏SDK 的内存配置表里会专门预留一块 region 给核间通信不在 Linux 的常规内存池范围内。这块配置要是改错了现象会非常诡异双系统各自单跑都正常一同时运行就随机死机查半天往往发现是内存重叠。1.2 SDK 目录结构里藏着全貌SDK 解压后一般会分成两层。外层以版本号命名的目录里面是 release note、工具链、文档以及一个 unpack 脚本运行 unpack 脚本之后才会真正展开 osdrv、mpp、board、smp、tools 这些核心子目录。日志上先大致描述一下这几个目录的职责后面详细说osdrv全称大概是 OS Drive板级系统相关包含 BootLoader、Linux 内核源码补丁、rootfs 构建脚本、双系统镜像的打包链路。mpp媒体处理平台这是海思 SDK 的核心价值所在VI、VPSS、VENC、VO、ISP、AI/AO 这些模块的库文件、头文件和 sample 例程全在这里。smp这个词在不同版本里含义有差异在双系统方案里它通常和异构核启动、核间通信相关包含 LiteOS 侧的一些接口封装和双系统 demo。board板级配置相关的设备树、DDR 初始化参数。改动硬件或内存颗粒时主要和这个目录打交道。toolsPC 端工具和部分驱动源码比如烧写工具的 PC 端版本、DDR 训练工具等。我见过不少人一拿到 SDK 就直奔 mpp 去编译 sample结果卡在找不到头文件或内核编译不过原因是根本没有先跑通 osdrv 的编译流程。SDK 内部是有依赖关系的建议编译顺序是从 osdrv 开始内核和设备树就位后再往上编 mpp最后才是 sample。2. 拿到 SDK 之后先翻哪里核心目录拆解与实操要点2.1 永远不要跳过 release note 和 docs这句话听起来像废话但实际看过的人真不多。HI3556V200 的 SDK 在 docs 目录下有一批官方文档命名规律一般是《开发环境搭建》《U-Boot 使用说明》《MPP 开发指南》《双系统使用说明》之类。我不是让你把每本都读完但至少要把《开发环境搭建》和《双系统使用说明》这两本前几章翻烂。原因很简单SDK 的中文文档和实际工具链版本、内核分支是强绑定的。比如某个版本的工具链必须是某个版本才能编过内核某一个版本的 u-boot 需要特定的 DDR 配置脚本。这些信息都写在 release note 和文档配套说明里而不是在代码注释里。我遇到过朋友拿着旧版 SDK 的交叉工具链去编新版内核报错一堆最后发现只是版本匹配问题。实操建议解压完 SDK 后第一件事打开 release note用表格记下三个信息工具链版本号、内核版本号、已知问题列表。后面编译出问题先回来核对这三个信息至少能排除一半的“玄学问题”。2.2 osdrv系统构建的主战场osdrv 是 double 系统方案的骨架目录。它里面通常包含 opensource、uboot、kernel 和 rootfs 相关的构建脚本。以常见布局来说直接在最外层执行 make 命令就能一口气编完 u-boot、kernel、rootfs最后在 osdrv/out 目录里得到烧写用的镜像。为了照顾不熟悉海思构建体系的读者我把编译流程的标准姿势写一下# 前提确认交叉工具链路径已加入 PATH # 进入 osdrv 目录后 make all # 如果只是想先编内核 make kernel # 如果只想编 rootfs make rootfs我个人的建议是第一次直接make all让整个链路完整跑一遍即使失败也会暴露出环境里缺什么。另外osdrv 编译时对主机环境有要求我实际用的是一台 x86 的 Ubuntu 机器装齐 make、gcc、bison、flex、libncurses-dev 等基础包后整体编译大概十几分钟到半小时。别用 root 身份直接编SDK 脚本里部分步骤对权限有要求反而容易出奇怪问题建议在普通用户下加 sudo 执行具体命令。编译完的产物放在 osdrv 的 out 目录里面有 boot 相关的 u-boot 镜像、内核镜像和 rootfs 镜像。值得注意的是这个 rootfs 并不是一个普通的最小文件系统里面已经预留好了 LiteOS 镜像的挂载路径和加载脚本这也是后面双系统能正确拉起对核的关键。2.3 mpp媒体能力集中在哪mpp 是海思平台最有含金量的目录。先看它的顶层布局一般分为 include、ko、lib、sample、tools 几个部分。include所有对外暴露的 API 头文件以 hi_xxx.h 命名比如 hi_mpi_vi.h、hi_mpi_venc.h。ko编译好的内核模块这些模块不是独立的野驱动而是 MPP 运行时的底层支撑比如内存管理模块和各个外设驱动。libMPP 的用户态库以 libmpi.a、libhdmi.so 等命名。sample 程序编译时链接的就是它们。sample官方例程源码值得花时间逐个读。从使用的角度来说MPP 的代码关系是先加载内核 Ko再调用用户态库的 API。Ko 的加载顺序不能乱所以 SDK 在 mpp 目录下提供了一组脚本统一负责按顺序 insmod 所有 Ko 并设置内存。正式项目的部署脚本基本也是从这套脚本改造出来的。我自己的习惯是先在 sample 目录下找一个最小的 pipeline 例程比如单纯的 VI→VPSS→VENC 编码链路跑通之后再往里面加自己的逻辑。不要一上来就动 VO 显示、AI 音频这种外围模块先把核心视频链路稳住了后面加模块心里才有底。2.4 smp 与双系统协同代码smp 目录在双系统学习系列里要特别提到。这里面的内容不仅包含 LiteOS 侧镜像的源码或预编译产物还封装了核间通信接口。如果 SDK 里带双系统 demo一般也会放在这个目录里它能告诉你两件事LiteOS 侧应该注册哪些任务、Linux 侧通过什么接口向对核发命令。我打开过其中一个双系统 demo 的源码它的结构大概是LiteOS 侧跑一个消息循环任务阻塞等待 maildrop 中断Linux 侧有一个应用通过 SDK 提供的接口把消息写到共享内存并触发中断对核收到中断后解析消息并执行对应动作。这个模型非常适合做云台控制或 Sensor 参数调节这类实时性要求高的场景。对于只想用 Linux 做业务的开发者smp 目录反而可能是你会忽略但最终又不得不面对的目录只要你的产品有硬实时外设控制需求迟早要把一部分逻辑挪到 LiteOS 侧那时候再回来翻这目录就晚了。3. 开发环境建立与双系统镜像的完整构建路径3.1 交叉编译工具链的选择与配置HI3556V200 是 ARM 架构所以必须用交叉编译工具链不能用 x86 自带的 gcc。SDK 的 tools 目录下一般会有工具链安装包不同版本可能命名不同常见的是 aarch64 开头的工具链。解压之后把工具链的 bin 目录加到 PATH 里然后验证一下aarch64-linux-gnu-gcc -v能看到版本输出就算配好了。有一个很隐蔽的坑很多老员工在 .bashrc 里配置的路径可能是之前项目留下的指向的是另一套工具链。所以编译前务必which aarch64-linux-gnu-gcc确认当前生效的是 SDK 要求的那一套。工具链版本不匹配的典型报错比如链接时出现 unrecognized option或者编译内核时出现一堆莫名其妙的结构体大小校验错误百分之九十都是工具链问题。这里我提供一套通用的排查流程先核对 release note 里的工具链版本号再aarch64-linux-gnu-gcc --version看当前生效版本最后确认不是多个工具链目录同时混在 PATH 里导致的优先级污染。3.2 双系统镜像的构建顺序拿到 SDK 后构建双系统镜像我的推荐路径分四步走第一步编译 osdrv得到 u-boot、内核、rootfs 三件套。这一步是基础如果内核尺寸、DDR 配置不对后面都不成立。第二步单独编译或获取 LiteOS 镜像。不同 SDK 版本对 LiteOS 的处理方式不完全一样有的直接提供 bin 产物有的提供源码让你自己编。如果是源码方式一般也有独立的编译脚本产物是一个 elf 或 bin 格式的镜像。第三步把 LiteOS 镜像拷贝到 rootfs 指定路径。具体路径可以参考 sdk 的部署脚本通常是某个/userdata或/lib/firmware之类的固定目录。拷贝完成后需要重新打包 rootfs 镜像。第四步把 u-boot、内核、新 rootfs 烧到板子启动后看系统日志确认 Linux 起来之后 LiteOS 也被加载、对核启动打印出现。有四步可能对第一次接触双系统的人有点抽象我建议先别急着烧写在 osdrv 目录里看打包脚本脚本里每一步对应什么文件、哪个路径多看两遍再动手比盲目烧写有效率得多。我自己第一次就是图快直接烧了默认镜像结果发现 LiteOS 没有启动日志后来看了脚本才知道要把镜像一个一个对应拷贝好再重新打包。3.3 rootfs 里 LiteOS 镜像的加载时机关于 LiteOS 镜像的加载时机我补充一个从日志里观察到的关键点。Linux 启动完成后系统 init 脚本中有一段逻辑检查 LiteOS 镜像是否存在存在就触发核间加载把镜像搬到对核指定地址然后释放对核复位。对核启动后会打印一串 LiteOS 的 banner 日志通常带版本号之类的信息。如果板子上电后只看到 Linux 启动日志、完全看不到 LiteOS banner先别怀疑芯片有问题优先检查两件事第一rootfs 里 LiteOS 镜像路径是否和脚本里写的一致第二DDR 内存配置表里给 LiteOS 预留的地址和镜像加载地址是否一致。这两个问题各占一半概率我实际调过的板子里 LiteOS 起不来的情况基本都是这两个原因。4. MPP 核心链路与双系统场景下的开发要点4.1 一条最小视频链路怎么跑起来MPP 的学习曲线比较陡但核心链路其实不复杂。以最常用的视频编码任务为例数据流是Sensor 采集 → VI 输入 → VPSS 处理 → VENC 编码 → 输出码流。SDK 的 sample 里有一个对应这个链路的例程代码结构大致是// 初始化视频输入 HI_MPI_VI_SetAttr(...); HI_MPI_VI_EnableChn(...); // 初始化 VPSS 通道 HI_MPI_VPSS_CreateGrp(...); HI_MPI_VPSS_SetChnAttr(...); // 绑定 VI 到 VPSS HI_MPI_SYS_Bind(...); // 创建编码通道 HI_MPI_VENC_CreateChn(...); // 循环获取编码码流 HI_MPI_VENC_GetStream(...);代码的调用顺序不要自己随便改因为 MPP 内部是有状态机的先创建组再绑定、先开通道再启动编码这个顺序都是由底层驱动决定的。如果顺序错了函数返回的错误码常见的是HI_ERR_MPI_ILLEGAL_PARAM或HI_ERR_MPI_NOT_ENABLE。编译 sample 的方式一般是在 mpp 的 sample 目录里直接执行 make。编译出来的可执行文件会放在一个 out 目录里需要的依赖库文件在 mpp/lib 目录下。把这个可执行文件转到板子上时记得同时把对应的 .so 库拷过去并且设好库路径环境变量export LD_LIBRARY_PATH/xxx否则运行时会报找不到库文件的错。4.2 Ko 加载顺序和内存参数跑 MPP 之前必须先加载一组内核模块。SDK 提供的加载脚本里有大量的 insmod 命令如果自己手写加载遗漏一个模块或顺序不对后续 API 调用可能会直接返回设备不存在的错误。内存参数是海思平台一个特有的问题。MPP 本身不是直接 malloc 内存而是从一块预留的物理连续内存里取。这块内存的基地址和大小在系统启动参数或 SDK 配置头文件里设定。它就像一个总池子VI、VPSS、VENC 按需从里面分配逻辑通道 buffer。改分辨率、加编码路数本质上就是在调整这块池子的分配。我碰到过一个典型问题把编码分辨率从 1080P 调到 4M 后VPSS 创建组失败错误码指向内存不足。查了cat /proc/meminfo发现系统本身就占了太多物理内存预留池子小了。最后调整启动参数里的内存分配把 MPP 池子从 64MB 加到 128MB问题解决。这类问题在调兼容多型号 sensor 时非常常见单人开发这种细节很难一次到位属于多做几个项目才能积累出来的经验。4.3 双系统开发时 MPP 的调用注意事项如果业务里需要硬实时控制比如用 LiteOS 侧接收串口指令控制云台再用 Linux 侧做视频编码就会涉及两条链路交叉访问硬件外设的问题。我的建议是视频链路相关的 API 全部留在 Linux 侧调用不要在 LiteOS 侧直接调 MPP 的 VI/VENC 接口去操作视频数据。这样做的原因是 MPP 内部有复杂的 buffer 管理逻辑和状态锁跨系统调用会让问题翻倍难查。合理的设计是LiteOS 侧只做 GPIO、PWM、串口这类简单外设的控制和响应把需要视频数据配合的操作通过核间消息机制转发给 Linux 侧完成。反过来Linux 侧应用需要读取实时外设状态时也不要去直接读寄存器因为有些外设挂在 LiteOS 侧管理的中断域里。规范化做法是向 LiteOS 侧发一条消息让对核采完数据后把结果放到共享内存里再告诉 Linux 侧去取。这个机制看起来多了一次消息传递但换来的是双系统边界清晰、调试定位容易这个交换在项目后期会非常值得。5. 常见问题与排查技巧实录5.1 LiteOS 没起来的排查顺序这个问题我在前面提到过一次但值得单独列出来。LiteOS 没启动的现象分两种一种是什么日志都没有另一种是有部分日志但没有 banner。第一种先查 rootfs 里 LiteOS 镜像有没有放对位置、权限是不是可执行第二种多半是 LiteOS 镜像加载到了内存但被异常触发常见原因是镜像地址和 Linux 的设备树保留内存重叠。排查工具方面推荐两个方法一是启动时从串口看完整 Linux 日志确认加载 LiteOS 的脚本有没有执行二是进入 Linux shell 后手动验证镜像文件是否存在再手动执行一遍加载逻辑逐步缩小问题边界。5.2 MPP 调用失败的错误码速查MPP 的错误码有一套统一的规则返回一个 32 位整数高 16 位是模块 ID低 16 位是错误类型。如果返回0xA00F0005这类数字不要逐字去记关键是学会去头文件里查。hi_errno.h里有每个模块的错误码定义和对应宏名。实际调试时把错误码往这个头文件里一对立刻就知道是模块初始化问题、参数错误还是资源不足。下面是我实际开发中最常遇到的几类问题汇总现象常见原因排查方向双系统启动后偶发死机核间通信内存与 MPP 池重叠核对内存配置表确认共享内存不在 MPP 池范围内VENC 取码流超时输入帧率不足或 VPSS 通道带宽不够检查 Sensor 帧率配置和 VPSS 通道分辩率加载 Ko 报符号找不到Ko 版本与内核版本不匹配确认 osdrv 和 mpp 是同一次 SDK 版本编译LiteOS 侧 GPIO 控制没反应引脚复用被 Linux 侧占用检查复用表GPIO 资源只能一侧配置运行 sample 报找不到 libmpi库路径没有导出export LD_LIBRARY_PATH 指向 mpp/lib5.3 编译环境的一些零碎经验SDK 编译对磁盘空间的要求比较高整个编译完成大概需要 20GB 以上空间别只想留几个 G 就开始编。尽量用 ext4 文件系统也有同事用过 NTFS 挂载的目录来编结果各种权限和符号链接问题。另外强烈建议所有编译都在普通用户下做不要 root。原因不是 root 编不过而是 root 编出来的文件在后续 NFS 挂载开发时经常出现权限问题导致板子上无法正常读写又花时间去调权限。这一条是很多人踩过之后才明白的。6. 个人经验之谈写到这里关于 HI3556V200 SDK 的内容基本都过了一遍。这套 SDK 的学习曲线说陡也陡说缓也缓关键在于是否按正确的顺序接触它。我个人的体会是先跑通 osdrv 编译再点亮一块板子最后才是深入 mpp 调各种 pipeline。双系统这个特性本质上没有那么神秘把它理解成“一个 Linux 系统加一个轻量实时核通过消息机制配合干活”就够了SDK 里所有的目录、脚本、镜像产物本质上都是在服务这个模型。最后分享一个实用小技巧拿到 SDK 后尽快把 release note 里的已知问题列表单独摘到一个备忘录里。海思 SDK 的 note 里通常会写一些已经发现的坑和规避方案这些信息散在文档里不显眼但真到调试时往往能让你少走很多弯路。我的习惯是边看边把它们整理成自己的排查手册等下一个项目再拿起同一套 SDK 时这份手册比任何官方文档都有用。