news 2026/9/3 16:48:23

MCAL、ECU Abstraction、Service、RTE、ASW 到底怎么分工?代码到底该放哪一层?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MCAL、ECU Abstraction、Service、RTE、ASW 到底怎么分工?代码到底该放哪一层?

MCAL、ECU Abstraction、Service、RTE、ASW 到底怎么分工?代码到底该放哪一层?

上一篇我们把 AUTOSAR Classic 的完整软件架构过了一遍。

如果还没看,可以先看这里:

一张图看懂 AUTOSAR Classic 完整软件架构

上一篇解决的是:

AUTOSAR Classic 里面到底有哪些层,每一层大概有什么东西。

但实际做项目的时候,真正让人头疼的往往不是“MCAL 是什么”“RTE 是什么”。

而是这种问题:

我现在要写一段代码,这段代码到底应该放哪里?

比如来了一个非常普通的需求:

KL15 接在 MCU 的 P33.0 上。
当 KL15 为高电平时,VCU 认为整车已经上低压,开始执行后续状态切换。

如果是一个普通裸机项目,这件事可能几分钟就写完了:

if(Dio_ReadChannel(KL15_PIN)==STD_HIGH){VehicleState=VEHICLE_STATE_WAKEUP;}

代码能不能跑?

当然能。

问题是:

这段代码应该写在 ASW 里面吗?

如果不应该,那:

Dio_ReadChannel()

应该在哪里调用?

P33.0 和 KL15 的对应关系应该谁知道?

“KL15 有效以后进入 Wakeup”又应该谁负责?

这才是 AUTOSAR 分层真正开始有意思的地方。


一、先别急着背层级,先拆一个 KL15

我们先把刚才那个需求拆开。

实际硬件是:

KL15 │ │ 12V / 电气信号 ▼ 输入电路 │ ▼ MCU P33.0

软件需要最终得到:

KL15 = ON

然后应用软件根据这个状态决定:

车辆进入唤醒状态

乍一看,这就是一个功能。

但如果仔细拆,会发现里面其实混了三件完全不同的事情。

第一件:

P33.0 当前是高电平还是低电平?

第二件:

P33.0 这个输入,在这块 ECU 上代表的是 KL15。

第三件:

KL15 有效以后,车辆应该做什么。

这三件事看起来很接近,实际上属于三个完全不同的抽象层级。

比较合理的关系应该是:

P33.0 │ ▼ Dio │ ▼ IoHwAb / ECU Abstraction │ ▼ RTE │ ▼ VehicleState SWC

把它翻译成人话:

Dio: 我只知道 P33.0 是高还是低。 IoHwAb: 我知道这个输入在 ECU 上叫 KL15。 ASW: 我只关心 KL15 有没有上电。 至于 KL15 接 P33.0 还是 P20.4,我不关心。

这就是 AUTOSAR 分层真正想解决的问题。


二、MCAL 最好不要知道“KL15”是什么

先从最下面说起。

P33.0 是 MCU 的一个具体 Pin。

真正读取它的,是 MCAL 里面的 Dio Driver。

类似:

Dio_LevelType level;level=Dio_ReadChannel(DioConf_DioChannel_P33_0);

Dio Driver 知道:

PORT33 Pin0 Input High / Low

但从软件架构上讲,它最好不知道:

这个 Pin 是 KL15

为什么?

因为“KL15”是 ECU 的电气功能定义。

而:

P33.0

是 MCU 的硬件资源定义。

这两件事情不是一个层级。

今天的原理图可能是:

KL15 → P33.0

下一版硬件有可能变成:

KL15 → P20.4

如果应用代码里面到处都是:

Dio_ReadChannel(DioConf_DioChannel_P33_0);

那硬件一改,应用代码也得跟着改。

这就意味着:

应用层已经和 MCU 硬件绑死了。

这显然不是 AUTOSAR 想要的结果。


三、那谁负责把 P33.0 变成 KL15?

这时候就到了 ECU Abstraction。

