4.6 错误处理机制
4.6 错误处理机制
📚 本节导读
学习时长: 约 35 分钟(含所有子节)
难度级别: ⭐⭐⭐☆☆
前置知识: C 语言基础、嵌入式系统调试经验、任务栈概念
在嵌入式实时操作系统中,错误处理是保障系统稳定性和可靠性的关键。OneOS 提供了从开发到生产的错误处理能力:一方面通过断言机制在开发调试阶段快速暴露"不应该发生"的编程逻辑错误;另一方面通过堆栈溢出检测捕获 RTOS 开发中最常见也最危险的 bug——任务栈越界。两者均会汇入安全机制(os_safety_*_process())进行最终保护。
本节是对 OneOS 错误处理体系的概述,帮助你理解断言与堆栈溢出检测的各自定位、如何选用,以及与错误码、安全机制的协作关系。
🎯 学习目标
- 理解断言机制与堆栈溢出检测在错误处理体系中的定位
- 掌握断言机制(
OS_ASSERT/OS_ASSERT_EX)的使用方法与适用场景 - 掌握堆栈溢出的检测原理、栈相关 API 与栈大小估算方法
- 理解断言、错误码、安全机制的协作关系
- 掌握开发/生产环境的配置策略
一、错误处理机制全景
OneOS 错误处理能力围绕断言与堆栈溢出检测两大核心机制,并由错误码与安全机制作为配套概念协同工作:
┌────────────────────────────────────────────────────────────────────────────┐
│ OneOS 错误处理机制全景 │
├──────────────────────────────────┬─────────────────────────────────────────┤
│ 核心机制 (Core) │ 配套概念 (Supporting) │
├──────────────────────────────────┼─────────────────────────────────────────┤
│ ① 断言机制 (Assert) │ • 错误码 (Error Code) │
│ OS_ASSERT / OS_ASSERT_EX │ os_errno() / OS_SET_ERRNO │
│ 用于开发调试阶段捕获编程错误 │ OS_GET_ERRNO │
│ │ │
│ ② 堆栈溢出检测 (Stack Overflow) │ • 安全机制 (Safety Mechanism) │
│ 栈水印 / 栈边界检查 │ os_safety_*_process() │
│ 栈大小估算 / 最佳实践 │ 故障最终保护 │
└──────────────────────────────────┴─────────────────────────────────────────┘两大核心机制的角色定位:
| 机制 | 触发时机 | 处理方式 | 适用阶段 | 对应子节 |
|---|---|---|---|---|
| 断言 | 违反"不可能发生"的编程逻辑 | 打印信息 + 调用 os_safety_assert_process() | 开发调试 | 4.6.1 |
| 堆栈溢出检测 | 任务栈水印被覆盖 / 栈边界越界 | 检测 + 调用 os_safety_task_stack_overflow_process() | 开发 + 生产 | 4.6.2 |
二、核心机制对比
下表从多个维度对比了本模块的两大核心机制:
| 特性 | 断言机制 | 堆栈溢出检测 |
|---|---|---|
| 用途 | 校验编程逻辑不变量 | 检测任务栈越界 |
| 触发来源 | 条件表达式为假 | 栈水印被覆盖 / 栈边界越界 |
| 可恢复性 | 通常不可恢复 | 不可恢复 |
| 可否配置关闭 | 是(OS_USING_ASSERT) | 随任务栈创建 |
| 适用阶段 | 开发调试 | 开发 + 生产 |
| 性能影响 | 可关闭为 0 | 栈填充开销 |
| 代表 API | OS_ASSERT / OS_ASSERT_EX | os_task_get_stack_top/begin/end |
与错误码、安全机制的协作关系:
┌──────────────┐ 触发 ┌───────────────────────┐
│ 编程逻辑错误 │ ────────► │ 断言 OS_ASSERT │ ─┐
└──────────────┘ └───────────────────────┘ │
▼
┌──────────────┐ 触发 ┌───────────────────────┐ ┌──────────────────────┐
│ 可恢复运行时 │ ────────► │ 错误码 return OS_xxx │ │ 安全机制 │
│ 错误 │ └───────────────────────┘ │ os_safety_*_process │
└──────────────┘ └──────────┬───────────┘
│
┌──────────────┐ 触发 ┌───────────────────────┐ │
│ 任务栈越界 │ ────────► │ 堆栈溢出检测 │ ─────────────┘
└──────────────┘ └───────────────────────┘三、选型指南
面对不同类型的错误,可按下面的决策流程确定"用哪种机制"。
3.1 何时使用断言
- 编程逻辑不变量:空指针、参数越界、状态不一致等"逻辑上不可能"的错误
- 开发阶段尽早暴露问题:函数入口参数合法性检查、内部状态一致性校验
- 需要定位到函数名 + 行号:断言失败会打印
[函数名][行号]
3.2 何时使用堆栈溢出检测
- 任务可能使用深层递归 / 大局部变量 / 长调用链:需要监控栈使用情况
- 生产环境需要栈越界保护:注册
os_safety_task_stack_overflow_set_hook钩子 - 需要优化栈大小:通过栈水印检测确定实际使用量,合理分配栈空间
3.3 错误处理选型快速决策流程
遇到问题/错误怎么处理?
│
├─ 该错误在逻辑上"不应该发生"吗(编程错误,如空指针、越界)?
│ ├─ 是 ──────────────────────────────► 断言 OS_ASSERT / OS_ASSERT_EX
│ └─ 否,是可能发生的运行时错误 ─────────► 错误码(函数返回值 / OS_SET_ERRNO)
│
├─ 是否担心任务栈越界 / 需要监控栈使用?
│ ├─ 是 ──────────────────────────────► 堆栈溢出检测
│ └─ 否
│ └─ 是否属于关键故障(断言失败 / 栈溢出)?
│ └─ 是 ──────────────────────► 安全机制(注册钩子)一句话速记:
| 场景 | 首选机制 |
|---|---|
| 校验编程逻辑不变量(空指针、越界) | 断言 |
| 处理可恢复的运行时错误(超时、资源不足) | 错误码 |
| 检测 / 监控任务栈越界 | 堆栈溢出检测 |
| 关键故障的最终保护 | 安全机制 |
四、错误码:模块共性概念
错误码是贯穿 OneOS 所有内核 API 的基础约定,也是理解断言"何时不该用"的关键参照。这里归纳其共性概念。
4.1 错误码分类速查
| 类别 | 错误码 | 典型场景 |
|---|---|---|
| 成功 | OS_SUCCESS (0) | 操作成功完成 |
| 通用 | OS_FAILURE (-1) | 未分类的通用错误 |
| 时间 | OS_TIMEOUT (-2) | 等待超时 |
| 资源状态 | OS_FULL (-3) / OS_EMPTY (-4) | 队列满 / 队列空 |
| 内存 | OS_NOMEM (-5) | 内存分配失败 |
| 功能 | OS_NOSYS (-6) / OS_ENOSYS (-19) | 功能未实现 |
| 设备 | OS_BUSY (-7) / OS_NODEV (-11) | 设备忙 / 设备不存在 |
| IO | OS_EIO (-8) | 输入输出错误 |
| 中断 | OS_INTR (-9) | 系统调用被中断 |
| 参数 | OS_INVAL (-10) | 无效参数 |
| 权限 | OS_EPERM (-12) / OS_EACCES (-14) | 操作不允许 / 权限拒绝 |
| 文件 | OS_EBADF (-13) | 错误的文件号 |
| 地址 | OS_EFAULT (-15) / OS_EADDRINUSE (-22) | 错误地址 / 地址已占用 |
| 死锁 | OS_EDEADLK (-16) | 资源死锁 |
| 限制 | OS_ENXIO (-17) / OS_E2BIG (-18) | 设备不存在 / 参数列表过长 |
| 重试 | OS_EAGAIN (-20) | 临时失败,请重试 |
| 数据 | OS_ENODATA (-21) | 无可用数据 |
4.2 断言 vs 错误码
| 特性 | 断言 | 错误码 |
|---|---|---|
| 处理方式 | 打印信息 + 进入安全处理 | 函数返回错误码 |
| 适用场景 | 不应发生的逻辑错误(编程错误) | 可预见的运行时错误 |
| 可恢复性 | 通常不可恢复 | 调用者可以处理 |
| 性能影响 | 可配置关闭 | 始终存在 |
| 典型用例 | 空指针、参数越界、状态不一致 | 资源不足、超时、权限拒绝 |
原则:用断言检查"不可能发生"的条件(编程错误),用错误码处理"可能发生但可恢复"的错误(运行时错误)。不要用断言替代错误码。
4.3 任务级错误码
os_errno() 返回当前任务的错误码变量指针,每个任务拥有独立的错误码,互不干扰。通过宏便捷读写:
OS_SET_ERRNO(OS_EIO); /* 设置错误号 */
os_err_t err = OS_GET_ERRNO(); /* 获取错误号 */五、安全机制:模块共性概念
安全机制是断言与堆栈溢出检测的最终保护出口,其行为由 OS_USING_SAFETY_MECHANISM 宏控制。这里归纳其共性机制。
5.1 故障处理入口
无论是断言失败还是堆栈溢出,最终都会汇聚到安全机制的 os_safety_*_process() 处理链:
| 故障来源 | 触发入口 | 处理函数 |
|---|---|---|
OS_ASSERT 失败 | os_safety_assert_process() | 安全机制 |
OS_ASSERT_EX 失败 | os_safety_assert_process() | 安全机制 |
| 堆栈溢出检测 | os_safety_task_stack_overflow_process() | 安全机制 |
5.2 钩子注册 vs 默认行为
| 状态 | 行为 |
|---|---|
OS_USING_SAFETY_MECHANISM 启用 | 优先调用用户注册的钩子;钩子未注册或返回后进入默认处理 |
OS_USING_SAFETY_MECHANISM 未启用 | 处理函数被替换为宏:os_irq_disable() + 无限循环("安全停止") |
相关钩子注册 API:os_safety_assert_set_hook / os_safety_task_stack_overflow_set_hook。
5.3 配置宏速查
| 配置宏 | 功能 | 开销 | 建议 |
|---|---|---|---|
OS_USING_ASSERT | 启用断言机制 | 可关闭为 0 | 开发阶段启用 |
OS_USING_SAFETY_MECHANISM | 启用安全机制 | 仅故障时开销 | 生产必须启用 |
六、子章节导航
| 章节 | 标题 | 核心内容 |
|---|---|---|
| 4.6.1 | 断言机制 | OS_ASSERT / OS_ASSERT_EX 宏、条件检查、断言处理流程 |
| 4.6.2 | 堆栈溢出检测 | 栈水印检测、栈边界检查、栈相关 API、栈大小估算、最佳实践 |
📝 本节小结
核心要点回顾
OneOS 错误处理体系围绕断言机制与堆栈溢出检测两大核心,并借助错误码、安全机制协同工作:
1. 两大核心机制
| 机制 | 核心用途 | 适用阶段 |
|---|---|---|
| 断言 | 校验编程逻辑不变量 | 开发调试 |
| 堆栈溢出检测 | 检测任务栈越界 | 开发 + 生产 |
2. 两个共性概念
- 错误码约定:
0表示成功、负值表示错误;os_errno()提供任务级错误码。断言检查"不可能"的编程错误,错误码处理"可能但可恢复"的运行时错误。 - 安全机制钩子:断言失败、堆栈溢出统一汇聚到
os_safety_*_process(),启用时优先调用用户钩子,未启用时关中断死循环。
3. 选型核心原则
- 编程逻辑错误(空指针、越界)→ 断言
- 可恢复运行时错误(超时、资源不足)→ 错误码
- 检测栈越界 / 优化栈大小 → 堆栈溢出检测
- 关键故障最终保护 → 安全机制
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 用断言处理可恢复的错误 | 断言用于"不可能发生"的编程错误;可恢复错误用错误码返回 |
| 忽略 API 返回值 | 始终检查返回值,即使确信"不会出错" |
| 栈大小不留余量 | 栈大小至少预留 20%-30% 安全余量,按实际使用量的 1.3-1.5 倍配置 |
| 生产环境禁用安全机制 | 生产应保留安全机制并注册钩子;可关闭的是断言等调试类工具 |
| 在断言中写有副作用表达式 | 断言条件应简洁,不要写 OS_ASSERT(++i < 10) 这类带副作用的表达式 |
下一步
本节是错误处理机制的概述,帮助你建立了整体认识。接下来请学习下一小节,逐个深入每种机制:
- 4.6.1 断言机制:掌握
OS_ASSERT/OS_ASSERT_EX宏的用法与断言处理流程。