引言
在汽车电子系统中,软件是实现功能安全的核心。虽然硬件提供了物理基础,但软件决定了系统如何响应、如何处理故障、如何确保安全。
想象一个真实场景:某汽车厂商的自动紧急制动系统(AEB)采用了先进的深度学习算法,能够精准识别障碍物。但是,由于软件中存在一个缓冲区溢出漏洞,导致攻击者可以通过车载信息系统远程控制制动系统,造成多起事故。
这个案例告诉我们:**软件级开发不仅要实现功能,更要确保代码的安全性、可靠性和可维护性。**这正是 ISO 26262-6 软件级开发的核心使命。
软件级开发的目标和范围
软件级开发的核心活动
ISO 26262-6 定义了软件级开发的八个核心活动:
软件安全需求(SSR)的初始化
- 分析系统级安全需求
- 软件架构的初步设计
- 软件安全需求清单
软件架构设计
- 设计软件组件的架构
- 定义软件组件之间的接口
- 评估软件架构的适用性
软件单元设计和实现
- 设计软件单元
- 编写代码
- 代码审查
软件单元测试
- 设计测试用例
- 执行单元测试
- 分析测试覆盖率
软件集成和测试
- 集成软件单元
- 执行集成测试
- 分析测试覆盖率
软件验证
- 静态分析
- 动态分析
- 回归测试
软件确认
- 软件在环测试(SIL)
- 处理器在环测试(PIL)
- 硬件在环测试(HIL)
软件工具置信度评估
- 工具分类
- 工具置信度评估
- 工具使用流程
软件级开发的输入和输出
输入
- 系统安全需求(SSyR):来自系统级开发
- 技术安全概念(TSC):来自系统级开发
- 硬件/软件接口规范(HSIS):来自系统级开发
- 软件安全需求(SSR):来自系统级开发
- 软件约束:性能、内存、实时性等约束
输出
- 软件架构设计文档:软件架构设计
- 软件单元设计文档:软件单元设计
- 源代码:实现软件功能
- 软件测试报告:测试结果
- 软件验证报告:验证结果
- 软件确认报告:确认结果
软件安全需求(SSR)的初始化
SSR 的来源
软件安全需求主要来自以下几个方面:
- 从系统级安全需求(SSyR)派生
- 从技术安全概念(TSC)派生
- 从硬件/软件接口规范(HSIS)派生
SSR 的分类
1. 功能性需求
描述软件应该实现的功能。
2. 性能需求
描述软件的性能指标,如响应时间、吞吐量等。
3. 安全机制需求
描述软件应该实现的安全机制,如故障检测、故障容错等。
4. 质量需求
描述软件的质量要求,如代码复杂度、可维护性等。
案例:制动系统的软件安全需求
来自 SSyR 的 SSR:
SSR-1.1(来自 SSyR-1.1):
“软件应每 10 ms 读取一次压力传感器数据,并执行一致性检查”
具体要求:
- 采样周期:10 ms
- 采样函数:
ReadPressureSensor() - 一致性检查:
CheckPressureConsistency()
SSR-1.2(来自 SSyR-1.3):
“软件应实现压力传感器故障诊断算法”
具体要求:
- 诊断函数:
DiagnosePressureSensor() - 诊断周期:10 ms
- 故障类型:开路、短路、漂移
SSR-1.3(来自 SSyR-1.3.2):
“软件应定期喂狗(例如每 5 ms)”
具体要求:
- 喂狗函数:
FeedWatchdog() - 喂狗周期:5 ms
软件架构设计
软件架构设计的原则
- 分层原则:应用层、中间件层、硬件抽象层
- 模块化原则:每个模块功能单一,接口清晰
- 独立性原则:安全相关软件与非安全相关软件分离
- 可测试性原则:架构便于单元测试和集成测试
软件架构的类型
1. 分层架构
适用于大多数汽车电子系统。
优点:
- 模块化
- 可维护性好
- 便于测试
缺点:
- 性能开销大
- 跨层调用困难
2. 微内核架构
适用于复杂的分布式系统。
优点:
- 可扩展性好
- 可靠性高
缺点:
- 开发复杂度高
- 调试困难
3. 事件驱动架构
适用于实时性要求高的系统。
优点:
- 实时性好
- 响应快
缺点:
- 调试困难
- 代码可读性差
案例:制动系统的软件架构
分层架构设计:
应用层(Application Layer)
├── 制动控制模块
│ ├── 压力控制算法
│ ├── 车轮速度控制
│ └── 车辆稳定性控制
│
├── 故障诊断模块
│ ├── 传感器故障诊断
│ ├── 执行器故障诊断
│ └── 系统故障诊断
│
├── 安全管理模块
│ ├── 故障处理逻辑
│ ├── 安全状态管理
│ └── 故障记录
│
└── 通信模块
├── CAN 通信
└── 诊断服务
│
中间件层(Middleware Layer)
├── 操作系统抽象层(OSAL)
│ ├── 任务调度
│ ├── 事件管理
│ └── 定时器管理
│
├── 通信层(Communication Layer)
│ ├── CAN 驱动
│ ├── SPI 驱动
│ └── LIN 驱动
│
└── 存储层(Storage Layer)
├── EEPROM 驱动
└── Flash 驱动
│
硬件抽象层(HAL)
├── ADC 驱动
├── GPIO 驱动
├── PWM 驱动
├── 定时器驱动
└── 看门狗驱动
│
硬件层(Hardware Layer)
├── MCU
├── 传感器
├── 执行器
└── 电源管理
软件组件接口定义
// 制动控制模块接口
typedef struct {
float pedal_position; // 制动踏板位置(0-1)
float vehicle_speed; // 车辆速度(km/h)
float wheel_speeds[4]; // 车轮速度(km/h)
} BrakingControlInput;
typedef struct {
float target_pressure; // 目标制动压力(bar)
float valve_command[4]; // 阀门控制指令(0-1)
} BrakingControlOutput;
// 函数原型
void BrakingControl_Init(void);
void BrakingControl_Run(const BrakingControlInput* input, BrakingControlOutput* output);
// 故障诊断模块接口
typedef enum {
FAULT_NONE = 0,
FAULT_SENSOR_OPEN,
FAULT_SENSOR_SHORT,
FAULT_SENSOR_DRIFT,
FAULT_ACTUATOR_STUCK,
FAULT_WATCHDOG_TIMEOUT
} FaultType;
typedef struct {
FaultType fault_type;
uint8_t component_id;
uint32_t timestamp;
} FaultEvent;
// 函数原型
void FaultDiagnosis_Init(void);
void FaultDiagnosis_Run(void);
bool FaultDiagnosis_GetFault(FaultEvent* event);
软件单元设计和实现
软件单元设计
软件单元是软件的最小可测试单元,通常是一个函数或一个类。
软件单元设计原则
- 单一职责原则:每个单元只做一件事
- 高内聚低耦合:单元内部紧密相关,单元之间松散耦合
- 可测试性:单元便于编写测试用例
- 可维护性:代码清晰,易于理解和修改
代码实现
编码规范
ISO 26262-6 要求使用编码规范,如 MISRA C。
MISRA C 的核心规则:
- 禁止未使用的变量和函数
- 禁止 goto 语句
- 禁止魔数,使用命名常量
- 函数参数个数 ≤ 7
- 函数圈复杂度 ≤ 15
- 禁止递归调用
代码示例
符合 MISRA C 的代码:
// 定义常量
#define MAX_PRESSURE 100.0f
#define MIN_PRESSURE 0.0f
#define SAMPLING_PERIOD_MS 10U
// 函数原型
static bool ValidatePressure(float pressure);
static void UpdateWatchdog(void);
// 主控制函数
void BrakingControl_Run(const BrakingControlInput* input, BrakingControlOutput* output)
{
float target_pressure = 0.0f;
float pedal_position = 0.0f;
// 输入检查
if (input == NULL || output == NULL)
{
return;
}
// 读取踏板位置
pedal_position = input->pedal_position;
// 限制踏板位置范围
if (pedal_position < 0.0f)
{
pedal_position = 0.0f;
}
else if (pedal_position > 1.0f)
{
pedal_position = 1.0f;
}
// 计算目标压力
target_pressure = pedal_position * MAX_PRESSURE;
// 验证压力
if (ValidatePressure(target_pressure) == false)
{
target_pressure = 0.0f;
}
// 设置输出
output->target_pressure = target_pressure;
// 更新看门狗
UpdateWatchdog();
}
// 验证压力
static bool ValidatePressure(float pressure)
{
bool is_valid = false;
if ((pressure >= MIN_PRESSURE) && (pressure <= MAX_PRESSURE))
{
is_valid = true;
}
return is_valid;
}
// 更新看门狗
static void UpdateWatchdog(void)
{
// 喂狗
FeedWatchdog();
}
代码审查
ISO 26262-6 要求进行代码审查。
审查清单:
- 代码是否符合编码规范?
- 函数是否满足单一职责原则?
- 变量命名是否清晰?
- 是否有未使用的变量或函数?
- 是否有魔数?
- 边界条件是否处理?
- 错误处理是否完善?
- 注释是否充分?
软件单元测试
单元测试的方法
- 白盒测试:基于代码结构设计测试用例
- 黑盒测试:基于需求设计测试用例
测试覆盖率
ISO 26262-6 要求达到一定的测试覆盖率:
| ASIL 等级 | 语句覆盖率 | 分支覆盖率 | MC/DC 覆盖率 |
|---|---|---|---|
| ASIL A | ≥ 80% | 不要求 | 不要求 |
| ASIL B | ≥ 90% | - | 不要求 |
| ASIL C | ≥ 90% | ≥ 90% | - |
| ASIL D | ≥ 100% | ≥ 100% | ≥ 100% |
案例:制动控制函数的单元测试
测试用例设计:
#include <unity.h>
#include "BrakingControl.h"
void setUp(void)
{
// 初始化
BrakingControl_Init();
}
void tearDown(void)
{
// 清理
}
// 测试用例 1:正常输入
void test_BrakingControl_NormalInput(void)
{
BrakingControlInput input = {0};
BrakingControlOutput output = {0};
input.pedal_position = 0.5f;
BrakingControl_Run(&input, &output);
TEST_ASSERT_EQUAL_FLOAT(50.0f, output.target_pressure);
}
// 测试用例 2:踏板位置超出上限
void test_BrakingControl_PedalPositionExceedsMaximum(void)
{
BrakingControlInput input = {0};
BrakingControlOutput output = {0};
input.pedal_position = 1.5f;
BrakingControl_Run(&input, &output);
TEST_ASSERT_EQUAL_FLOAT(100.0f, output.target_pressure);
}
// 测试用例 3:踏板位置超出下限
void test_BrakingControl_PedalPositionExceedsMinimum(void)
{
BrakingControlInput input = {0};
BrakingControlOutput output = {0};
input.pedal_position = -0.5f;
BrakingControl_Run(&input, &output);
TEST_ASSERT_EQUAL_FLOAT(0.0f, output.target_pressure);
}
// 测试用例 4:NULL 输入
void test_BrakingControl_NullInput(void)
{
BrakingControlOutput output = {0};
BrakingControl_Run(NULL, &output);
TEST_ASSERT_EQUAL_FLOAT(0.0f, output.target_pressure);
}
软件集成和测试
软件集成的方法
- 自底向上集成:从底层开始,逐层向上集成
- 自顶向下集成:从顶层开始,逐层向下集成
- 三明治集成:自底向上和自顶向下结合
软件集成测试
集成测试的目的是验证软件组件之间的接口和交互。
测试用例示例:
| 测试用例 | 测试目的 | 输入 | 预期输出 |
|---|---|---|---|
| TC-1 | 验证制动控制模块和传感器模块的接口 | 压力传感器正常 | 正常制动压力 |
| TC-2 | 验证制动控制模块和故障诊断模块的接口 | 压力传感器故障 | 检测到故障,进入安全状态 |
| TC-3 | 验证制动控制模块和通信模块的接口 | 接收到 CAN 消息 | 处理 CAN 消息,更新状态 |
软件验证
静态分析
静态分析是在不运行代码的情况下分析代码的技术。
静态分析工具:
- Coverity
- Klocwork
- Polyspace
- QAC
静态分析检查项:
- 编码规范符合性
- 未使用的变量和函数
- 空指针解引用
- 缓冲区溢出
- 整数溢出
- 死代码
静态分析报告示例:
| 文件 | 行号 | 类型 | 严重性 | 描述 |
|---|---|---|---|---|
| BrakingControl.c | 45 | 编码规范 | 警告 | 函数参数个数超过 7 |
| FaultDiagnosis.c | 78 | 空指针 | 错误 | 可能的空指针解引用 |
| CanDriver.c | 123 | 整数溢出 | 警告 | 整数加法可能溢出 |
动态分析
动态分析是在运行代码的情况下分析代码的技术。
动态分析工具:
- Valgrind
- Purify
- AddressSanitizer
动态分析检查项:
- 内存泄漏
- 数组越界
- 未初始化的变量
回归测试
回归测试是在代码修改后重新执行之前的测试用例,确保修改没有引入新的错误。
回归测试流程:
- 修改代码
- 执行所有测试用例
- 比较测试结果
- 分析失败的测试用例
- 修复错误
软件确认
软件在环测试(SIL)
SIL 测试是在仿真环境中测试软件。
SIL 测试的优点:
- 不需要硬件
- 测试速度快
- 便于调试
SIL 测试的缺点:
- 无法测试硬件相关的功能
- 测试环境可能与实际环境不同
处理器在环测试(PIL)
PIL 测试是在目标处理器上运行软件。
PIL 测试的优点:
- 更接近实际环境
- 可以测试编译器和优化
PIL 测试的缺点:
- 需要目标硬件
- 测试速度慢
硬件在环测试(HIL)
HIL 测试是在完整的硬件环境中测试软件。
HIL 测试的优点:
- 最接近实际环境
- 可以测试所有功能
HIL 测试的缺点:
- 成本高
- 测试速度慢
HIL 测试示例:
| 测试用例 | 测试目的 | 测试步骤 | 预期结果 |
|---|---|---|---|
| TC-1 | 正常制动功能 | 施加踏板力,测量制动压力 | 制动压力与踏板力成正比 |
| TC-2 | 传感器故障容错 | 断开压力传感器,测量制动压力 | 切换到备份传感器,制动正常 |
| TC-3 | 执行器故障容错 | 模拟阀门卡死,测量制动压力 | 进入安全状态,机械制动备份 |
软件工具置信度评估
工具分类
ISO 26262-6 将软件工具分为四个类别:
| 类别 | 描述 | 示例 |
|---|---|---|
| TCL 1 | 不推荐用于功能安全相关活动 | 普通文本编辑器 |
| TCL 2 | 可用于功能安全相关活动,不需要置信度评估 | 源代码管理工具 |
| TCL 3 | 可用于功能安全相关活动,需要置信度评估 | 编译器、链接器 |
| TCL 4 | 可用于功能安全相关活动,需要置信度评估 | 静态分析工具、测试工具 |
工具置信度评估
工具置信度评估包括:
- 工具厂商的声明:工具是否符合 ISO 26262 要求
- 工具使用历史:工具是否被广泛使用
- 工具验证:工具是否经过验证
案例:编译器的工具置信度评估
工具:GNU Compiler Collection (GCC)
评估过程:
工具厂商声明:
- GCC 声称不保证符合 ISO 26262
- 因此,GCC 属于 TCL 3 或 TCL 4
工具使用历史:
- GCC 被广泛使用,有大量的实际使用经验
工具验证:
- 需要进行验证,证明 GCC 满足安全要求
评估结果:
- GCC 属于 TCL 3
- 需要工具置信度评估
实战案例:自动紧急制动系统(AEB)的软件级开发
让我们以一个实际项目为例,展示软件级开发的完整流程。
项目背景
某汽车厂商正在开发 AEB 系统,用于在检测到碰撞风险时自动施加制动。ASIL 等级:D。
第一步:软件安全需求初始化
来自系统级开发的 SSR:
SSR-1.1:
“软件应实现多传感器融合算法,综合判断碰撞风险”
具体要求:
- 融合算法:扩展卡尔曼滤波(EKF)
- 更新频率:10 Hz
- 预测误差:距离 < 0.5m,速度 < 1km/h
SSR-1.2:
“软件应实现传感器数据一致性检查”
具体要求:
- 检查方法:多传感器数据交叉验证
- 故障判定:连续 5 次数据不一致
- 故障响应:标记传感器为不可用,并报警
第二步:软件架构设计
分层架构:
应用层
├── 传感器融合模块
│ ├── EKF 算法
│ ├── 数据融合
│ └── 状态估计
│
├── 碰撞检测模块
│ ├── TTC 计算
│ ├── 风险评估
│ └── 决策逻辑
│
├── 制动控制模块
│ ├── 制动策略
│ ├── 压力控制
│ └── 阀门控制
│
└── 故障诊断模块
├── 传感器故障诊断
├── 执行器故障诊断
└── 系统故障诊断
│
中间件层
├── 操作系统抽象层(OSAL)
├── 通信层(Communication Layer)
└── 存储层(Storage Layer)
│
硬件抽象层(HAL)
├── 摄像头驱动
├── 雷达驱动
├── 超声波驱动
└── 制动执行器驱动
│
硬件层
├── 摄像头
├── 雷达
├── 超声波传感器
└── 制动执行器
第三步:软件单元设计和实现
碰撞检测模块示例:
// 定义常量
#define MAX_TTC 5.0f
#define MIN_DISTANCE 5.0f
// 数据结构
typedef struct {
float distance; // 距离(m)
float relative_speed; // 相对速度(km/h)
float ttc; // 碰撞时间(s)
} CollisionRisk;
// 函数原型
static float CalculateTTC(float distance, float relative_speed);
static bool AssessCollisionRisk(const CollisionRisk* risk);
// 主函数
void CollisionDetection_Run(void)
{
CollisionRisk risk = {0};
float distance = 0.0f;
float relative_speed = 0.0f;
// 读取传感器数据
SensorFusion_GetData(&distance, &relative_speed);
// 计算 TTC
risk.ttc = CalculateTTC(distance, relative_speed);
// 评估碰撞风险
if (AssessCollisionRisk(&risk) == true)
{
// 触发制动
BrakingControl_Trigger();
}
}
// 计算 TTC
static float CalculateTTC(float distance, float relative_speed)
{
float ttc = MAX_TTC;
if (relative_speed > 0.0f)
{
ttc = distance / (relative_speed / 3.6f); // km/h 转换为 m/s
// 限制 TTC 范围
if (ttc > MAX_TTC)
{
ttc = MAX_TTC;
}
}
return ttc;
}
// 评估碰撞风险
static bool AssessCollisionRisk(const CollisionRisk* risk)
{
bool is_collision = false;
if (risk->distance < MIN_DISTANCE)
{
is_collision = true;
}
else if (risk->ttc < 2.0f)
{
is_collision = true;
}
return is_collision;
}
第四步:软件单元测试
测试用例设计:
#include <unity.h>
#include "CollisionDetection.h"
void setUp(void)
{
// 初始化
CollisionDetection_Init();
}
void tearDown(void)
{
// 清理
}
// 测试用例 1:正常情况,无碰撞风险
void test_CollisionDetection_NoCollision(void)
{
CollisionRisk risk = {0};
risk.distance = 50.0f;
risk.relative_speed = 10.0f;
risk.ttc = 18.0f;
bool is_collision = AssessCollisionRisk(&risk);
TEST_ASSERT_FALSE(is_collision);
}
// 测试用例 2:距离小于最小距离
void test_CollisionDetection_DistanceTooSmall(void)
{
CollisionRisk risk = {0};
risk.distance = 3.0f;
risk.relative_speed = 10.0f;
risk.ttc = 1.08f;
bool is_collision = AssessCollisionRisk(&risk);
TEST_ASSERT_TRUE(is_collision);
}
// 测试用例 3:TTC 小于阈值
void test_CollisionDetection_TTCTooSmall(void)
{
CollisionRisk risk = {0};
risk.distance = 10.0f;
risk.relative_speed = 30.0f;
risk.ttc = 1.2f;
bool is_collision = AssessCollisionRisk(&risk);
TEST_ASSERT_TRUE(is_collision);
}
第五步:软件集成和测试
集成测试计划:
| 测试用例 | 测试目的 | 测试步骤 | 预期结果 |
|---|---|---|---|
| TC-1 | 正常制动功能 | 模拟障碍物出现 | AEB 及时制动 |
| TC-2 | 摄像头故障容错 | 模拟摄像头故障 | 切换到雷达+超声波 |
| TC-3 | 雷达故障容错 | 模拟雷达故障 | 切换到摄像头+超声波 |
| TC-4 | 误制动测试 | 正常驾驶 | AEB 不误制动 |
第六步:软件验证
静态分析:
使用 Coverity 进行静态分析:
cov-build --dir cov-int make
cov-analyze --dir cov-int --enable-concurrency-fb
cov-format-errors --dir cov-int
静态分析结果:
| 文件 | 行号 | 类型 | 严重性 | 描述 |
|---|---|---|---|---|
| CollisionDetection.c | 45 | 编码规范 | 警告 | 变量命名不符合规范 |
| SensorFusion.c | 78 | 空指针 | 错误 | 可能的空指针解引用 |
修复错误:
// 修复空指针解引用
static bool AssessCollisionRisk(const CollisionRisk* risk)
{
bool is_collision = false;
// 添加空指针检查
if (risk == NULL)
{
return false;
}
if (risk->distance < MIN_DISTANCE)
{
is_collision = true;
}
else if (risk->ttc < 2.0f)
{
is_collision = true;
}
return is_collision;
}
第七步:软件确认
HIL 测试:
使用 HIL 测试台进行测试:
| 测试用例 | 测试目的 | 测试步骤 | 预期结果 |
|---|---|---|---|
| TC-1 | 正常制动功能 | 模拟障碍物在 30m 处,速度 50km/h | AEB 在 TTC < 2s 时制动 |
| TC-2 | 传感器故障容错 | 模拟摄像头故障 | AEB 使用雷达+超声波正常工作 |
| TC-3 | 响应时间测试 | 测量从检测障碍物到制动的时间 | 响应时间 < 150 ms |
常见错误和最佳实践
常见错误
软件架构设计不合理
- 模块耦合度过高
- 缺乏分层
- 可测试性差
代码质量差
- 不符合编码规范
- 代码复杂度高
- 注释不足
测试不充分
- 测试覆盖率不达标
- 只测试正常场景
- 未进行故障注入测试
工具使用不当
- 使用未经评估的工具
- 忽视工具的局限性
最佳实践
使用分层架构
- 应用层、中间件层、硬件抽象层
- 每层职责清晰
严格遵循编码规范
- 使用 MISRA C 或其他编码规范
- 定期进行代码审查
充分的测试
- 达到测试覆盖率要求
- 使用自动化测试工具
- 进行故障注入测试
使用静态分析工具
- 定期运行静态分析
- 及时修复发现的错误
建立软件质量门禁
- 设置质量标准
- 不达标不能进入下一阶段
总结
ISO 26262-6 软件级开发是编写安全的代码的关键环节。通过本文的深入解读和丰富的案例实践,我们掌握了:
软件安全需求(SSR)的初始化:
- SSR 的来源和分类
- SSR 的制定方法
软件架构设计:
- 软件架构设计的原则
- 分层架构、微内核架构、事件驱动架构
- 制动系统的软件架构实例
软件单元设计和实现:
- 软件单元设计原则
- 编码规范(MISRA C)
- 代码审查
软件单元测试:
- 单元测试的方法
- 测试覆盖率要求
- 制动控制函数的测试用例
软件集成和测试:
- 软件集成的方法
- 软件集成测试
- 测试用例示例
软件验证:
- 静态分析
- 动态分析
- 回归测试
软件确认:
- 软件在环测试(SIL)
- 处理器在环测试(PIL)
- 硬件在环测试(HIL)
软件工具置信度评估:
- 工具分类(TCL 1-4)
- 工具置信度评估
- 编译器的工具置信度评估
实战案例:
- AEB 软件级开发完整实践
核心要点:
- 软件是实现功能安全的核心,必须确保代码的安全性、可靠性和可维护性
- 软件架构设计必须考虑分层、模块化、独立性和可测试性
- 编码规范(如 MISRA C)是提高代码质量的重要手段
- 充分的测试(单元测试、集成测试、确认测试)是确保软件安全的关键
- 静态分析和动态分析是发现软件缺陷的重要工具
- 软件工具必须经过置信度评估,确保其满足安全要求
在下一篇文章中,我们将深入解读 ISO 26262-7 生产和运行部分,学习如何确保产品在生产和使用过程中的功能安全。