上一篇我们已经讲过 ECU Abstraction 是什么,这里不再重复定义。

这次只看它在项目里到底干什么。

可以做一个非常简单的封装:

Std_ReturnTypeIoHwAb_GetKl15State(boolean*state){Dio_LevelType level;level=Dio_ReadChannel(DioConf_DioChannel_KL15);if(level==STD_HIGH){*state=TRUE;}else{*state=FALSE;}returnE_OK;}

从这层开始,软件里面第一次真正出现:

KL15

而不是:

P33.0

这一步非常关键。

因为从现在开始,上层软件看到的已经不再是 MCU 引脚。

而是 ECU 的功能信号:

KL15 BrakeSwitch WakeupInput IgnitionState CrashInput

这就是为什么叫:

ECU Abstraction。

它抽象掉的已经不是单纯的 MCU,而是:

这块 ECU 的具体硬件连接。


四、ASW 最好连“Dio”这个单词都不要看到

继续往上。

现在 VehicleStateManager 需要知道 KL15 状态。

如果直接这么写:

if(Dio_ReadChannel(DioConf_DioChannel_KL15)==STD_HIGH){VehicleState=VEHICLE_STATE_WAKEUP;}

短期看没什么问题。

甚至很多实际项目里确实有人这么写。

但从架构上看,这里已经出现了一个很明显的问题:

ASW ↓ 直接调用 ↓ MCAL

中间所有抽象层都被绕过去了。

这意味着 VehicleStateManager 必须知道:

KL15 是一个 DIO

这其实已经不应该是 ASW 关心的事情了。

因为对于应用软件来说:

KL15 是通过 DIO 采集的

和:

KL15 是通过 SPI SBC 读取的

理论上没有区别。

应用只应该看到:

Kl15State = TRUE

至于这个 TRUE 是怎么来的,是硬件层的事情。

所以更合理的应用代码可能只是:

boolean kl15State;Rte_Read_Kl15State(&kl15State);if(kl15State==TRUE){VehicleState=VEHICLE_STATE_WAKEUP;}

这时候 ASW 终于干净了。

它只处理:

KL15有效 ↓ 车辆状态切换

没有:

P33.0 Dio PORT Input Channel

这些东西。


五、这时候再看 RTE,就容易理解多了

很多人学 AUTOSAR 时,最容易把 RTE 学得很玄乎。

其实放到刚才这个例子里就很简单。

下面已经有一个:

KL15 State

上面的 VehicleState SWC 需要它。

中间谁负责把数据接起来?

RTE。

于是:

IoHwAb │ │ KL15 State ▼ RTE │ ▼ VehicleState SWC

应用里面只看到:

Rte_Read_Kl15State(&kl15State);

甚至它都不知道:

这个数据到底是谁提供的。

这就是 RTE 非常重要的一个价值:

让软件组件之间不需要互相知道对方的具体实现。


六、那为什么不能所有东西都直接 Rte_Read?

这里再补一个实际项目里很容易产生的误区。

有人看到这里可能会觉得:

那我是不是所有输入全部做成 Rte_Read 就完事了?

也不是。

AUTOSAR 分层不是为了“所有函数外面套一个 Rte_”。

真正重要的是:

不同的软件层,只知道自己应该知道的信息。

比如还是 KL15。

MCAL 应该知道:

哪个 Port 哪个 Pin 怎么读硬件

ECU Abstraction 应该知道:

这个 Pin 在 ECU 上是什么功能

ASW 应该知道:

KL15 有效以后车辆怎么响应

RTE 只是把这些不同世界连接起来。

所以分层的重点不是 API 长什么样。

而是:

知识边界。

谁应该知道什么。

谁不应该知道什么。

这个思维比背 AUTOSAR 模块分类重要得多。


七、再看一个 ADC,问题会更明显

KL15 还算简单。

换一个稍微复杂一点的。

比如 VCU 上有一路模拟量:

油门踏板传感器。

硬件可能是:

0~5V ↓ MCU ADC Channel

现在问一个问题:

