4.6.6 调试追踪
4.6.6 调试追踪
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐☆☆☆
前置知识: 内存管理基础、多任务调试经验
🎯 学习目标
- 了解 OneOS 提供的调试追踪工具
- 掌握内存追踪的使用方法
- 了解 CPU 监控和自旋锁检查的作用
- 学会使用
os_kprintf进行调试输出
一、调试追踪概述
OneOS 提供了一系列调试追踪工具,帮助开发者在开发阶段发现和定位问题。这些工具覆盖了内存管理、CPU 使用率、自旋锁使用和内存池标签等多个方面。
调试追踪工具体系:
┌─────────────────────────────────────────────────┐
│ OS_USING_MEM_TRACE 内存追踪 │
│ OS_USING_CPU_MONITOR CPU 监控 │
│ OS_USING_SPINLOCK_CHECK 自旋锁检查 │
│ OS_USING_MEMPOOL_CHECK_TAG 内存池标签检查 │
│ OS_USING_KERNEL_DEBUG 内核调试日志 │
│ os_kprintf() 格式化调试输出 │
└─────────────────────────────────────────────────┘二、内存追踪
2.1 配置
#define OS_USING_MEM_TRACE /* 启用内存追踪 */2.2 API
定义在 kernel/include/os_memory.h 中:
#ifdef OS_USING_MEM_TRACE
/* 追踪默认堆的内存使用情况 */
os_err_t os_default_heap_trace(void);
/* 追踪指定堆的内存使用情况 */
os_err_t os_heap_trace(os_heap_id heap_id);
#endif2.3 功能
内存追踪工具可以记录和显示内存分配的历史信息,帮助开发者发现:
- 内存泄漏:分配了但没有释放的内存块
- 重复释放:同一块内存被释放多次
- 缓冲区溢出:写入超过分配大小的数据
- 内存碎片:堆内存的碎片化程度
2.4 使用示例
#include <os_memory.h>
void memory_trace_demo(void)
{
void *ptr1, *ptr2;
/* 分配内存 */
ptr1 = os_malloc(128);
ptr2 = os_malloc(256);
/* 追踪默认堆状态 */
os_default_heap_trace();
os_kprintf("ptr1: %d bytes allocated\r\n", 128);
os_kprintf("ptr2: %d bytes allocated\r\n", 256);
/* 释放内存 */
os_free(ptr1);
os_kprintf("ptr1 freed\r\n");
/* 再次追踪,检查是否有泄漏 */
os_default_heap_trace();
/* 故意"忘记"释放 ptr2 - 内存泄漏! */
os_kprintf("WARNING: ptr2 not freed - memory leak!\r\n");
/* 追踪将显示未释放的内存块 */
os_default_heap_trace();
}三、CPU 监控
3.1 配置
#define OS_USING_CPU_MONITOR /* 启用 CPU 监控 */3.2 功能
CPU 监控器(定义在 kernel/include/debug/cpu_monitor/cpu_monitor.h)可以统计:
- 各任务的 CPU 使用率:每个任务占用 CPU 时间的百分比
- CPU 总利用率:系统整体的 CPU 使用率
- 空闲任务占比:CPU 空闲时间的比例
3.3 使用示例
#ifdef OS_USING_CPU_MONITOR
#include <cpu_monitor.h>
void cpu_monitor_demo(void)
{
/* 启动 CPU 监控 */
cpu_monitor_init();
/* 运行一段时间 */
os_task_msleep(10000);
/* 获取 CPU 使用率报告 */
cpu_monitor_report();
/* 获取单个任务的 CPU 使用率 */
os_task_id my_task = os_task_self();
uint32_t cpu_usage = cpu_monitor_get_task_usage(my_task);
os_kprintf("My task CPU usage: %d%%\r\n", cpu_usage);
}
#endif四、自旋锁检查
4.1 配置
#define OS_USING_SPINLOCK_CHECK /* 启用自旋锁检查 */4.2 功能
自旋锁检查(定义在 kernel/include/os_spinlock.h)在开发和调试阶段检测以下自旋锁使用错误:
- 重复加锁:同一 CPU 重复获取已持有的自旋锁
- 死锁检测:检测潜在的死锁情况
- 持有时间过长:自旋锁被持有时间超过预期
- 在错误上下文中使用:在不应使用自旋锁的上下文中使用
4.3 使用建议
自旋锁检查主要用于开发调试阶段,生产版本中建议关闭以减小开销:
/* 开发版本 */
#define OS_USING_SPINLOCK_CHECK
/* 生产版本 */
// #define OS_USING_SPINLOCK_CHECK五、内存池标签检查
5.1 配置
#define OS_USING_MEMPOOL_CHECK_TAG /* 启用内存池标签检查 */5.2 功能
内存池标签检查在内存池的每个块前后添加标签(魔术字),用于检测:
- 缓冲区溢出:写入超出内存池块边界的数据
- 内存损坏:意外的内存写入破坏了内存池块
- 重复释放:同一块被多次释放回内存池
5.3 工作原理
内存池块结构(启用标签检查):
┌──────────────┐
│ 前标签(TAG) │ ← 固定魔术字,用于检测溢出
├──────────────┤
│ │
│ 用户数据区 │
│ │
├──────────────┤
│ 后标签(TAG) │ ← 固定魔术字,用于检测溢出
└──────────────┘每次分配和释放时,系统检查前后标签是否完整。如果标签被破坏,说明发生了缓冲区溢出。
六、os_kprintf 调试输出
6.1 函数签名
定义在 kernel/include/os_util.h 中:
void os_kprintf(const char *fmt, ...);6.2 功能
os_kprintf 是 OneOS 提供的格式化调试输出函数,类似于标准 C 库的 printf,但专为内核环境设计:
- 支持基本的格式化输出(
%d、%x、%s、%c等) - 输出到已注册的控制台设备
- 在内核上下文中安全使用
6.3 使用示例
#include <os_util.h>
void debug_output_demo(void)
{
os_task_id self = os_task_self();
os_tick_t tick = os_tick_get_value();
/* 基本输出 */
os_kprintf("System started at tick: %d\r\n", tick);
/* 任务信息 */
os_kprintf("Current task: %s\r\n", os_task_get_name(self));
os_kprintf("Task priority: %d\r\n", os_task_get_priority(self));
/* 十六进制输出 */
uint32_t addr = 0x20001000;
os_kprintf("Buffer address: 0x%08X\r\n", addr);
/* 条件输出 */
#ifdef DEBUG_ENABLED
os_kprintf("DEBUG: Variable x = %d\r\n", x);
#endif
}6.4 内核调试日志(OS_USING_KERNEL_DEBUG)
启用内核调试日志后,内核会输出更详细的运行信息:
#define OS_USING_KERNEL_DEBUG /* 启用内核调试日志 */内核日志级别(定义在 kernel/source/os_kernel_internal.h):
#define KERN_ERROR 0 /* 错误条件 */
#define KERN_WARNING 1 /* 警告条件 */
#define KERN_INFO 2 /* 信息性消息 */
#define KERN_DEBUG 3 /* 调试级别消息 */七、任务切换钩子
任务切换钩子允许在任务切换时执行自定义代码,常用于追踪系统行为:
/* 设置任务切换钩子 */
void os_task_switch_set_hook(void (*hook)(os_task_id from, os_task_id to));使用示例:
static void task_switch_hook(os_task_id from, os_task_id to)
{
os_kprintf("Switch: %s -> %s\r\n",
os_task_get_name(from),
os_task_get_name(to));
}
void init_trace(void)
{
os_task_switch_set_hook(task_switch_hook);
}注意:任务切换钩子在调度器关键路径中执行,应保持极短,避免影响系统性能。
八、配置汇总
| 配置宏 | 功能 | 开销 | 建议 |
|---|---|---|---|
OS_USING_MEM_TRACE | 内存分配追踪 | 中等 | 开发阶段启用 |
OS_USING_CPU_MONITOR | CPU 使用率监控 | 低 | 开发和调优启用 |
OS_USING_SPINLOCK_CHECK | 自旋锁使用检查 | 低 | 开发阶段启用 |
OS_USING_MEMPOOL_CHECK_TAG | 内存池标签检查 | 低 | 开发和测试启用 |
OS_USING_KERNEL_DEBUG | 内核调试日志 | 中等 | 开发阶段启用 |
os_kprintf | 格式化调试输出 | 取决于使用频率 | 按需使用 |
📝 本节小结
OneOS 提供了丰富的调试追踪工具:内存追踪(os_default_heap_trace / os_heap_trace)用于检测内存泄漏和溢出;CPU 监控用于分析任务 CPU 使用率;自旋锁检查用于检测锁使用错误;内存池标签检查用于检测缓冲区溢出。os_kprintf 提供了便捷的格式化调试输出,内核调试日志提供了分级的日志输出能力。这些工具应在开发阶段充分使用,帮助快速定位和修复问题。