文章目录
- 1. IOC概念与定位
- 1.1 与其他通信机制的对比
- 1.2 IOC的两种工作模式
- 2. 数据结构层级
- 2.1 核心配置类型
- 2.2 发送方/接收方配置
- 2.3 数据类型支持
- 2.4 队列节点(仅Send/Receive模式)
- 3. 锁机制(核间同步核心)
- 3.1 跨核锁的实现
- 3.2 释放顺序
- 4. 四种API接口梳理
- 4.1 API总览
- 4.2 公共 API 层(含内存保护)
- 4.3 Write/Read 内部实现(单值模式)
- 4.4 Send/Receive 内部实现(队列模式)
- 4.5 队列操作的宏实现
- 5. 跨核消息传递完整流程
- 5.1 跨核 Send 场景时序
- 5.2 关键设计点
- 6. 初始化流程
- 7. 错误码与状态机
- 7.1 错误码定义
- 7.2 丢失状态机
1. IOC概念与定位
IOC(Inter OS-Application Communication,应用间通信) 是 AUTOSAR OS 提供的轻量级数据通信机制,用于在不同 OS-Application 之间(含跨核的 OS-Application)传递数据。
1.1 与其他通信机制的对比
| 机制 | 适用场景 | 是否阻塞 | 是否跨核 | 依赖OS资源 |
| Event | 同核任务间同步 | 可阻塞 | 否 | Task |
| IOC | 应用间数据传递(含跨核) | 非阻塞 | 支持 | App 权限 + 可选 Spinlock |
| Counter/Alarm | 定时触发 | 非阻塞 | 否 | Counter |
| Message(COM) | RTE 层消息(含 DLC、超时) | 可阻塞 | 支持 | COM模块 |
IOC 的核心定位:OS 层提供的、非阻塞的、支持跨核的、类型安全的数据搬运通道,不依赖 RTE,适合 BSW 内部或应用层直接使用。
1.2 IOC的两种工作模式
| 模式 | API | 缓冲结构 | 场景 |
| Write/Read(单值模式) | Os_Ioc_Write / Os_Ioc_Read | 单个变量缓冲(dataType->buffer) | 最新值覆盖,无队列 |
| Send/Receive(队列模式) | Os_Ioc_Send / Os_Ioc_Receive | 环形队列(Os_QueueType_*) | FIFO 缓冲,支持丢包标记 |
2. 数据结构层级
2.1 核心配置类型
┌──────────────────────────────────────────────────────────────────┐ │Os_IocCfgType(每个 IOC 的静态配置)│ ├──────────────────────────────────────────────────────────────────┤ │ boolean sysCall 是否需走 SysCall(内存保护场景) │ │ Os_IocDataType*dataType ──→{dataType,dataSize,buffer}│ │ uint8 dataTypeCnt 数据类型组数(Write/Read 用) │ │ uint8 lockType IOC_LOCKTYPE_NONE/INT/INT_SPINLOCK │ │ SpinlockIdType spinlockId 跨核时用的 Spinlock ID │ │ uint32 bufferLength 队列长度 │ │ Os_IocTxCfgType*iocTxCfg ──→{accessingApps,callBack}[]│ │ uint32 senderCnt 发送方数量 │ │ Os_IocRxCfgType*iocRxCfg ──→{accessingApps}│ │ Os_IocDynType*iocDyn ────→{iocState}运行态(丢失标志) │ │ IocQueueInit func 队列初始化回调(NULL=非队列模式) │ └──────────────────────────────────────────────────────────────────┘2.2 发送方/接收方配置
Os_IocTxCfgType(每个发送方)Os_IocRxCfgType(接收方)┌──────────────────────────┐ ┌──────────────────────────┐ │ uint32 accessingApps │ │ uint32 accessingApps │ │ ReceiverPullCB callBack │ └──────────────────────────┘ └──────────────────────────┘accessingApps 是 App 位图,发送/接收时 OS 会检查 OS_CORE_GETAPPID(coreId) 是否在此位图中,实现访问权限控制。
2.3 数据类型支持
IOC_DATATYPE_BL(0)booleanIOC_DATATYPE_S8(5)sint8IOC_DATATYPE_U8(1)uint8IOC_DATATYPE_S16(6)sint16IOC_DATATYPE_U16(2)uint16IOC_DATATYPE_S32(7)sint32IOC_DATATYPE_U32(3)uint32IOC_DATATYPE_S64(8)sint64IOC_DATATYPE_U64(4)uint64IOC_DATATYPE_FL(9)float32IOC_DATATYPE_DB(10)float64IOC_DATATYPE_PTR(11)自定义结构(按字节拷贝)2.4 队列节点(仅Send/Receive模式)
每种数据类型对应一个 Os_QueueType_* 结构 ,本质是环形队列:
typedefstruct{uint16 writeId;// 写位置uint16 readId;// 读位置uint16 size;// 队列容量T*data;// 数据缓冲指针}Os_QueueType_T;3. 锁机制(核间同步核心)
IOC 的跨核同步完全依赖 Os_Ioc_Lock/ULock 的三种锁策略:
| 锁类型 | 值 | 适用场景 | 实现 |
| IOC_LOCKTYPE_NONE | 0 | 单核单发送方、无并发风险 | 无锁 |
| IOC_LOCKTYPE_INT | 1 | 同核多任务、单核内同步 | Os_Arch_IntDisable/Enable |
| IOC_LOCKTYPE_INT_SPINLOCK | 2 | 跨核场景 | 关中断 + Spinlock |
3.1 跨核锁的实现
staticFUNC(void,OS_CODE)Os_Ioc_Lock(...){if(IOC_LOCKTYPE_INT==iocCfg->lockType){Os_Arch_IntDisable(intState);// 仅关本核中断}elseif(IOC_LOCKTYPE_INT_SPINLOCK==iocCfg->lockType){Os_Arch_IntDisable(intState);// 1. 先关本核中断Os_Spinlock_IntlGetSpinlock(iocCfg->spinlockId,coreId);// 2. 获取自旋锁}}为什么需要先关中断再获取 Spinlock?
- 防止本核 ISR 抢占任务后再次尝试获取同一 Spinlock 导致自死锁
- Spinlock 本身是跨核互斥,但关中断是本核操作,二者配合保证原子性
3.2 释放顺序
staticFUNC(void,OS_CODE)Os_Ioc_ULock(...){if(IOC_LOCKTYPE_INT_SPINLOCK==iocCfg->lockType){Os_Arch_IntEnable(*intState);// 1. 先释放 SpinlockOs_Spinlock_IntlReleaseSpinlock(iocCfg->spinlockId,coreId);}elseif(IOC_LOCKTYPE_INT==iocCfg->lockType){Os_Arch_IntEnable(*intState);// 2. 再开中断}}Note:
代码中 ULock 的 Spinlock 路径里,先 IntEnable 再 ReleaseSpinlock,这与 Lock 的顺序相反,是有意的:保证在持有 Spinlock 期间恢复中断状态,避免不必要的关中断时间过长。
4. 四种API接口梳理
4.1 API总览
| API | 模式 | 方向 | 缓冲行为 |
| Os_Ioc_Write | Write/Read | 发送方→缓冲 | 覆盖最新值 |
| Os_Ioc_Read | Write/Read | 缓冲→接收方 | 读取当前值 |
| Os_Ioc_Send | Send/Receive | 发送方→队列 | 入队 FIFO |
| Os_Ioc_Receive | Send/Receive | 队列→接收方 | 出队 FIFO |
| Os_Ioc_EmptyQueue | Send/Receive | - | 清空队列 |
| Os_Ioc_Init | 通用 | - | 初始化 |
| Os_Ioc_ClearQueue | 内部 | - | 清空队列实现 |
4.2 公共 API 层(含内存保护)
以 Os_Ioc_Write 为例:
FUNC(Std_ReturnType,OS_CODE)Os_Ioc_Write(iocCfg,senderId,data){// 1. 参数检查if((NULL_PTR!=iocCfg)&&(NULL_PTR!=data)&&(senderId<iocCfg->senderCnt)){// 2. 内存保护场景:走 SysCall#if(OS_MEMORY_PROTECTION==STD_ON)if(TRUE==iocCfg->sysCall){Os_SysCallParamType sysCallParam;sysCallParam.Os_IocWrite.iocCfg=iocCfg;sysCallParam.Os_IocWrite.senderId=senderId;sysCallParam.Os_IocWrite.data=data;Os_SysCall(Os_Syscall_IocWrite_ID,&sysCallParam);// ★ 陷入内核态ret=sysCallParam.Os_IocWrite.retVal;}else#endif{// 3. 非内存保护或可信 App:直接调用ret=Os_Ioc_IntlWrite(iocCfg,senderId,data);}}}SysCall 路径:
staticFUNC(void,OS_CODE)Os_SysCall_IocWrite(parameter){iocCfg=parameter->Os_IocWrite.iocCfg;senderId=parameter->Os_IocWrite.senderId;data=parameter->Os_IocWrite.data;ret=Os_Ioc_IntlWrite(iocCfg,senderId,data);// 最终也调到内部函数parameter->Os_IocWrite.retVal=ret;}为什么需要 SysCall?
非信任 App 访问 IOC 时,IOC 缓冲区属于其他 App 或系统区,需切换到特权模式才能访问共享内存。SysCall 通过 trap 指令进入特权态执行。
4.3 Write/Read 内部实现(单值模式)
Os_Ioc_IntlWrite 流程:
1.获取 coreId2.检查 App 权限:OS_APPMATCH(accessingApps,currentAppId)├─ 不匹配 → IOC_E_NOK └─ 匹配 → 继续3.加锁:Os_Ioc_Lock()4.遍历 dataTypeCnt 个数据类型: ├─ 按 dataType 分支:直接赋值(标量)或逐字节拷贝(PTR) └─*buffer=*dataSrc5.解锁:Os_Ioc_ULock()6.返回 IOC_E_OK特点:直接覆盖缓冲区最新值,无队列,无满/空判断,适用于"最新值生效"的场景(如传感器数据)。
4.4 Send/Receive 内部实现(队列模式)
Os_Ioc_IntlSend 流程:
1.获取 coreId2.检查 App 权限3.加锁4.检查队列满:OS_QUEUE_ISFULL()├─ 满 → ret=IOC_E_LIMIT,iocState=IOC_STATE_LOST ★ 标记丢失 └─ 未满 →OS_QUEUE_PUSH()5.解锁6.返回Os_Ioc_IntlReceive 流程:
1.获取 coreId2.检查 App 权限3.加锁4.检查丢失标志:if(IOC_STATE_LOST==iocDyn->iocState)└─ ret=IOC_E_LOST_DATA ★ 告知接收方有数据丢失5.检查队列空:OS_QUEUE_ISEMPTY()├─ 空 → ret=IOC_E_NO_DATA └─ 非空 →OS_QUEUE_POP()6.清除丢失标志:iocState=IOC_STATE_OK ★ 一次丢失只报告一次7.解锁8.返回关键设计:
- 数据丢失检测:发送满时标记 IOC_STATE_LOST,接收时优先返回 IOC_E_LOST_DATA,接收成功后清除标志
- 返回值层次:IOC_E_LOST_DATA(64) > IOC_E_NO_DATA(131) > IOC_E_OK(0)
4.5 队列操作的宏实现
/* 入队 */#defineOS_QUEUE_PUSH(name,value)\do{\*(name->data+name->writeId)=value;\name->writeId++;\if(name->writeId==name->size)\name->writeId=OS_QUEUE_INIT;/* 环形回绕 */\}while(0)/* 出队 */#defineOS_QUEUE_POP(name,value)\do{\*(value)=(name->data[name->readId]);\name->readId++;\if(name->readId==name->size)\name->readId=OS_QUEUE_INIT;/* 环形回绕 */\}while(0)/* 判空:读写指针相等 */#defineOS_QUEUE_ISEMPTY(name)(name->readId==name->writeId)/* 判满:写指针追上读指针(差1) */#defineOS_QUEUE_ISFULL(name)(((readId==0)&&(writeId==size-1))||(readId-writeId==1))环形队列容量:实际可用容量 = size - 1(保留一个空位以区分满和空)
5. 跨核消息传递完整流程
5.1 跨核 Send 场景时序
假设:Core0 的 TaskA(属于 App0)向 Core1 的 TaskB(属于 App1)发送数据。
Core0(发送方)Core1(接收方)───────────── ───────────── TaskA 调用Ioc_Send(iocId,senderId,data)│ ▼Os_Ioc_Send()├─ 参数检查 ├─ sysCall=true →Os_SysCall(IocSend_ID)│ │ │ ▼ Trap #6│Os_SysCall_IocSend()│ │ │ ▼ │Os_Ioc_IntlSend()│ ├─ App 权限检查(App0 in accessingApps?) │ ├─Os_Ioc_Lock()★ 关本核中断+获取 Spinlock │ │ ├─Os_Arch_IntDisable()│ │ └─Os_Spinlock_IntlGetSpinlock(spinlockId,0)│ │ └─ 忙等待直到 Core1 释放(若被占用) │ ├─OS_QUEUE_ISFULL()?│ │ ├─ 满 → iocState=LOST,ret=E_LIMIT │ │ └─ 未满 →OS_QUEUE_PUSH()★ 写入共享内存 │ └─Os_Ioc_ULock()│ ├─Os_Arch_IntEnable()│ └─Os_Spinlock_IntlReleaseSpinlock()★ 释放 Spinlock └─ 返回 TaskB 调用Ioc_Receive(iocId,data)│ ▼Os_Ioc_Receive()├─ sysCall →Os_SysCall(IocReceive_ID)│ │ │ ▼ │Os_Ioc_IntlReceive()│ ├─ App 权限检查(App1 in accessingApps?) │ ├─Os_Ioc_Lock()★ 关本核中断+Spinlock │ ├─if(IOC_STATE_LOST)→ E_LOST_DATA │ ├─OS_QUEUE_ISEMPTY()?│ │ ├─ 空 → E_NO_DATA │ │ └─ 非空 →OS_QUEUE_POP()★ 从共享内存读 │ ├─ iocState=OK │ └─Os_Ioc_ULock()└─ 返回5.2 关键设计点
- 共享内存要求:IOC 的 buffer 必须位于多核共享的非缓存内存区(OS_START_SEC_GLOBAL_NOCACHE_VAR,见 Os_Ioc_Lcfg.c:48)。NOCACHE 是关键:若缓存,Core0 写入 Core1 看不到。
- Spinlock 是唯一跨核同步原语:IOC 跨核场景必须配置 lockType = IOC_LOCKTYPE_INT_SPINLOCK,复用 OS 的 Spinlock 机制(之前梳理过的 Os_Spinlock 模块)。
- 非阻塞设计:所有 IOC API 都不阻塞调用者,发送满返回 E_LIMIT,接收空返回 E_NO_DATA,调用者需自行重试或处理。
- 无主动通知:IOC 不主动通知接收方有数据到达,接收方需轮询或配合 Alarm/Task 周期性调用 Receive。callBack 字段虽定义但代码中未见调用,属于预留接口。
- 多发送方支持:iocTxCfg[] 数组支持多个发送方,每个发送方有独立权限位图和 senderId,适用于多核多任务向同一 IOC 写入的发布订阅场景。
6. 初始化流程
主核负责初始化所有 IOC:
#if(OS_CFG_IOC_ENABLE==STD_ON)if(TRUE==coreCfg->coreIfMaster){for(objectIdx=OS_ZERO_VALUE;objectIdx<IOC_CFG_COUNTER;objectIdx++){Os_Ioc_Init(&Os_IocCfg[objectIdx]);}}#endif1.iocState=IOC_STATE_OK// 清丢失标志2.if(func!=NULL)iocInitFunc();// 队列模式:调用生成的初始化函数// 设置 writeId=readId=0, size=队列长度, data=&Buffer7. 错误码与状态机
7.1 错误码定义
| 错误码 | 值 | 含义 |
| IOC_E_OK | 0 | 成功 |
| IOC_E_NOK | 1 | 参数错误或权限错误 |
| IOC_E_LOST_DATA | 64 | 接收成功但有数据丢失过 |
| IOC_E_LIMIT | 130 | 发送时队列满 |
| IOC_E_NO_DATA | 131 | 接收时队列空 |
7.2 丢失状态机
Send(队列满)OK ──────────────────→ LOST ↑ │ │ │Receive(返回E_LOST_DATA后)└───────────────────────┘