ADC 采集到的 Raw Value 转成实际电压,应该放哪一层?

再进一步:

电压转成 0~100% 的踏板开度,又应该放哪一层?

这两个事情其实并不一样。

假设 ADC 读取:

Adc_ValueGroupType adcRaw;Adc_ReadGroup(...,&adcRaw);

得到:

2456

这是一个纯硬件量。

它属于 MCAL 能理解的世界。

继续往上:

2456 ↓ 2.31 V

这是硬件电气量转换。

这类事情通常更适合放在:

IoHwAb / ECU Abstraction

再往上:

2.31 V ↓ 43.5%

这时候就开始接近功能定义了。

比如踏板的:

0.5V = 0% 4.5V = 100%

还可能包含:

死区 合理性判断 双路踏板校验 故障降级

这些已经越来越接近应用功能。

最终可能变成:

ADC Raw ↓ MCAL ↓ Voltage ↓ IoHwAb ↓ Pedal Position ↓ RTE ↓ TorqueControl SWC

但实际项目里,到底在哪里做电压转换、在哪里做百分比换算,并没有一句放之四海而皆准的答案。

要看:

公司软件架构 功能安全分区 软件复用要求 具体传感器设计

但判断原则还是一样:

越靠近硬件的知识,越往下放。

越靠近车辆功能的知识,越往上放。

这个原则非常实用。


八、CAN 更能说明“为什么不能乱跨层”

再看大家最熟悉的 CAN。

假设 VCU 要发送:

VCU_HVStatus

很多人刚接触 AUTOSAR 时,会看到下面几个函数:

Can_Write()CanIf_Transmit()Com_SendSignal()Rte_Write()

然后开始混乱:

到底应该用哪个?

答案其实不是看“哪个函数能把报文发出去”。

而是看:

你现在站在哪一层。


如果你在 ASW

比如 VehicleStateManager 算出来:

VCU_HVStatus = HV_READY

你应该表达的是:

我的应用信号变成 HV_READY 了。

所以应用层更合理的是:

Rte_Write_VCU_HVStatus(HV_READY);

它不应该知道:

CAN ID PDU Hardware Object CAN Controller

到 COM

COM 负责的是:

Signal I-PDU Update Bit Timeout Signal Group Transmission Mode

所以它看到的是:

VCU_HVStatus

和:

这个 Signal 属于哪个 I-PDU

到 PduR

PduR 更不关心:

VCU_HVStatus 是不是高压状态

它只关心:

这个 PDU 应该往哪里路由。


到 CanIf

CanIf 开始知道:

这个 PDU 应该走哪个 CAN Driver 哪个 Tx PDU 哪个 Controller

但它依然不会直接去操作 CAN Controller 寄存器。


最后到 Can Driver

真正调用硬件的才是:

Can_Write()

这时候已经到了 MCAL。

所以一条完整发送链路大概是:

ASW │ │ Rte_Write ▼ RTE │ ▼ COM │ ▼ PduR │ ▼ CanIf │ ▼ Can Driver │ ▼ CAN Controller

现在再回头看一个问题:

ASW 能不能直接调用 Can_Write()?

技术上:

很多情况下你确实能做到。

架构上:

基本不应该这么干。

因为一旦这么做,ASW 就开始知道:

Hardware Object PDU Handle CAN Driver Controller

应用和底层立刻耦合起来。


九、Can_Write 能调用,不代表你就该调用

这个问题其实很值得单独说一下。

实际开发中经常会出现一种情况:

编译能过 功能能跑 测试也没问题

于是大家觉得:

这代码没问题。

但 AUTOSAR 项目里,“能不能跑”和“架构对不对”其实是两个问题。

例如:

Dem_SetEventStatus(...);

某些项目里 ASW 直接调用它可能完全能工作。

但如果整个项目定义的是:

SWC ↓ RTE ↓ Dem

那应用直接调用:

Dem_SetEventStatus()

就相当于:

绕过 RTE

这未必会立刻产生 Bug。

