引言

在汽车电子系统中,软件是实现功能安全的核心。虽然硬件提供了物理基础,但软件决定了系统如何响应、如何处理故障、如何确保安全。

想象一个真实场景:某汽车厂商的自动紧急制动系统(AEB)采用了先进的深度学习算法,能够精准识别障碍物。但是,由于软件中存在一个缓冲区溢出漏洞,导致攻击者可以通过车载信息系统远程控制制动系统,造成多起事故。

这个案例告诉我们:**软件级开发不仅要实现功能,更要确保代码的安全性、可靠性和可维护性。**这正是 ISO 26262-6 软件级开发的核心使命。

软件级开发的目标和范围

软件级开发的核心活动

ISO 26262-6 定义了软件级开发的八个核心活动:

  1. 软件安全需求(SSR)的初始化

    • 分析系统级安全需求
    • 软件架构的初步设计
    • 软件安全需求清单
  2. 软件架构设计

    • 设计软件组件的架构
    • 定义软件组件之间的接口
    • 评估软件架构的适用性
  3. 软件单元设计和实现

    • 设计软件单元
    • 编写代码
    • 代码审查
  4. 软件单元测试

    • 设计测试用例
    • 执行单元测试
    • 分析测试覆盖率
  5. 软件集成和测试

    • 集成软件单元
    • 执行集成测试
    • 分析测试覆盖率
  6. 软件验证

    • 静态分析
    • 动态分析
    • 回归测试
  7. 软件确认

    • 软件在环测试(SIL)
    • 处理器在环测试(PIL)
    • 硬件在环测试(HIL)
  8. 软件工具置信度评估

    • 工具分类
    • 工具置信度评估
    • 工具使用流程

软件级开发的输入和输出

输入

  • 系统安全需求(SSyR):来自系统级开发
  • 技术安全概念(TSC):来自系统级开发
  • 硬件/软件接口规范(HSIS):来自系统级开发
  • 软件安全需求(SSR):来自系统级开发
  • 软件约束:性能、内存、实时性等约束

输出

  • 软件架构设计文档:软件架构设计
  • 软件单元设计文档:软件单元设计
  • 源代码:实现软件功能
  • 软件测试报告:测试结果
  • 软件验证报告:验证结果
  • 软件确认报告:确认结果

软件安全需求(SSR)的初始化

SSR 的来源

软件安全需求主要来自以下几个方面:

  1. 从系统级安全需求(SSyR)派生
  2. 从技术安全概念(TSC)派生
  3. 从硬件/软件接口规范(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. 独立性原则:安全相关软件与非安全相关软件分离
  4. 可测试性原则:架构便于单元测试和集成测试

软件架构的类型

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);

软件单元设计和实现

软件单元设计

软件单元是软件的最小可测试单元,通常是一个函数或一个类。

软件单元设计原则

  1. 单一职责原则:每个单元只做一件事
  2. 高内聚低耦合:单元内部紧密相关,单元之间松散耦合
  3. 可测试性:单元便于编写测试用例
  4. 可维护性:代码清晰,易于理解和修改

代码实现

编码规范

ISO 26262-6 要求使用编码规范,如 MISRA C。

MISRA C 的核心规则

  1. 禁止未使用的变量和函数
  2. 禁止 goto 语句
  3. 禁止魔数,使用命名常量
  4. 函数参数个数 ≤ 7
  5. 函数圈复杂度 ≤ 15
  6. 禁止递归调用

代码示例

符合 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 要求进行代码审查。

审查清单

  • 代码是否符合编码规范?
  • 函数是否满足单一职责原则?
  • 变量命名是否清晰?
  • 是否有未使用的变量或函数?
  • 是否有魔数?
  • 边界条件是否处理?
  • 错误处理是否完善?
  • 注释是否充分?

软件单元测试

单元测试的方法

  1. 白盒测试:基于代码结构设计测试用例
  2. 黑盒测试:基于需求设计测试用例

测试覆盖率

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);
}

软件集成和测试

软件集成的方法

  1. 自底向上集成:从底层开始,逐层向上集成
  2. 自顶向下集成:从顶层开始,逐层向下集成
  3. 三明治集成:自底向上和自顶向下结合

软件集成测试

集成测试的目的是验证软件组件之间的接口和交互。

测试用例示例

测试用例测试目的输入预期输出
TC-1验证制动控制模块和传感器模块的接口压力传感器正常正常制动压力
TC-2验证制动控制模块和故障诊断模块的接口压力传感器故障检测到故障,进入安全状态
TC-3验证制动控制模块和通信模块的接口接收到 CAN 消息处理 CAN 消息,更新状态

软件验证

静态分析

静态分析是在不运行代码的情况下分析代码的技术。

静态分析工具

  • Coverity
  • Klocwork
  • Polyspace
  • QAC

