ChibiOS/RT 调试与数据结构
一、调试支持概述
ChibiOS/RT提供了丰富的调试支持机制,帮助开发者在开发阶段发现和定位问题。这些调试功能通过 chdebug.h 头文件中的宏定义进行控制,大部分功能在发布版本中可以完全禁用,零开销运行。
调试功能的启用通过 chconf.h 配置文件中的宏定义进行控制:
/* chconf.h 调试配置示例 */
/* 系统状态检查 - 验证API调用时的系统状态 */#define CH_DBG_SYSTEM_STATE_CHECK TRUE
/* API参数检查 - 验证API参数的有效性 */#define CH_DBG_ENABLE_CHECKS TRUE
/* 断言检查 - 启用chDbgAssert断言 */#define CH_DBG_ENABLE_ASSERTS TRUE
/* 调试追踪掩码 - 控制追踪输出的类别 */#define CH_DBG_TRACE_MASK CH_DBG_TRACE_MASK_ALL
/* 栈溢出检查 - 检测线程栈溢出 */#define CH_DBG_ENABLE_STACK_CHECK TRUE
/* 线程栈填充 - 用魔术值填充栈空间便于检测 */#define CH_DBG_FILL_THREADS TRUE
/* 性能测量钩子 - 启用性能测量回调 */#define CH_DBG_ENABLE_HOOKS TRUE二、调试宏详解
2.1 系统状态检查(CH_DBG_SYSTEM_STATE_CHECK)
此选项启用对系统状态的检查,确保API调用在正确的上下文中进行。例如,在中断处理器中调用某些不允许的API时会触发断言。
/* 启用后,以下代码会触发断言失败 */void ISR_handler(void) { /* 在中断中调用chThdSleep会触发断言 */ chThdSleep(MS2ST(100)); /* 错误!中断中不能睡眠 */}2.2 API参数检查(CH_DBG_ENABLE_CHECKS)
此选项启用对API参数有效性的检查。如果传递了无效参数(如NULL指针、超出范围的值),会触发断言失败。
/* 启用后,以下代码会触发断言失败 */void bad_example(void) { /* 传递NULL指针 */ chSemWait(NULL); /* 错误!NULL指针 */
/* 传递超出范围的优先级 */ chThdCreateStatic(wa_thread, sizeof(wa_thread), 256, /* 错误!最大优先级为255 */ thread_func, NULL);}2.3 断言检查(CH_DBG_ENABLE_ASSERTS)
断言是最常用的调试工具,用于验证程序逻辑的正确性。ChibiOS/RT提供了 chDbgAssert 宏用于自定义断言。
/* 自定义断言示例 */void process_data(uint8_t *data, size_t len) { /* 断言数据指针不为空 */ chDbgAssert(data != NULL, "data pointer is NULL");
/* 断言数据长度有效 */ chDbgAssert(len > 0 && len <= MAX_DATA_SIZE, "invalid data length");
/* 断言数据内容有效 */ chDbgAssert(validate_checksum(data, len), "data checksum mismatch");
/* 正常处理 */ do_processing(data, len);}
/* 条件断言 */void process_sensor(sensor_t *sensor) { chDbgCheck(sensor != NULL);
/* 检查传感器状态 */ if (sensor->calibrated) { chDbgAssert(sensor->offset != 0, "sensor not properly calibrated"); }}三、栈溢出检查
栈溢出是嵌入式系统中最常见也最难调试的问题之一。ChibiOS/RT提供了多种机制来检测和预防栈溢出。
3.1 栈水印检查(CH_DBG_ENABLE_STACK_CHECK)
栈水印(Stack Watermark)是一种高效的栈使用检测方法。它在线程栈的底部填充特定的魔术值,运行时检查这些值是否被破坏。
/* chconf.h 配置 */#define CH_DBG_ENABLE_STACK_CHECK TRUE#define CH_DBG_STACK_LIMIT 64 /* 栈溢出警告阈值(字节) */
/* 检查线程栈使用情况 */void check_stack_usage(void) { thread_t *tp = chThdGetSelfX();
/* 获取栈使用量 */ size_t used = chThdGetUsedStack(tp); size_t limit = chThdGetLimitStack(tp);
chprintf((BaseSequentialStream *)&SD1, "Thread: %s, Stack used: %d/%d bytes\r\n", tp->name, (int)used, (int)limit);
/* 检查是否接近栈溢出 */ if (used > limit - CH_DBG_STACK_LIMIT) { chprintf((BaseSequentialStream *)&SD1, "WARNING: Stack almost full!\r\n"); }}3.2 栈填充(CH_DBG_FILL_THREADS)
启用此选项后,ChibiOS/RT会在创建线程时用魔术值(0x55)填充整个栈空间。这使得栈使用情况更加直观,也便于检测栈溢出。
/* 栈填充后的内存布局示意 *//*+------------------+ 栈顶| 线程栈帧 || 局部变量 || 函数调用 |+------------------+ <-- SP(栈指针)| 未使用区域 | <-- 填充了 0x55| 0x55 0x55 ... |+------------------+ 栈底*/
/* 检查栈填充是否被破坏 */void check_stack_filling(thread_t *tp) { uint8_t *stack_base = (uint8_t *)tp->wabase; uint8_t *stack_limit = stack_base + chThdGetLimitStack(tp);
/* 检查栈底区域的填充值 */ for (uint8_t *p = stack_base; p < stack_limit; p++) { if (*p != 0x55) { chprintf((BaseSequentialStream *)&SD1, "Stack corruption detected at %p\r\n", p); break; } }}3.3 栈溢出检测实践
/* 完整的栈溢出检测方案 */#define STACK_SIZE 512static THD_WORKING_AREA(wa_worker, STACK_SIZE);
static THD_FUNCTION(worker_thread, arg) { (void)arg;
/* 获取当前线程信息 */ thread_t *tp = chThdGetSelfX(); size_t initial_sp = (size_t)tp->sp;
chprintf((BaseSequentialStream *)&SD1, "Worker thread started, SP: 0x%08X\r\n", initial_sp);
/* 模拟可能导致栈溢出的操作 */ while (true) { /* 定期检查栈使用情况 */ size_t current_sp; __asm volatile("mov %0, sp" : "=r"(current_sp));
size_t stack_used = initial_sp - current_sp; if (stack_used > STACK_SIZE - 64) { chprintf((BaseSequentialStream *)&SD1, "Stack overflow imminent! Used: %d bytes\r\n", (int)stack_used); }
chThdSleepMilliseconds(100); }}四、性能测量
ChibiOS/RT提供了强大的性能测量工具,帮助开发者分析系统性能瓶颈。
4.1 时间测量(chTM)
时间测量宏用于精确测量代码段的执行时间:
#include "ch.h"
void performance_measurement_example(void) { time_measurement_t tm;
/* 初始化时间测量结构 */ chTMObjectInit(&tm);
/* 开始测量 */ chTMStartMeasurementX(&tm);
/* 要测量的代码 */ for (int i = 0; i < 1000; i++) { process_data(i); }
/* 停止测量 */ chTMStopMeasurementX(&tm);
/* 输出测量结果 */ chprintf((BaseSequentialStream *)&SD1, "Elapsed: %d cycles, %d ticks\r\n", (int)tm.n, (int)tm.best);}4.2 性能钩子
通过启用性能钩子,可以在每次上下文切换、空闲循环等事件时执行自定义代码:
/* chconf.h 配置 */#define CH_DBG_ENABLE_HOOKS TRUE
/* 上下文切换钩子 */void chDbgContextSwitchHook(thread_t *ntp, thread_t *otp) { /* 记录上下文切换 */ trace_context_switch(otp->name, ntp->name);}
/* 空闲循环钩子 */void chDbgIdleHook(void) { /* 更新空闲计数器 */ idle_counter++;
/* 测量空闲时间 */ idle_start = chVTGetSystemTimeX();}
/* 锁入口钩子 */void chDbgLockEnterHook(void) { lock_entry_time = chVTGetSystemTimeX();}
/* 锁退出钩子 */void chDbgLockExitHook(void) { lock_exit_time = chVTGetSystemTimeX(); lock_duration += lock_exit_time - lock_entry_time;}4.3 CPU使用率测量
/* 基于空闲钩子的CPU使用率测量 */static volatile uint32_t idle_counter = 0;static volatile uint32_t total_counter = 0;
/* 定期采样的定时器回调 */static void cpu_usage_timer_cb(virtual_timer_t *vtp, void *param) { (void)vtp; (void)param;
/* 计算CPU使用率 */ uint32_t idle = idle_counter; uint32_t total = total_counter; uint32_t used = total - idle;
uint32_t usage = (used * 100) / total;
chprintf((BaseSequentialStream *)&SD1, "CPU Usage: %d%% (idle: %d, total: %d)\r\n", (int)usage, (int)idle, (int)total);
/* 重置计数器 */ idle_counter = 0; total_counter = 0;}
void cpu_usage_init(void) { static virtual_timer_t vt;
/* 启动定时器,每1秒采样一次 */ chVTSetContinuous(&vt, S2ST(1), cpu_usage_timer_cb, NULL);}
/* 空闲钩子中递增计数器 */void chDbgIdleHook(void) { idle_counter++;}五、系统检查
ChibiOS/RT提供了系统级的检查函数,用于验证内核对象的状态和完整性。
5.1 对象验证(chDbgCheck)
chDbgCheck 用于验证对象是否有效,检查对象的魔数(magic number)是否正确:
/* 验证信号量对象 */void validate_semaphore(semaphore_t *sp) { chDbgCheck(sp != NULL); chDbgAssert(chSemGetStateI(sp) >= 0, "invalid semaphore state");}
/* 验证互斥锁对象 */void validate_mutex(mutex_t *mp) { chDbgCheck(mp != NULL); chDbgAssert(chMtxGetStateX(mp) >= 0, "invalid mutex state");}
/* 验证线程对象 */void validate_thread(thread_t *tp) { chDbgCheck(tp != NULL); chDbgAssert(tp->state >= THD_STATE_READY && tp->state <= THD_STATE_WTONE, "invalid thread state");}5.2 系统完整性检查
/* 完整的系统健康检查 */typedef struct { bool_t threads_valid; bool_t timers_valid; bool_t semaphores_valid; bool_t memory_valid; uint32_t error_count;} system_health_t;
system_health_t check_system_health(void) { system_health_t health = {0};
/* 检查所有线程 */ thread_t *tp; chSysLock(); tp = ch.rlist.rlink_predecessor; while (tp != (thread_t *)&ch.rlist) { if (tp->state == THD_STATE_WTFINALIZE) { health.threads_valid = FALSE; health.error_count++; } tp = tp->rlist.rlink_predecessor; } chSysUnlock();
/* 检查内存池 */ memory_heap_t *heapp = NULL; /* ... 遍历所有堆 ... */
/* 检查虚拟定时器 */ chSysLock(); virtual_timer_t *vtp = ch.vtlist.vt_next; while (vtp != (virtual_timer_t *)&ch.vtlist) { if (vtp->vt.func == NULL) { health.timers_valid = FALSE; health.error_count++; } vtp = vtp->vt_next; } chSysUnlock();
return health;}六、内核数据结构
6.1 线程结构体(thread_t)
thread_t 是ChibiOS/RT中最核心的数据结构,代表一个线程的完整上下文:
struct thread { /* 内核链表 */ ch_list_t rlist; /* 就绪列表节点 */
/* 栈信息 */ void *wabase; /* 工作区基地址 */ void *sp; /* 栈指针 */ void *limit; /* 栈限制 */
/* 线程属性 */ tstate_t state; /* 线程状态 */ tprio_t prio; /* 优先级 */ bool umode; /* 用户模式标志 */
/* 等待信息 */ msg_t msg; /* 等待消息 */ sysinterval_t utime; /* 延迟时间 */ sysinterval_t vttim; /* 虚拟定时器 */
/* 扩展数据 */ void *user_data; /* 用户数据指针 */ const char *name; /* 线程名称 */};6.2 线程状态
typedef enum { THD_STATE_READY = 0, /* 就绪状态 */ THD_STATE_CURRENT, /* 当前运行状态 */ THD_STATE_WTSTART, /* 等待启动 */ THD_STATE_WTSEMAPHORE, /* 等待信号量 */ THD_STATE_WTCOND, /* 等待条件变量 */ THD_STATE_WTMTX, /* 等待互斥锁 */ THD_STATE_WTMSG, /* 等待消息 */ THD_STATE_WTQUEUE, /* 等待队列 */ THD_STATE_SNDNXT, /* 发送下一条消息 */ THD_STATE_WT Events, /* 等待事件 */ THD_STATE_WTOREVT, /* 等待或事件 */ THD_STATE_WTANDEVT, /* 等待与事件 */ THD_STATE_SLEEPEVT, /* 事件睡眠 */ THD_STATE_FINAL, /* 终止状态 */ THD_STATE_WTFINALIZE /* 等待终止 */} tstate_t;6.3 虚拟定时器(virtual_timer_t)
虚拟定时器用于实现软件定时器,支持单次和周期性定时:
typedef struct virtual_timer { virtual_timer_t *vt_next; /* 下一个定时器 */ virtual_timer_t *vt_prev; /* 上一个定时器 */ sysinterval_t vt_delta; /* 增量值 */ vtfunc_t vt_func; /* 回调函数 */ void *vt_par; /* 回调参数 */} virtual_timer_t;
/* 定时器回调函数类型 */typedef void (*vtfunc_t)(virtual_timer_t *vtp, void *param);
/* 使用示例 */static void timer_callback(virtual_timer_t *vtp, void *param) { (void)vtp; int *counter = (int *)param; (*counter)++; chprintf((BaseSequentialStream *)&SD1, "Timer fired: %d\r\n", *counter);}
void timer_example(void) { static virtual_timer_t vt; static int counter = 0;
/* 启动单次定时器,1秒后触发 */ chVTSet(&vt, S2ST(1), timer_callback, &counter);
/* 启动周期定时器,每500ms触发一次 */ chVTSetContinuous(&vt, MS2ST(500), timer_callback, &counter);}6.4 信号量(semaphore_t)
信号量用于线程同步和资源计数:
typedef struct { cnt_t cnt; /* 计数器 */ /* ... 等待队列 ... */} semaphore_t;
/* 使用示例 */static SEMAPHORE_DECL(sem, 0); /* 声明并初始化信号量 */
void producer(void) { /* 产生资源 */ chSemSignal(&sem); /* 释放信号量 */}
void consumer(void) { chSemWait(&sem); /* 等待信号量 */ /* 使用资源 */}6.5 互斥锁(mutex_t)
互斥锁用于保护共享资源,支持优先级继承协议:
typedef struct { tcnt_t cnt; /* 锁计数 */ tprio_t owner_prio; /* 持有者原始优先级 */ tprio_t orgprio; /* 原始优先级 */ thread_t *owner; /* 持有者线程 */ /* ... 等待队列 ... */} mutex_t;
/* 使用示例 */static MUTEX_DECL(mtx);
void protected_function(void) { chMtxLock(&mtx); /* 获取互斥锁 */ /* 临界区代码 */ chMtxUnlock(&mtx); /* 释放互斥锁 */}6.6 事件源(event_source_t)
事件源用于异步事件通知:
typedef struct { eventmask_t mask; /* 事件掩码 */ /* ... 等待线程列表 ... */} event_source_t;
/* 使用示例 */static event_source_t es_source;
void event_source_init(void) { chEvtObjectInit(&es_source);}
void event_handler(eventid_t id) { switch (id) { case 0: chprintf((BaseSequentialStream *)&SD1, "Event 0\r\n"); break; case 1: chprintf((BaseSequentialStream *)&SD1, "Event 1\r\n"); break; }}
void event_thread(void) { event_listener_t el;
/* 注册事件监听器 */ chEvtRegister(&es_source, &el, 0);
while (true) { eventmask_t mask = chEvtWaitAny(ALL_EVENTS); chEvtDispatch(event_handler, mask); }}七、基础数据结构
7.1 双向链表(ch_list_t)
双向链表是ChibiOS/RT中最基础的数据结构,用于组织各种内核对象:
typedef struct ch_list { struct ch_list *next; /* 下一个节点 */ struct ch_list *prev; /* 上一个节点 */} ch_list_t;
/* 链表操作宏 */#define CH_LIST_INIT(p) { (p)->next = (p); (p)->prev = (p); }#define CH_LIST_IS_EMPTY(p) ((p)->next == (p))#define CH_LIST_INSERT_AFTER(l, n) { \ (n)->next = (l)->next; \ (n)->prev = (l); \ (l)->next->prev = (n); \ (l)->next = (n); \}#define CH_LIST_INSERT_BEFORE(l, n) { \ (n)->prev = (l)->prev; \ (n)->next = (l); \ (l)->prev->next = (n); \ (l)->prev = (n); \}#define CH_LIST_REMOVE(n) { \ (n)->prev->next = (n)->next; \ (n)->next->prev = (n)->prev; \}
/* 使用示例 */typedef struct { ch_list_t list_node; /* 链表节点必须是第一个成员 */ int data; char name[32];} my_item_t;
void list_example(void) { ch_list_t list; CH_LIST_INIT(&list);
my_item_t item1 = {.data = 1, .name = "item1"}; my_item_t item2 = {.data = 2, .name = "item2"};
/* 插入节点 */ CH_LIST_INSERT_BEFORE(&list, &item1.list_node); CH_LIST_INSERT_BEFORE(&list, &item2.list_node);
/* 遍历链表 */ ch_list_t *node = list.next; while (node != &list) { my_item_t *item = (my_item_t *)node; chprintf((BaseSequentialStream *)&SD1, "%s: %d\r\n", item->name, item->data); node = node->next; }}7.2 队列(ch_queue_t)
ChibiOS/RT内部使用的队列数据结构:
typedef struct { ch_list_t *head; /* 队列头 */ ch_list_t *tail; /* 队列尾 */ cnt_t cnt; /* 元素数量 */} ch_queue_t;
/* 队列操作 */#define CH_QUEUE_INIT(q) { (q)->head = NULL; (q)->tail = NULL; (q)->cnt = 0; }#define CH_QUEUE_IS_EMPTY(q) ((q)->head == NULL)
/* 入队操作 */static inline void ch_queue_insert(ch_queue_t *qp, ch_list_t *item) { item->next = NULL; item->prev = qp->tail; if (qp->tail != NULL) { qp->tail->next = item; } else { qp->head = item; } qp->tail = item; qp->cnt++;}
/* 出队操作 */static inline ch_list_t *ch_queue_remove_head(ch_queue_t *qp) { ch_list_t *item = qp->head; if (item != NULL) { qp->head = item->next; if (qp->head != NULL) { qp->head->prev = NULL; } else { qp->tail = NULL; } qp->cnt--; } return item;}7.3 优先级队列(ch_priority_queue_t)
优先级队列用于按优先级排序的线程调度:
typedef struct { ch_list_t *head; /* 队列头 */ cnt_t cnt; /* 元素数量 */} ch_priority_queue_t;
/* 优先级队列操作 */#define CH_PRIORITY_QUEUE_INIT(q) { (q)->head = NULL; (q)->cnt = 0; }
/* 插入节点(按优先级排序) */static inline void ch_priority_queue_insert(ch_priority_queue_t *pq, ch_list_t *item, tprio_t prio) { (void)prio; ch_list_t *node = pq->head; ch_list_t *prev = NULL;
/* 找到插入位置 */ while (node != NULL) { /* 这里简化了比较逻辑,实际实现更复杂 */ prev = node; node = node->next; }
/* 插入节点 */ if (prev != NULL) { prev->next = item; item->prev = prev; } else { pq->head = item; item->prev = NULL; } item->next = node; if (node != NULL) { node->prev = item; } pq->cnt++;}
/* 移除最高优先级节点 */static inline ch_list_t *ch_priority_queue_remove_highest(ch_priority_queue_t *pq) { ch_list_t *item = pq->head; if (item != NULL) { pq->head = item->next; if (pq->head != NULL) { pq->head->prev = NULL; } pq->cnt--; } return item;}7.4 增量链表(ch_delta_list_t)
增量链表用于实现虚拟定时器的高效管理:
typedef struct { ch_list_t *head; /* 链表头 */ cnt_t cnt; /* 节点数量 */} ch_delta_list_t;
/* 增量链表操作 */#define CH_DELTA_LIST_INIT(dl) { (dl)->head = NULL; (dl)->cnt = 0; }
/* 插入定时器节点(按增量值排序) */static inline void ch_delta_list_insert(ch_delta_list_t *dl, ch_list_t *item, sysinterval_t delta) { (void)delta; ch_list_t *node = dl->head; ch_list_t *prev = NULL;
/* 找到插入位置(按增量值排序) */ while (node != NULL) { /* 实际实现中会检查增量值 */ prev = node; node = node->next; }
/* 插入节点 */ if (prev != NULL) { prev->next = item; item->prev = prev; } else { dl->head = item; item->prev = NULL; } item->next = node; if (node != NULL) { node->prev = item; } dl->cnt++;}
/* 移除头节点 */static inline ch_list_t *ch_delta_list_remove_head(ch_delta_list_t *dl) { ch_list_t *item = dl->head; if (item != NULL) { dl->head = item->next; if (dl->head != NULL) { dl->head->prev = NULL; } dl->cnt--; } return item;}八、类型别名
ChibiOS/RT定义了一系列类型别名,提高代码的可读性和可移植性:
/* 基本类型 */typedef int32_t msg_t; /* 消息类型 */typedef int32_t cnt_t; /* 计数器类型 */typedef uint32_t tprio_t; /* 线程优先级 */typedef uint32_t sysinterval_t; /* 系统间隔时间 */typedef systime_t sysinterval_t; /* 系统时间间隔 */
/* 线程状态 */typedef uint8_t tstate_t; /* 线程状态 */
/* 事件相关 */typedef uint32_t eventmask_t; /* 事件掩码 */typedef uint32_t eventid_t; /* 事件ID */
/* 时间测量 */typedef struct { rtcnt_t best; /* 最佳时间 */ rtcnt_t worst; /* 最差时间 */ rtcnt_t cumulated; /* 累计时间 */ rtcnt_t last; /* 上次时间 */ cnt_t n; /* 测量次数 */} time_measurement_t;
/* 内存池 */typedef struct { void *free; /* 空闲列表 */ /* ... 其他字段 ... */} memory_pool_t;
/* 堆 */typedef struct { void *next; /* 下一个堆块 */ size_t size; /* 堆块大小 */} memory_heap_t;使用示例
/* 类型安全的函数原型 */static THD_FUNCTION(worker_thread, void *arg) { (void)arg;
/* 使用正确的类型 */ msg_t result = MSG_OK; cnt_t count = 0; tprio_t my_prio = chThdGetPriorityX(); sysinterval_t timeout = TIME_MS2I(100);
/* 类型安全的API调用 */ result = chSemWaitTimeout(&sem, timeout); if (result == MSG_OK) { count++; }
chprintf((BaseSequentialStream *)&SD1, "Priority: %d, Count: %d\r\n", (int)my_prio, (int)count);}九、总结
ChibiOS/RT提供了完善的调试支持和高效的基础数据结构:
调试支持
- 系统状态检查:验证API调用上下文
- 参数检查:验证API参数有效性
- 断言:验证程序逻辑正确性
- 栈溢出检查:检测和预防栈溢出
- 性能测量:分析系统性能瓶颈
内核数据结构
- thread_t:线程核心结构
- virtual_timer_t:软件定时器
- semaphore_t:信号量同步
- mutex_t:互斥锁保护
- event_source_t:事件通知
基础数据结构
- ch_list_t:双向链表
- ch_queue_t:队列
- ch_priority_queue_t:优先级队列
- ch_delta_list_t:增量链表
合理利用这些调试工具和数据结构,可以显著提高嵌入式系统的开发效率和运行可靠性。
部分信息可能已经过时