但会留下几个问题:

软件组件失去独立性 代码复用困难 依赖关系变复杂 软件组件测试困难 架构规则越来越乱

所以 AUTOSAR 项目里经常需要区分两个概念:

Functionally Correct 和 Architecturally Correct

也就是:

功能上是对的

不代表:

架构上是对的

做大型 ECU 项目,这个区别非常重要。


十、NvM 也是一样

再举一个很典型的例子。

应用软件需要保存:

驾驶模式记忆

比如用户上次选择的是:

SPORT

下次上电以后还要恢复。

最粗暴的方法:

ASW ↓ 直接写 Flash

理论上完全可以。

但如果这么做,应用就必须知道:

Flash地址 擦写方式 Sector Page 写入时序 数据有效性 掉电保护

这些全部变成应用层的负担。

所以 AUTOSAR 才会设计:

ASW ↓ RTE ↓ NvM ↓ MemIf ↓ Fee / Ea ↓ Fls / Eep ↓ Hardware

应用只想表达:

把这个数据保存下来。

至于底下到底:

什么时候写 写到哪里 怎么做冗余 怎么做 Block 管理

不应该由 VehicleMode SWC 来操心。


十一、Service Layer 为什么看起来特别杂?

到这里再回头看 Service Layer,理解起来会比背定义简单很多。

因为这一层其实放的是 ECU 的各种“公共能力”。

比如:

Dem Dcm NvM ComM EcuM BswM COM PduR

这些模块彼此做的事情完全不一样。

所以第一次看 AUTOSAR 架构图时,会觉得:

为什么这些乱七八糟的东西都放在 Service Layer?

其实它们有一个共同点:

都不是某一个具体应用功能独享的。

比如 Dem。

不是只有:

TorqueControl

需要故障管理。

可能:

ThermalManagement VehicleState Charging EnergyManagement

全部都需要。

NvM 也一样。

COM 也一样。

ComM、EcuM 更明显。

它们提供的是:

整个 ECU 共用的基础软件能力。

所以叫 Service。

从这个角度看,就比死记“Service Layer 里面有哪些模块”自然很多。


十二、外部芯片到底算 MCAL,还是 ECU Abstraction?

这个问题在实际项目里非常常见。

比如 ECU 上有一颗 SBC:

TLE8888

通过 SPI 和 MCU 通信。

那 TLE8888 Driver 属于 MCAL 吗?

一般来说:

不是标准 MCAL。

因为 MCAL 主要面对的是 MCU 内部外设。

比如:

SPI ADC CAN PORT PWM

TLE8888 是 ECU 板子上的外部器件。

它可能通过:

SPI Driver

去访问。

关系更像:

Application / BSW ↓ TLE8888 Driver ↓ SPI ↓ MCU QSPI

这里 SPI 属于 MCAL。

而 TLE8888 的控制逻辑,根据具体软件架构,可能被放在:

ECU Abstraction

或者:

CDD

十三、什么时候会用 CDD?

CDD 也就是:

Complex Device Driver。

项目里经常遇到一些硬件或者功能,AUTOSAR 标准模块并没有很好地覆盖。

比如:

特殊电源芯片 特殊 AFE 定制 ASIC FPGA 复杂硬件控制 强实时功能

这时候可能就会做一个 CDD。

比如:

ASW ↓ RTE ↓ CDD_TLE8888 ↓ SPI ↓ MCU

或者某些 CDD 直接访问硬件。

这也是 AUTOSAR 很现实的一面。

它并没有要求:

所有东西必须严格按照标准模块一级一级走。

而是:

标准能够解决的,尽量走标准架构。

特殊问题,允许通过 CDD 等方式解决。

否则很多真实 ECU 根本没法做。


十四、实际项目里最怕的不是跨层,而是“没人知道为什么跨层”

这里说一个比较实际的经验。

做 AUTOSAR 项目,千万不要把“不能跨层”理解成绝对铁律。

真实项目一定会存在一些:

