4. Runnable 与 Timing:Runnable Entity 创建、触发事件配置、Task Mapping 与调度

好,咱们进入第四章。这一章是 DaVinci Developer 里最核心的实操环节之一——Runnable。说白了,Runnable 就是 SWC 里真正干活的函数。你前面把 Port、Interface 都搭好了,但代码跑起来靠什么?就靠这些 Runnable。

我刚开始接触 AUTOSAR 时,总觉得 Runnable 不就是个函数嘛,随便配配就行。后来在一个实际项目中,因为触发事件配错了,导致信号延迟了 50ms,整车控制器直接报故障。嗯,从那以后我再也不敢小看这一步了。

4.1 什么是 Runnable Entity?

Runnable Entity,中文叫“可运行实体”。你可以把它理解成 SWC 内部的一个个任务片段。每个 Runnable 都是一段 C 函数代码,由 RTE 在特定时机调用。

举个例子:一个 SWC 负责采集车速信号,它内部会有三个 Runnable——一个负责初始化、一个负责周期采集、一个负责故障处理。每个 Runnable 都有自己的触发条件。

关键点:Runnable 不是 Task,它是被 Task 调用的逻辑单元。一个 Task 可以串行执行多个 Runnable。

4.2 创建 Runnable

在 DaVinci Developer 里创建 Runnable 很简单,但有几个坑要注意。

4.2.1 创建步骤

  1. 在 SWC 的编辑界面,找到 Runnables 标签页
  2. 右键点击空白区域,选择 New Runnable
  3. 填写 Runnable 名称(建议用 Rte_ 前缀 + 功能名)
  4. 选择 Runnable Type(稍后细说)
  5. 配置 Symbol Name(生成的 C 函数名)

我的习惯:Symbol Name 我一般写成 Rte_SWCName_FunctionName,这样在生成的代码里一眼就能看出属于哪个 SWC。

4.2.2 Runnable 类型

类型 说明 典型用途
Periodic 按固定周期触发 传感器采集、控制算法
OnEvent 由特定事件触发 数据到达、错误处理
OnInit SWC 初始化时执行一次 变量初始化、硬件配置
OnExit SWC 退出时执行一次 资源释放、状态保存

你想想看,如果选错了类型,比如把周期采集配成了 OnEvent,那采集函数可能永远不触发——因为没人给它发事件。我在一个项目中就见过这种低级错误,排查了两天才找到原因。

4.3 触发事件配置

触发事件决定了 Runnable 什么时候被调用。这是整个调度的基础。

4.3.1 常见触发事件

  • TimingEvent:按固定周期触发,最常用。配置时指定周期值(单位:秒)
  • DataReceivedEvent:当某个 Receiver Port 收到新数据时触发
  • DataSendCompletedEvent:当某个 Sender Port 发送完成时触发
  • OperationInvokedEvent:当 Client-Server 接口的 Operation 被调用时触发
  • ModeSwitchEvent:当 Mode Manager 切换模式时触发

避坑指南:我曾经把 TimingEvent 的周期设成了 0.001s(1ms),但底层 OS 的 Tick 周期是 10ms。结果 Runnable 根本没按 1ms 跑,而是按 10ms 跑。记住:Runnable 的周期不能小于 OS Tick 周期。

4.3.2 配置示例

假设我们要配一个 10ms 周期的采集 Runnable:

Runnable: Rte_Collector_ReadSensor
  Type: Periodic
  Trigger: TimingEvent
  Period: 0.01  // 10ms
  Symbol: Rte_Collector_ReadSensor

再配一个数据到达触发的处理 Runnable:

Runnable: Rte_Processor_HandleData
  Type: OnEvent
  Trigger: DataReceivedEvent
  Port: RPort_DataIn
  Symbol: Rte_Processor_HandleData

4.4 Task Mapping 与调度

Runnable 配好了,但谁来执行它?答案是 Task。Task 是 OS 层面的调度单位,Runnable 需要被映射到 Task 上才能运行。

4.4.1 Task 与 Runnable 的关系

说白了,Task 是“车”,Runnable 是“货”。车跑起来,货才能被送到目的地。一个 Task 可以拉多个 Runnable,但一个 Runnable 只能属于一个 Task。

概念 角色 特点
Task 调度执行者 由 OS 调度,有优先级、周期
Runnable 业务逻辑单元 由 RTE 调用,无调度属性
Mapping 绑定关系 决定 Runnable 在哪个 Task 里执行

4.4.2 映射原则

  • 同周期 Runnable 尽量放同一个 Task:减少 Task 切换开销
  • 高实时性 Runnable 放高优先级 Task:比如安全相关的逻辑
  • 避免 Runnable 间数据竞争:如果两个 Runnable 共享数据,最好放同一个 Task

我的经验:在 DaVinci Developer 里,Task Mapping 是在 Task Mapping Editor 里完成的。你可以把 Runnable 从左侧列表拖到右侧的 Task 里。拖拽时注意看底部的 Worst Case Execution Time (WCET) 预估,别让一个 Task 超载。

4.4.3 调度示例

假设我们有三个 Runnable:

  • Runnable_A:10ms 周期,采集传感器
  • Runnable_B:10ms 周期,处理数据
  • Runnable_C:100ms 周期,发送诊断信息

合理的映射方案:

Task_T10ms (优先级 5):
  - Runnable_A
  - Runnable_B

Task_T100ms (优先级 3):
  - Runnable_C

这样,10ms 的两个 Runnable 在同一个 Task 里顺序执行,100ms 的单独一个 Task。既保证了实时性,又减少了 Task 数量。

4.5 调度时序分析

配完 Task Mapping 后,一定要做时序分析。DaVinci Developer 提供了 Timing View 功能,可以直观看到 Runnable 的执行顺序和时间线。

我一般会检查以下几点:

  1. 是否有 Runnable 被遗漏:所有 Runnable 都必须映射到 Task
  2. Task 周期是否合理:Task 周期不能小于内部 Runnable 的最大周期
  3. 优先级是否冲突:高优先级 Task 不能长时间阻塞低优先级 Task

我曾经踩过的坑:在一个项目中,我把两个 10ms 的 Runnable 放到了不同 Task 里,结果因为 Task 切换开销,实际执行时间变成了 12ms。后来合并到同一个 Task,问题解决。记住:Task 不是越多越好。

4.6 本章知识体系

下面这张图总结了 Runnable 从创建到调度的完整流程:

Runnable 创建与调度流程 Runnable 创建与调度流程 创建 Runnable 选择类型、命名 配置触发事件 Timing/Data/Mode Task Mapping 绑定到 Task 调度执行 OS 按优先级/周期调度 时序分析 检查 WCET、延迟、冲突 注意事项 • 周期不能小于 OS Tick • 同周期放同一 Task • 高实时性高优先级 • 避免数据竞争

这张图把整个流程串起来了。从创建 Runnable 开始,到配置触发事件,再到 Task Mapping,最后是调度执行和时序分析。每一步都环环相扣。

4.7 小结

这一章我们讲了 Runnable 的创建、触发事件配置、Task Mapping 和调度。说白了,就是让 SWC 里的函数能按预期跑起来。记住几个关键点:

  • Runnable 是逻辑单元,Task 是执行单元
  • 触发事件决定了 Runnable 何时被调用
  • Task Mapping 决定了 Runnable 在哪个 Task 里执行
  • 时序分析是必不可少的步骤

嗯,这些内容在实际项目中会反复用到。配错了轻则功能异常,重则整车故障。所以每一步都要仔细。