4.2 顶层调度器——MainFunction的任务编排
Init建立骨架,MainFunction驱动每一轮
Dcm.c是DCM的最上层——文件不长(358行),但定义了诊断协议栈的入口和心跳。PduR通过六个回调入口把数据灌进DCM,而Dcm_MainFunction以固定周期(通常是每次ECU主任务调度的一次“诊断任务执行周期“)驱动DSL→DSD→DSP的三级流水线。
Dcm_Init:把配置的骨架注入运行态
void Dcm_Init(const Dcm_ConfigType *ConfigPtr) {
VALIDATE(DCM_MODULE_ID, DCM_INIT_ID, FALSE,
ConfigPtr != NULL_PTR, DCM_E_CONFIG_INVALID, DCM_E_UNINIT);
Dcm_ConfigPtr = ConfigPtr; // ← 全局配置指针,后面所有子模块都用它
// 初始化三层子模块
DslInit(); // DSL:初始化协议行、缓冲区、安全等级、会话
DsdInit(); // DSD:目前为空占位
DspInit(TRUE); // DSP:firstCall=TRUE → 复位所有服务状态机
dcmState = DCM_INITIALIZED;
}
配置指针Dcm_ConfigPtr指向的结构——Dcm_ConfigType——包含了整栋楼的蓝图:Dsp(DspDid[]、DspSession[]、DspSecurity[]……)、Dsd(DsdServiceTable[])、Dsl(DslProtocolRowList[]、DslBuffer[])。这个指针在后面的DID查询、SID路由、会话校验中会被反复解引用——它是一个全局不变的只读数据库。
Dcm_MainFunction:三层流水线的固定调度顺序
void Dcm_MainFunction(void) {
// 首次调用: 允许协议启动请求
if (dcmFirstCycle == TRUE) {
dcmFirstCycle = FALSE;
DcmSetProtocolStartRequestsAllowed(TRUE);
}
#ifdef DCM_USE_SPLIT_TASK_CONCEPT
// 分离任务模式: DSL+DspTimer在高优先级任务中执行
DslMain(); // ← DSL: 管理缓冲区、S3超时、传输完成
DspTimerMain(); // ← DSP: 安全访问延迟定时器减计数
#else
// 标准模式: 五步流水线
DspPreDsdMain(); // 1. DSP预处理: 周期DID、事件响应、boot跳转
DsdMain(); // 2. DSD: DsdHandleRequest() → SID路由
DspMain(); // 3. DSP: 处理异步服务 (复位、内存、读取DID、例行、安全……)
DspTimerMain(); // 4. DSP定时器: 安全访问延迟减计数
DslMain(); // 5. DSL: 管理缓冲区、S3超时、回传待发响应
Dcm_ROE_MainFunction(); // ResponseOnEvent 轮询
#endif
}
为什么是这个顺序?
DspPreDsdMain跑在最前面——因为它需要在新一轮请求被路由之前完成“上一轮请求的后续动作“(比如boot跳转的发后响应、ResponseOnEvent的DID轮询)。
DsdMain在中间——它检查有没有新的请求到达(dsdDslDataIndication == TRUE)——如果有则路由到对应服务。
DspMain在后——它执行DSP里所有异步状态机的推进步——上一次请求因为E_PENDING还没有完成,本次MainFunction把剩下的部分走完。
DspTimerMain跟着——它递减安全访问的延迟定时器——不依赖其他模块状态。
DslMain最后——它处理当前缓冲区的状态、管理S3会话超时、把DSD标记的“响应已就绪“的Tx数据传给PduR传输。
六个PduR回调入口——数据从哪里进来
DCM不主动拉数据——PduR把数据推给DCM。推的方式通过六个回调函数:
// 1. 传输层开始接收——DCM分配缓冲区
BufReq_ReturnType Dcm_StartOfReception(PduIdType dcmRxPduId, ...) {
return DslStartOfReception(dcmRxPduId, tpSduLength, rxBufferSizePtr, FALSE);
}
// 2. 传输层逐段拷贝数据
BufReq_ReturnType Dcm_CopyRxData(PduIdType dcmRxPduId, ...) {
return DslCopyDataToRxBuffer(dcmRxPduId, pduInfoPtr, rxBufferSizePtr);
}
// 3. 传输层通知接收完成
void Dcm_TpRxIndication(PduIdType dcmRxPduId, NotifResultType result) {
DslTpRxIndicationFromPduR(dcmRxPduId, result, FALSE, FALSE);
}
// 4. DCM回传响应数据给传输层
BufReq_ReturnType Dcm_CopyTxData(PduIdType dcmTxPduId, ...) {
return DslCopyTxData(dcmTxPduId, pduInfoPtr, FALSE, txDataCntPtr);
}
// 5. 传输层通知发送完成
void Dcm_TpTxConfirmation(PduIdType dcmTxPduId, NotifResultType result) {
DslTpTxConfirmation(dcmTxPduId, result);
}
// 6. ComM模式变更通知——DCM静默/全通信模式切换
void Dcm_ComM_FullComModeEntered(uint8 Network) {
DslComModeEntered(Network, DCM_FULL_COM);
}
这六个回调的职责清晰地划分了DCM和传输层的边界:DCM不关心数据是怎么从CAN/以太网来的——DCM只负责在分配的缓冲区里消费数据。
架构洞察:为什么Dcm.c这么薄
Dcm.c只有358行,因为它的全部职责就是编排——它知道什么时候该调谁、把什么全局状态给到谁。具体逻辑全部下沉到Dsl/Dsd/Dsp。这是AUTOSAR架构的一个典型特征:模块文件充当入口,子模块文件承载逻辑。
本篇小结
Dcm_Init存储全局配置指针并初始化三层子模块——DSL建立缓冲区运行时数据、DSD复位状态、DSP复位所有服务状态机。Dcm_MainFunction的调用顺序是经过深思熟虑的五步流水:预处理→路由→异步服务→定时器→会话/缓冲区管理。- 六个PduR回调定义了DCM和传输层的刚性分界——DCM不碰CAN帧,只消费准备好的SDU数据。
- Dcm.c作为薄编排层是对AUTOSAR三层架构的忠实体现。
【下集预告】:Dcm.c让你看到了整体调度——但数据从传输层进入DCM之后,第一件发生的事发生在什么地方?DSL。Diagnostic Session Layer——缓冲区在哪里分配、Rx/Tx状态机的六个态是怎么流转的、S3会话定时器在哪里倒计数——我们拆Dsl,1828行,一步一步看。