ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AUTOSAR OS多核控制实战:OS-Application与ChainTask核心机制解析

AUTOSAR OS多核控制实战:OS-Application与ChainTask核心机制解析 1. 从单核思维切换到多核RTOS为什么AUTOSAR OS的多核控制值得单独拎出来讲很多做汽车电子的朋友第一次接触AUTOSAR OS多核的时候脑子里冒出来的第一个问题往往是我在STM32上跑FreeRTOS任务调度、信号量、队列用得也挺顺多核不就是多几个核跑同样的东西吗这个想法在单核时代没问题但放到AUTOSAR OS的多核语境下会直接踩坑。因为AUTOSAR OS的多核控制不是多几个核这么简单它引入了一整套围绕OS-Application、核间任务分配、跨核通信和多核启动同步的机制这些机制在单核RTOS里根本不存在。我自己最早接触这块是在一个域控制器项目上。当时系统里有一个主控核负责整车通信和诊断另外两个核分别跑电机控制和传感器融合。一开始我按单核的思路去分配任务结果发现任务激活之后死活不执行查了半天才发现任务被分配到了另一个核上而那个核的调度器还没启动。这就是典型的单核思维在多核环境下的翻车现场。所以这篇内容我想把AUTOSAR OS多核控制的核心机制拆开讲清楚。适合谁看如果你已经了解AUTOSAR OS的基本概念比如Task、ISR、Event、Alarm这些但一碰到多核就发懵那这篇就是写给你的。如果你还在用FreeRTOS或者裸机开发想了解汽车软件里多核RTOS到底怎么玩也可以顺着看下去我会尽量用类比把概念讲透。核心关键词我先摆出来RTOS、AUTOSAR OS、多核控制、OS-Application、ChainTask。这几个词贯穿全文尤其是OS-Application和ChainTask前者是多核资源隔离的抓手后者是多核任务调度的关键API。2. AUTOSAR OS多核架构的底层逻辑不是核多就快而是分工要清晰2.1 多核RTOS和单核RTOS的本质差异在哪里单核RTOS的调度模型很直观一个调度器一条就绪队列谁优先级高谁先跑。你不需要关心任务跑在哪个核上因为只有一个核。但到了多核问题立刻复杂起来。AUTOSAR OS把每个核看作一个独立的调度单元每个核有自己的调度器、自己的就绪队列、自己的中断向量表。这意味着什么呢意味着一个任务从被激活到真正执行中间多了一层路由——它得先确定自己属于哪个核。这里有个容易混淆的点AUTOSAR OS里的核Core和OS-Application是两个不同维度的概念。核是物理执行单元OS-Application是逻辑资源容器。一个OS-Application可以绑定到一个核上也可以跨核分布但跨核的OS-Application在资源访问上会受到严格限制。我见过不少项目为了图省事把所有任务塞进一个OS-Application结果多核的优势完全没发挥出来反而因为跨核同步开销把性能拖下去了。打个比方单核RTOS像一个只有一个窗口的银行所有客户排队等叫号。多核RTOS像有多个窗口的银行每个窗口有自己的队列但客户不能随便换窗口而且有些业务只能在特定窗口办。OS-Application就是那个业务类型的划分它决定了哪些任务可以共享资源哪些任务必须隔离。2.2 OS-Application多核资源隔离的核心抓手OS-Application在AUTOSAR OS里扮演的角色类似于进程在Linux里的角色但又不完全一样。它的核心作用是资源归属划分。每个OS-Application拥有自己的Task集合、ISR集合、Alarm集合、ScheduleTable集合以及自己的内存保护边界。在多核环境下OS-Application还必须声明它属于哪个核或者是否跨核。为什么需要这个因为汽车软件对功能安全的要求极高。假设你的系统里同时跑着刹车控制和娱乐系统你绝对不希望娱乐系统的一个野指针把刹车控制的内存踩了。OS-Application通过内存保护机制MPU把不同功能域隔离开即使一个OS-Application崩溃了也不会影响其他OS-Application。这在单核RTOS里通常靠程序员自觉来保证但在多核AUTOSAR OS里是硬件强制执行的。实际配置的时候OS-Application的属性里有一个关键字段叫OsApplicationTrusted。如果设为TRUE这个OS-Application就运行在特权模式可以访问所有资源如果设为FALSE它就运行在非特权模式只能访问自己边界内的资源。我个人的经验是除了极少数需要直接操作硬件的底层模块大部分应用层OS-Application都应该设为非特权模式。这样虽然会增加一点上下文切换的开销但换来的安全性提升是值得的。还有一个坑跨核的OS-Application在访问共享资源时必须通过AUTOSAR OS提供的跨核通信机制不能直接操作另一个核的内存。这一点和Linux的进程间通信很像你不能直接读另一个进程的变量得通过管道、共享内存或者消息队列。2.3 核间任务分配哪些任务该放在哪个核上任务分配是多核设计里最考验经验的部分。AUTOSAR OS本身不强制你怎么分配它只提供机制策略得你自己定。但根据我做过几个项目的经验有几个原则可以参考。第一按功能域划分。把同一功能域的任务尽量放在同一个核上减少跨核通信。比如电机控制相关的任务电流环、速度环、PWM更新放在一个核上通信协议栈CAN、LIN、以太网放在另一个核上。这样每个核内部的调度是独立的不需要频繁跨核同步。第二按实时性要求划分。硬实时任务比如点火控制、喷油控制放在一个核上软实时任务比如诊断、标定放在另一个核上。这样硬实时核的调度不会被软实时任务干扰。AUTOSAR OS支持给每个核单独配置调度策略你可以让硬实时核跑抢占式调度软实时核跑时间片轮转。第三按负载均衡划分。这个比较难因为汽车软件的负载往往是不均匀的。我的做法是先按功能域粗分然后用Trace工具比如Lauterbach或者iSYSTEM实测每个核的CPU占用率再微调。如果某个核的占用率长期超过70%就得考虑把一些任务迁到其他核上。这里有个细节AUTOSAR OS的任务分配是在配置阶段也就是用工具生成OS代码的时候就确定了的运行时不能动态迁移。所以你在设计阶段就得想清楚不能等到跑起来发现负载不均再改。这也是为什么AUTOSAR OS的项目前期配置工作量很大但一旦配好运行时的确定性非常强。3. ChainTask在多核环境下的行为一个容易被误解的API3.1 ChainTask的基本语义和单核下的表现ChainTask是AUTOSAR OS里一个很特殊的API。它的作用是终止当前任务并激活指定任务。注意它不是激活另一个任务然后自己继续跑而是自己先死然后让另一个任务活。这个语义在单核环境下很清晰当前任务调用ChainTask之后调度器立刻切换到目标任务当前任务的状态被重置为挂起。为什么要有这个API主要是为了减少任务切换的开销。在单核环境下如果你用ActivateTask加TerminateTask的组合中间会经过一次调度器决策可能还会触发其他就绪任务的调度。而ChainTask直接指定了下一个要跑的任务调度器不需要再做决策效率更高。这在汽车软件的周期性任务里很常见比如一个10ms的任务跑完之后直接链到下一个10ms任务形成一条任务链。但这里有个限制ChainTask的目标任务必须和当前任务在同一个核上。如果你试图ChainTask到一个不同核的任务AUTOSAR OS会返回错误。这个限制在单核时代不是问题因为只有一个核。但到了多核这个限制就成了一个必须注意的约束。3.2 跨核ChainTask为什么被禁止AUTOSAR OS规范里明确规定ChainTask不能跨核调用。原因很简单跨核的任务切换涉及到两个核的调度器同步开销远大于单核内的切换。如果允许跨核ChainTask那么当前核在终止任务之后需要等待目标核的调度器响应这期间当前核可能处于空闲状态浪费了计算资源。而且跨核同步本身需要锁机制如果在锁的保护下调用ChainTask还可能引发死锁。我踩过的一个坑是这样的当时我把一个通信任务和一个控制任务分别放在两个核上通信任务处理完一帧数据之后想直接ChainTask到控制任务去触发控制计算。结果编译的时候工具直接报错说跨核ChainTask不支持。后来改成用跨核Event触发通信任务发一个Event给控制任务所在的核控制任务在Event的等待点上被唤醒。虽然多了一次Event传递的开销但逻辑上更清晰也不会阻塞当前核。所以记住一条ChainTask是核内API跨核通信用Event或Message。这个原则在多核AUTOSAR OS里是铁律。3.3 多核下用ChainTask组织任务链的正确姿势虽然ChainTask不能跨核但在核内组织任务链依然非常有用。我的做法是在每个核内部把同一功能域的周期性任务串成一条链。比如在电机控制核上任务链是这样的电流采样任务 - Clarke/Park变换任务 - PID计算任务 - SVPWM更新任务 - 电流采样任务下一个周期。这条链上的每个任务都用ChainTask指向下一个形成一个闭环。这样做的好处是任务切换的开销降到最低而且任务的执行顺序是确定的不会因为调度器的随机性导致顺序错乱。对于硬实时控制来说确定性比什么都重要。但要注意一点ChainTask链上的任务优先级必须相同。如果优先级不同AUTOSAR OS的行为是未定义的。我一般会把链上所有任务的优先级设成同一个值然后通过ChainTask的顺序来控制执行流。这样调度器不会在链中间插入其他任务保证了链的原子性。还有一个细节ChainTask调用之后当前任务不会返回到调用点。也就是说ChainTask后面的代码永远不会执行。这一点和TerminateTask一样但很多人第一次用的时候会忘记在ChainTask后面写了清理代码结果那些代码从来没跑过。我建议在ChainTask之前把所有清理工作做完ChainTask作为函数的最后一条语句。4. 多核启动与同步核间握手不是可有可无的仪式4.1 多核启动的顺序问题多核系统的启动比单核复杂得多。单核系统上电之后从复位向量开始跑初始化时钟、内存、外设然后启动OS调度器完事。多核系统不行因为多个核可能同时上电如果每个核都去初始化同一份共享资源就会冲突。AUTOSAR OS的解决方案是主从核启动模型。系统上电后只有一个核通常叫主核或者Master Core先启动它负责初始化共享资源比如全局变量、共享内存、跨核通信通道。等这些初始化完成之后主核再通过核间中断或者硬件信号量去唤醒其他核从核。从核启动之后只初始化自己私有的资源然后等待主核发出可以开始调度的信号。这个顺序不能乱。我见过一个项目从核启动太快在主核还没初始化完共享内存的时候就跑去访问结果读到的全是随机值系统行为完全不可预测。后来在从核的启动代码里加了一个自旋等待等主核把一个标志位置位之后才继续问题才解决。4.2 核间同步的几种机制和适用场景AUTOSAR OS提供了几种核间同步机制每种都有适用的场景。第一种是自旋锁Spinlock。这是最底层的机制用于保护跨核的临界区。一个核要访问共享资源时先获取自旋锁访问完释放。如果锁被另一个核持有当前核就原地空转等待。自旋锁的优点是开销小缺点是等待期间浪费CPU。所以自旋锁只适合保护很短的临界区比如几个变量的读写。如果临界区里有耗时操作绝对不能用自旋锁。第二种是跨核Event。一个核可以设置另一个核上任务的Event触发那个任务从等待状态变为就绪状态。这是跨核通信最常用的方式开销比自旋锁大但不会浪费CPU。我一般用跨核Event来传递数据准备好了或者命令到了这样的信号。第三种是跨核Message。AUTOSAR OS支持通过Message对象在核间传递数据。Message有固定的长度和队列深度发送方把数据写入Message接收方从Message读取。这种方式适合传递小批量的结构化数据比如传感器读数或者控制命令。第四种是共享内存加自旋锁。对于大批量数据比如图像帧或者雷达点云用Message传递效率太低。这时候可以在共享内存里开辟一块区域发送方写数据然后通过自旋锁或者内存屏障保证数据可见性接收方读数据。但这种方式需要非常小心地处理缓存一致性问题因为不同核的缓存可能看到不同的数据。4.3 启动同步中的常见坑和排查方法多核启动同步最容易出的问题是死锁。比如主核在等从核发来的初始化完成信号而从核在等主核发来的可以开始信号两边互相等系统就卡死了。这种问题在调试的时候很难查因为两个核都不报错就是不动。我的排查方法是在启动阶段加一些GPIO翻转或者调试打印把每个核的启动进度打出来。如果发现某个核卡在某个等待点上就去检查它等的那个信号是谁发的那个核是不是也在等别的信号。顺着依赖链一路查下去总能找到环。还有一个坑是缓存一致性。在多核系统里每个核可能有自己的L1缓存。主核写了一个变量到共享内存从核去读的时候可能读到的是自己缓存里的旧值。解决方法是把共享变量声明为volatile并且在写完之后执行一次内存屏障指令比如ARM的DMB。但volatile只能保证编译器不优化不能保证缓存一致。真正的缓存一致性需要硬件支持比如ARM的CCICache Coherent Interconnect。如果硬件不支持就得手动刷缓存那就很麻烦了。所以我在项目里一般建议跨核共享的数据尽量放在非缓存区域或者用硬件支持的缓存一致性机制。如果这两者都做不到那就用Message传递数据让AUTOSAR OS的通信层去处理缓存问题。5. 多核调试和性能分析工具链怎么选数据怎么看5.1 多核调试和单核调试的本质区别单核调试的时候你暂停一个核整个系统就停了所有状态都是静止的你可以慢慢看变量、看调用栈。多核调试不行你暂停一个核其他核还在跑。如果你暂停的那个核正在持有自旋锁其他核就会一直等整个系统就卡住了。所以多核调试的第一原则是尽量不要在运行时暂停单个核。那怎么做用Trace。Trace工具可以在不暂停CPU的情况下记录任务的切换、中断的触发、跨核通信的事件。Lauterbach的Trace32和iSYSTEM的winIDEA都支持AUTOSAR OS的Trace可以看到每个核上任务的执行时间线。我一般会用Trace来看几个关键指标每个核的CPU占用率、任务的最大响应时间、跨核通信的延迟。5.2 用Trace数据定位多核性能瓶颈Trace数据拿到之后怎么分析我一般分三步走。第一步看核间负载是否均衡。如果某个核的占用率明显高于其他核说明任务分配有问题。这时候要考虑把一些任务迁到负载低的核上。但迁移任务不是随便迁的得看任务之间的依赖关系。如果两个任务之间有频繁的跨核通信把它们放在同一个核上反而更好。第二步看跨核通信的延迟。如果跨核Event或者Message的延迟很大说明核间同步开销太高。这时候要检查是不是自旋锁的临界区太长了或者跨核通信的频率太高了。我遇到过一个案例两个核之间每1ms就要同步一次每次同步都要获取自旋锁结果自旋锁的争用把性能拖垮了。后来改成每5ms同步一次中间用本地缓存顶着性能立刻上去了。第三步看任务的最大响应时间。对于硬实时任务最大响应时间必须小于截止时间。如果Trace显示某个任务偶尔会超时就要查是什么原因。可能是被高优先级任务抢占了可能是被中断延迟了也可能是跨核同步等太久了。找到原因之后要么调整优先级要么优化跨核通信要么把任务迁到干扰更少的核上。5.3 多核调试中的几个实用技巧第一个技巧用GPIO做时间戳。在关键代码段的开头和结尾翻转一个GPIO然后用示波器或者逻辑分析仪看波形。这个方法虽然原始但非常有效尤其是在Trace工具不好用的时候。我一般会留几个GPIO专门做调试用不接外设只接逻辑分析仪。第二个技巧给每个核分配独立的调试串口。如果条件允许每个核用一个独立的UART输出调试信息。这样你可以同时看到多个核的日志不用来回切换。如果UART不够可以用一个高速UART每个核的日志加上核ID前缀混在一起输出然后在PC端用脚本分离。第三个技巧在启动阶段加心跳信号。每个核在启动过程中定期翻转一个GPIO如果某个核的心跳停了说明它卡住了。这个方法可以快速定位是哪个核在启动阶段出了问题。第四个技巧用AUTOSAR OS的ErrorHook。AUTOSAR OS提供了ErrorHook回调当API调用返回错误时会触发。在多核环境下很多错误比如跨核ChainTask、跨核资源访问都会通过ErrorHook报出来。我一般会在ErrorHook里把错误码和出错的核ID打印出来这样调试的时候一目了然。6. 从项目实战看多核AUTOSAR OS的设计取舍6.1 一个域控制器项目的多核分配实例我拿一个实际做过的域控制器项目来举例。这个项目用的是三核MCU一个核跑通信CAN、LIN、以太网一个核跑控制电机控制、传感器融合一个核跑诊断和安全监控。通信核上我放了CAN接收任务、CAN发送任务、以太网协议栈任务、LIN主节点任务。这些任务的实时性要求不高但数据量大所以通信核的负载主要在数据搬运上。控制核上我放了电流环任务10kHz、速度环任务1kHz、位置环任务100Hz、传感器融合任务100Hz。这些任务的实时性要求极高尤其是电流环必须在100us内完成。诊断核上我放了UDS诊断任务、故障管理任务、安全监控任务。这些任务的实时性要求最低但逻辑复杂。核间通信方面通信核收到CAN帧之后通过跨核Message把解析后的信号发给控制核。控制核算完控制量之后通过跨核Message把状态发给通信核通信核再打包成CAN帧发出去。诊断核通过共享内存读取控制核和通信核的状态用于故障判断。这个分配方案的核心逻辑是把实时性要求相近的任务放在同一个核上把数据流方向一致的任务放在相邻的核上。这样跨核通信的次数最少每个核的调度也最稳定。6.2 多核设计中最容易犯的三个错误第一个错误把所有任务塞进一个OS-Application。我见过一个项目为了省事所有任务都在一个OS-Application里结果内存保护完全没起作用一个任务的野指针把整个系统搞崩了。正确的做法是按功能域划分OS-Application至少把安全相关和非安全相关的分开。第二个错误跨核共享变量不加保护。两个核同时读写一个全局变量不加自旋锁或者内存屏障结果读到的数据一半是新的一半是旧的。这种问题在调试的时候很难复现因为取决于两个核的时序。我的建议是跨核共享的数据要么用Message传递要么用自旋锁保护绝对不要裸共享。第三个错误忽略核间中断的优先级。核间中断也是中断也有优先级。如果核间中断的优先级设得太低跨核通信的延迟就会很大。如果设得太高又会干扰本地的高优先级任务。我一般会把核间中断的优先级设在中间偏上的位置既保证跨核通信的及时性又不干扰硬实时任务。6.3 多核AUTOSAR OS项目的配置工作流最后说一下配置工作流。AUTOSAR OS的配置通常用工具完成比如Elektrobit的EB tresos、Vector的DaVinci Configurator、ETAS的ISOLAR。这些工具的工作流大同小异。第一步定义OS-Application。确定每个OS-Application的名字、所属核、是否可信、包含哪些Task和ISR。第二步分配Task到核。在Task的属性里指定它属于哪个核。注意一个Task只能属于一个核不能跨核。第三步配置核间通信。定义跨核Event、跨核Message、自旋锁。这些对象的配置需要指定发送方和接收方工具会生成相应的通信代码。第四步配置启动同步。指定主核和从核配置启动顺序和同步信号。第五步生成OS代码。工具会根据配置生成OS的初始化代码、调度器代码、通信代码。生成之后你需要把这些代码集成到你的工程里然后编译、烧录、调试。这个工作流看起来简单但每一步都有很多细节。我的经验是配置阶段花的时间越多调试阶段花的时间越少。尤其是OS-Application的划分和Task的分配一定要在配置阶段想清楚不要等到跑起来再改。因为改配置意味着重新生成代码、重新编译、重新烧录整个流程走一遍至少半天。7. 一些个人体会和后续可以深入的方向多核AUTOSAR OS这块我最大的体会是它不是一个纯软件问题而是一个软硬件协同设计问题。你不能只盯着OS的API看还得理解硬件的核间通信机制、缓存一致性、中断路由。很多时候软件上看起来无解的性能问题换个硬件配置就解决了。另外一个体会是多核的复杂性主要来自不确定性。单核系统里任务的执行顺序是确定的你可以在脑子里模拟出来。多核系统里两个核的时序关系是不确定的你很难在脑子里模拟。所以多核开发必须依赖工具Trace、逻辑分析仪、调试器一个都不能少。后续如果大家感兴趣可以往这几个方向深入一是多核功能安全怎么用OS-Application的隔离机制满足ISO 26262的要求二是多核时间同步怎么用AUTOSAR的Global Time Synchronization机制让多个核的时钟对齐三是多核性能优化怎么通过任务迁移和通信优化把多核的算力榨干。这几个方向每一个都够写一篇长文有机会再展开聊。最后分享一个小技巧如果你刚开始做多核AUTOSAR OS建议先用一个简单的双核项目练手比如一个核跑LED闪烁一个核跑串口输出两个核之间用跨核Event同步。把这个最小系统跑通之后再往上加功能。这样你可以把多核的启动、同步、通信、调试这几个环节都走一遍踩的坑也都是小坑不会一上来就被大项目搞懵。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进