特殊需求 性能要求 历史代码 供应商限制 芯片限制 Bootloader接口 复杂驱动 功能安全设计

导致某些调用并不完全符合最漂亮的架构图。

这很正常。

真正的问题不是:

有没有跨层。

而是:

为什么跨层,有没有明确设计。

如果大家都知道:

这个模块因为 XX 性能原因, 经架构评审允许直接访问 XX Driver。

这是设计。

但如果工程里面变成:

这里为了方便直接调一下 CanIf。 那里为了省事直接调一下 Dio。 再那里直接操作寄存器。

做着做着就会发现:

软件分层只剩下 PPT 里的架构图。

代码里面已经完全不是那么回事了。

这才是最麻烦的。


十五、判断一段代码放哪层,我一般会问三个问题

实际开发的时候,我觉得没有必要把问题搞得特别复杂。

看到一段代码,先问三个问题。


第一个问题:它知道具体硬件吗?

比如代码里面出现:

P33.0 ADC Group 3 CAN Controller 1 SPI Channel 2 GTM TOM

那它大概率已经非常靠近:

MCAL / Driver

第二个问题:它知道 ECU 上这个硬件是干什么的吗?

比如:

KL15 BrakeSwitch PowerRelay WakeupPin SensorSupply

已经开始从:

MCU资源

变成:

ECU硬件功能

那通常已经到了:

ECU Abstraction / IoHwAb / CDD

这个范围。


第三个问题:它是不是在决定车辆应该怎么做?

比如:

KL15有效以后是否进入Ready SOC低于多少开始限扭 什么条件允许上高压 什么时候允许快充 什么时候进入故障降级

这种代码基本已经属于:

ASW

这三个问题在实际工作中非常好用。


十六、再把 KL15 从头走一遍

现在重新看文章开头那个需求:

KL15 接在 P33.0。
KL15 有效以后,让 VCU 进入唤醒状态。

合理的软件责任可以拆成:

MCAL

负责:

读取 P33.0

例如:

Dio_ReadChannel(...)

它不管:

这个 Pin 为什么要读。

ECU Abstraction / IoHwAb

负责:

P33.0 ↓ KL15 State

它知道:

这个硬件输入在这块 ECU 上代表 KL15。


RTE

负责:

把 KL15 State 送给需要它的 SWC

ASW

负责:

KL15 = ON ↓ Vehicle State Transition

它知道:

KL15 有效以后整车应该怎么运行。

但不知道:

KL15 到底接在哪个 Pin 上。

这样整个软件边界就非常清楚。


十七、再看一次 ADC

同样:

MCU ADC Channel ↓ Adc Driver ↓ IoHwAb ↓ RTE ↓ Application

各层关心的事情分别是:

Adc Driver “这个 ADC Channel 采到了多少?”

IoHwAb “这个值代表某一路 ECU 模拟输入。”

ASW “这个传感器现在是什么状态, 接下来车辆应该怎么控制?”

十八、再看一次 CAN

CAN Controller ↓ Can Driver ↓ CanIf ↓ PduR ↓ COM ↓ RTE ↓ ASW

各层继续逐渐忘掉硬件细节。

越往上:

CAN Controller Hardware Object PDU I-PDU Signal Vehicle Function

你会发现这是一个很有意思的过程。

AUTOSAR 从下往上,其实一直在做一件事:

不停地把“硬件语言”翻译成“功能语言”。

最下面说:

PORT33.0 = HIGH

中间变成:

KL15 = ON

最上面变成:

车辆应该唤醒

CAN 也是一样。

最下面是:

Controller / Mailbox / Frame

中间是:

PDU / Signal

最上面变成:

SOC VehicleSpeed TorqueRequest HVStatus

如果从这个角度理解 AUTOSAR,很多东西就不需要硬背了。


十九、为什么大型 ECU 必须这么折腾?

看到这里,还是会有人问:

直接写不是简单很多吗?

是。

小项目绝对是直接写更简单。

比如:

一个 MCU 几十个 IO 一条 CAN 几千行代码

