Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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架构的一个典型特征:模块文件充当入口,子模块文件承载逻辑


本篇小结

  1. Dcm_Init存储全局配置指针并初始化三层子模块——DSL建立缓冲区运行时数据、DSD复位状态、DSP复位所有服务状态机。
  2. Dcm_MainFunction的调用顺序是经过深思熟虑的五步流水:预处理→路由→异步服务→定时器→会话/缓冲区管理。
  3. 六个PduR回调定义了DCM和传输层的刚性分界——DCM不碰CAN帧,只消费准备好的SDU数据。
  4. Dcm.c作为薄编排层是对AUTOSAR三层架构的忠实体现。

【下集预告】:Dcm.c让你看到了整体调度——但数据从传输层进入DCM之后,第一件发生的事发生在什么地方?DSL。Diagnostic Session Layer——缓冲区在哪里分配、Rx/Tx状态机的六个态是怎么流转的、S3会话定时器在哪里倒计数——我们拆Dsl,1828行,一步一步看。