3. RTA-OS工程结构解析
在把 RTA-OS 集成进一个 ECU 项目时,最先遇到的并不是 API 怎么调用,而是“文件该放哪、哪些能改、哪些一改就被覆盖”。RTA-OS 的工程结构本质上是一条从配置到生成、再到编译链接的单向流水线:工程师只维护少量输入文件,其余大量 C 代码、头文件和汇编桩由生成器产出。理解这条边界,是后续所有集成工作的前提。
一、工程目录组织
RTA-OS 本身以“安装目录 + 工程目录”两层存在。安装目录(例如 C:\ETAS\RTA-OS3.x)存放编译器适配、目标端口、模板和工具链,属于只读资源;工程目录才是每个 ECU 项目独立维护的部分。一个典型的工程目录组织如下:
MyEcuProject/
├── config/
│ ├── MyEcu.oil # OIL 配置输入(手写)
│ └── MyEcu.arxml # 可选:ARXML 导入源
├── generated/
│ ├── Os_Cfg.c / Os_Cfg.h # 生成:配置数据
│ ├── Os_Lcfg.c / Os_Lcfg.h # 生成:链接期配置
│ ├── Os_Callout.c # 生成:回调桩(可被用户实现覆盖)
│ └── Os_MemMap.h # 生成:内存段映射
├── src/
│ ├── Os_Callout_User.c # 手写:回调实现
│ ├── Os_App_Task.c # 手写:任务体
│ └── Os_Hook_Error.c # 手写:错误钩子
├── build/
│ ├── Makefile # 构建脚本
│ └── rtaos.mk # 生成器调用规则
└── out/
└── MyEcu.elf # 链接产物
关键约定是:config/ 与 src/ 属于“人写的”,generated/ 属于“机器写的”,out/ 属于“可丢弃的”。把生成目录纳入版本管理通常没有必要,但把生成目录与手写目录混放,会导致清理构建时误删手写代码,这是最常见的工程事故之一。
二、配置文件类型
RTA-OS 的配置输入主要有三类。第一类是 OIL 文件(OSEK Implementation Language),它是文本形式的声明式配置,描述 OS 对象及其属性,例如:
CPU MyCpu {
OS MyOs {
STATUS = EXTENDED;
STARTUPHOOK = TRUE;
ERRORHOOK = TRUE;
PRETASKHOOK = FALSE;
};
TASK Task_10ms {
PRIORITY = 10;
ACTIVATION = 1;
SCHEDULE = FULL;
AUTOSTART = FALSE;
};
ALARM Alarm_10ms {
COUNTER = SystemCounter;
ACTION = ACTIVATETASK { TASK = Task_10ms; };
};
COUNTER SystemCounter {
MINCYCLE = 1;
MAXALLOWEDVALUE = 65535;
TICKSPERBASE = 1;
};
};
第二类是 ARXML 文件,用于从 AUTOSAR 系统描述中导入 OS 相关配置,适合与上层 ECU 配置工具链对接。第三类是 生成器选项文件(如 .rtaos 或命令行参数),控制目标编译器、内存模型、是否生成调试信息等。OIL 决定“系统里有什么”,选项文件决定“生成成什么样”,两者职责不能混淆。
三、生成代码与手写代码的边界
RTA-OS 生成器读取 OIL 后,会产出配置数据、链接期常量、回调桩和内存映射。这些文件顶部通常带有“generated, do not edit”标记。边界规则可以概括为:凡是生成器能推导出来的,都不要手写;凡是生成器留出接口的,才由用户实现。
以回调为例,OIL 中声明 ERRORHOOK = TRUE 后,生成器会在 Os_Callout.c 中给出弱符号桩,用户应在 src/Os_Hook_Error.c 中提供强符号实现:
/* src/Os_Hook_Error.c —— 手写,覆盖生成桩 */
#include "Os.h"
FUNC(void, OS_CODE) ErrorHook(StatusType Error)
{
/* 记录错误码,点亮故障灯,进入安全状态 */
(void)Error;
Dem_SetEventStatus(DEM_EVENT_OS_ERROR, DEM_EVENT_STATUS_FAILED);
}
如果误把实现写进 generated/Os_Callout.c,下一次重新生成就会被覆盖,且编译可能因重复定义失败。判断某个文件能否修改,最可靠的方法是看它是否位于生成目录、是否带生成标记、是否在构建脚本中被列为生成目标。
四、构建脚本的组成
构建脚本把上述目录和文件串成可重复执行的流水线。一个最小可用的 Makefile 通常包含四段:变量定义、生成规则、编译规则、链接与清理。下面给出与上图对应的示例片段:
RTAOS_DIR := C:/ETAS/RTA-OS3.x
CC := tricore-gcc
OIL := config/MyEcu.oil
GEN_DIR := generated
SRC_DIR := src
OUT_DIR := out
INCLUDES := -I$(GEN_DIR) -I$(RTAOS_DIR)/include -I$(RTAOS_DIR)/target/tc2xx
# 1) 生成规则:OIL 变化才重新生成
$(GEN_DIR)/Os_Cfg.c: $(OIL)
rtaosgen -o $(GEN_DIR) -t tc2xx $(OIL)
# 2) 编译规则:手写与生成代码统一编译
$(OUT_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(INCLUDES) -c $< -o $@
$(OUT_DIR)/%.o: $(GEN_DIR)/%.c
$(CC) $(INCLUDES) -c $< -o $@
# 3) 链接
$(OUT_DIR)/MyEcu.elf: $(OBJS)
$(CC) $(OBJS) -L$(RTAOS_DIR)/lib -lrtaos_tc2xx -o $@
# 4) 清理:只删产物,不碰手写源码
clean:
rm -rf $(OUT_DIR) $(GEN_DIR)
这里有两个容易踩的坑。第一,生成规则必须把 OIL 作为依赖,否则改了配置却不会重新生成,现象是“配置明明改了,行为没变”。第二,clean 只能删除 out/ 和 generated/,绝不能把 src/ 或 config/ 纳入删除范围。建议在脚本中显式列出生成文件清单,而不是用通配符删除整个工程目录。
generated/Os_Cfg.c 中的优先级或激活次数。这些值来自 OIL,下次生成即被覆盖,且会造成配置与代码不一致,排查时极难定位。正确做法是回到 OIL 修改并重新生成。把目录、配置、代码边界和构建脚本四者对齐之后,RTA-OS 的集成就从“一堆看不懂的文件”变成了“输入—生成—编译—链接”的可控流程。后续无论是增加任务、调整调度还是接入诊断,都可以沿着这条链路定位改动点:改 OIL、重新生成、编译链接、观察运行结果。