你完全可以:

ReadPin();ReadAdc();SendCan();

很快就做完了。

但是实际量产 ECU 往往是:

几百甚至上千个 CAN Signal 几十个诊断 DID 几百个 DTC 多路 CAN / LIN 大量 IO / ADC / PWM Bootloader 网络管理 睡眠唤醒 多核 功能安全 几十甚至上百个应用组件

而且还有:

应用供应商 BSW供应商 MCAL供应商 整车厂 Tier1 测试团队 标定团队

共同开发。

这个时候真正麻烦的已经不是:

代码能不能写出来。

而是:

这么多人写的代码怎么保证互相不搅在一起。

AUTOSAR 分层的价值就在这里。


二十、所以 MCAL、ECU Abstraction、Service、RTE、ASW 到底是什么关系?

看到最后,其实可以不用再背一遍定义。

只需要记住这一条变化:

硬件资源 ↓ MCAL ↓ ECU硬件功能 ↓ ECU Abstraction ↓ 公共基础能力 ↓ Service Layer ↓ RTE ↓ 车辆功能 ↓ ASW

不过再次强调:

这不是严格的调用顺序图。

真实 AUTOSAR 里面存在不同的软件路径。

比如 CAN:

ASW ↓ RTE ↓ COM ↓ PduR ↓ CanIf ↓ Can

而 IO 可能是:

ASW ↓ RTE ↓ IoHwAb ↓ Dio

NvM 又是:

ASW ↓ RTE ↓ NvM ↓ MemIf ↓ Fee ↓ Fls

所以不要试图把所有 AUTOSAR 模块强行排成一条直线。

真正需要理解的是:

每一层负责什么信息,每一层不应该知道什么信息。


最后

上一篇我们看 AUTOSAR 架构图的时候,更多是在认地图:

这里是 ASW 这里是 RTE 这里是 Service 这里是 ECU Abstraction 这里是 MCAL

这一篇真正想说的是:

地图不是拿来背的。

实际做项目的时候,更有价值的问题应该是:

我现在这段代码,到底属于硬件资源、ECU硬件功能,还是车辆功能?

一旦这个问题能够想清楚,后面再去看:

Dio Adc CanIf PduR COM NvM Dem Dcm

就会轻松很多。

因为它们不再是一堆缩写。

你会开始知道:

它为什么在这里。

以及:

它为什么不能随便跑到另外一层去。


下一篇预告

AUTOSAR 里的 SWC、Runnable、Port、Interface 到底是什么?

前面几篇我们一直在讲:

ASW RTE BSW

下一篇开始正式进入应用软件。

比如:

一个 SWC 到底是什么? Runnable 是不是一个普通 C 函数? Sender/Receiver Port 是怎么通信的? Client/Server 又是什么? Rte_Read、Rte_Write 到底是怎么生成出来的?

这些东西理解以后,才算真正开始走进 AUTOSAR 应用软件开发。


车控码农 · AUTOSAR 从 0 到 1

尽量不背概念。

从真实 ECU 开发的问题出发,一点一点把 AUTOSAR 拆开。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 16:47:33

前端技术选型:理性评估框架与工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 16:43:03

计算机单片机毕设实战-基于 51 单片机的多参量环境采集与智能联动控制系统设计 基于 51 单片机的室内安防与环境设备自动化调控系统设计(017506)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/3 16:38:45

网络安全系统学习指南:从基础到实战的完整路径规划

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 16:37:13

砂轮材质、粒度、硬度与加工适配

砂轮材质、粒度、硬度选型逻辑与加工适配实操原则 干磨削加工十几年,我说实话:市面上绝大多数砂轮问题,根本不是机器参数没调好,纯粹是砂轮选错了。 车间里常见的工件烧黑、表面起纹路、尺寸忽大忽小、砂轮磨两下就废、打磨效率上…

作者头像 李华
网站建设 2026/9/3 16:36:22

SBM-GML指数:绿色全要素生产率测算的模型原理与R语言实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华