静态分析检查项

  • 编码规范符合性
  • 未使用的变量和函数
  • 空指针解引用
  • 缓冲区溢出
  • 整数溢出
  • 死代码

静态分析报告示例

文件行号类型严重性描述
BrakingControl.c45编码规范警告函数参数个数超过 7
FaultDiagnosis.c78空指针错误可能的空指针解引用
CanDriver.c123整数溢出警告整数加法可能溢出

动态分析

动态分析是在运行代码的情况下分析代码的技术。

动态分析工具

  • Valgrind
  • Purify
  • AddressSanitizer

动态分析检查项

  • 内存泄漏
  • 数组越界
  • 未初始化的变量

回归测试

回归测试是在代码修改后重新执行之前的测试用例,确保修改没有引入新的错误。

回归测试流程

  1. 修改代码
  2. 执行所有测试用例
  3. 比较测试结果
  4. 分析失败的测试用例
  5. 修复错误

软件确认

软件在环测试(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可用于功能安全相关活动,需要置信度评估静态分析工具、测试工具

工具置信度评估

工具置信度评估包括:

  1. 工具厂商的声明:工具是否符合 ISO 26262 要求
  2. 工具使用历史:工具是否被广泛使用
  3. 工具验证:工具是否经过验证

案例:编译器的工具置信度评估

工具:GNU Compiler Collection (GCC)

评估过程

  1. 工具厂商声明

    • GCC 声称不保证符合 ISO 26262
    • 因此,GCC 属于 TCL 3 或 TCL 4
  2. 工具使用历史

    • GCC 被广泛使用,有大量的实际使用经验
  3. 工具验证

    • 需要进行验证,证明 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.c45编码规范警告变量命名不符合规范
SensorFusion.c78空指针错误可能的空指针解引用

修复错误

// 修复空指针解引用
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/hAEB 在 TTC < 2s 时制动
TC-2传感器故障容错模拟摄像头故障AEB 使用雷达+超声波正常工作
TC-3响应时间测试测量从检测障碍物到制动的时间响应时间 < 150 ms

常见错误和最佳实践

常见错误

  1. 软件架构设计不合理

    • 模块耦合度过高
    • 缺乏分层
    • 可测试性差
  2. 代码质量差

    • 不符合编码规范
    • 代码复杂度高
    • 注释不足
  3. 测试不充分

    • 测试覆盖率不达标
    • 只测试正常场景
    • 未进行故障注入测试
  4. 工具使用不当

    • 使用未经评估的工具
    • 忽视工具的局限性

最佳实践

  1. 使用分层架构

    • 应用层、中间件层、硬件抽象层
    • 每层职责清晰
  2. 严格遵循编码规范

    • 使用 MISRA C 或其他编码规范
    • 定期进行代码审查
  3. 充分的测试

    • 达到测试覆盖率要求
    • 使用自动化测试工具
    • 进行故障注入测试
  4. 使用静态分析工具

    • 定期运行静态分析
    • 及时修复发现的错误
  5. 建立软件质量门禁

    • 设置质量标准
    • 不达标不能进入下一阶段

总结

ISO 26262-6 软件级开发是编写安全的代码的关键环节。通过本文的深入解读和丰富的案例实践,我们掌握了:

  1. 软件安全需求(SSR)的初始化

    • SSR 的来源和分类
    • SSR 的制定方法
  2. 软件架构设计

    • 软件架构设计的原则
    • 分层架构、微内核架构、事件驱动架构
    • 制动系统的软件架构实例
  3. 软件单元设计和实现

    • 软件单元设计原则
    • 编码规范(MISRA C)
    • 代码审查
  4. 软件单元测试

    • 单元测试的方法
    • 测试覆盖率要求
    • 制动控制函数的测试用例
  5. 软件集成和测试

    • 软件集成的方法
    • 软件集成测试
    • 测试用例示例
  6. 软件验证

    • 静态分析
    • 动态分析
    • 回归测试
  7. 软件确认

    • 软件在环测试(SIL)
    • 处理器在环测试(PIL)
    • 硬件在环测试(HIL)
  8. 软件工具置信度评估

    • 工具分类(TCL 1-4)
    • 工具置信度评估
    • 编译器的工具置信度评估
  9. 实战案例

    • AEB 软件级开发完整实践

核心要点

  • 软件是实现功能安全的核心,必须确保代码的安全性、可靠性和可维护性
  • 软件架构设计必须考虑分层、模块化、独立性和可测试性
  • 编码规范(如 MISRA C)是提高代码质量的重要手段
  • 充分的测试(单元测试、集成测试、确认测试)是确保软件安全的关键
  • 静态分析和动态分析是发现软件缺陷的重要工具
  • 软件工具必须经过置信度评估,确保其满足安全要求

在下一篇文章中,我们将深入解读 ISO 26262-7 生产和运行部分,学习如何确保产品在生产和使用过程中的功能安全。

延伸阅读