IPC Trace
6.5 IPC Trace
📚 本节导读
学习时长: 约 30 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务同步与通信(第4.3节)、信号量(第4.3.1节)
🎯 学习目标
- 理解 IPC Trace 的设计目的和架构
- 掌握 IPC Hook 机制的使用方法
- 掌握信号量追踪 Hook 点的注册和回调
- 理解环形缓冲区在追踪记录中的应用
- 能够分析 IPC Trace 追踪数据
一、概述
IPC Trace 是 OneOS 提供的 IPC 通信追踪工具,用于记录和分析任务间通信(Inter-Process Communication)的时序和状态。当系统出现死锁、优先级反转、资源竞争等疑难问题时,IPC Trace 可以帮助开发者快速定位问题根源。
IPC Trace 的核心设计思想是非侵入式 Hook 注入:
┌─────────────────────────────────────────────────┐
│ IPC Trace 架构 │
├─────────────────────────────────────────────────┤
│ ┌─────────┐ Hook Call ┌──────────────────┐ │
│ │ IPC 对象 │ ────────────→ │ IPC Trace Hook │ │
│ │ (Sem) │ │ (os_ipc_trace) │ │
│ └─────────┘ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐│
│ │ 环形缓冲区 (Ring Buffer) ││
│ │ ┌─────┬─────┬─────┬─────┬─────┬─────┐ ││
│ │ │Item0│Item1│Item2│ ... │ItemN│ ... │ ││
│ │ └─────┴─────┴─────┴─────┴─────┴─────┘ ││
│ └──────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐│
│ │ 追踪数据分析 (Shell) ││
│ └──────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘二、核心数据结构
2.1 IPC Trace 记录项 (os_ipc_trace_item_t)
来自 os_ipc_trace.h:
struct os_ipc_trace_item
{
os_ubase_t desc;
os_task_id task;
os_ubase_t irq;
};
typedef struct os_ipc_trace_item os_ipc_trace_item_t;| 成员 | 类型 | 说明 |
|---|---|---|
desc | os_ubase_t | 描述字段,通常编码操作类型(如 wait/post)和状态(如 success/block) |
task | os_task_id | 触发该 IPC 操作的任务 ID,中断上下文则为 NULL |
irq | os_ubase_t | 标记是否在 ISR 中触发,0 表示任务上下文,非 0 表示中断上下文 |
关键理解:
os_ipc_trace_item仅 3 个成员(desc+task+irq),32 位平台各 4 字节,共 12 字节。每次入队通过rb_ring_buff_put_force写入这 12 字节(见 5.2)。
2.2 IPC Trace 记录缓冲区 (os_ipc_trace_rec_t)
来自 os_ipc_trace.h:
struct os_ipc_trace_rec
{
os_ipc_trace_item_t record[OS_IPC_TRACE_ITEM_NUM];
os_ubase_t total_cnt;
os_ubase_t valid_cnt;
rb_ring_buff_t rb;
};
typedef struct os_ipc_trace_rec os_ipc_trace_rec_t;| 成员 | 说明 |
|---|---|
record[] | 固定大小的追踪记录数组,大小由 OS_IPC_TRACE_ITEM_NUM(默认 10)决定 |
total_cnt | 累计入队的总次数(用于统计) |
valid_cnt | 当前缓冲区中有效的记录数(≤ OS_IPC_TRACE_ITEM_NUM) |
rb | 环形缓冲区实例,基于 record[] 数组分配 |
2.3 IPC Trace 对象 (os_ipc_trace_t)
来自 os_ipc_trace.h:
struct os_ipc_trace
{
os_ubase_t ipc_obj;
os_ipc_trace_rec_t trace;
};
typedef struct os_ipc_trace os_ipc_trace_t;每个 IPC 对象(如信号量)可以关联一个独立的 os_ipc_trace_t,记录该对象的所有操作历史。
三、IPC Hook 机制
IPC Hook 是 OneOS 内核提供的一套通用的 Hook 注入框架,通过宏定义实现编译时 Hook 点的注册和调用。定义在 os_ipc_hook.h 中。
3.1 Hook 宏体系(完整源码)
宏体系在 os_ipc_hook.h 中,受 OS_USING_IPC_HOOK 控制。完整源码:
#if !defined(OS_USING_IPC_HOOK)
#define OS_IPC_HOOK_DEFINE(domain, hp, sta, ...)
#define OS_IPC_HOOK_DECLARE(domain, hp, sta, ...)
#define OS_IPC_HOOK_CALL(domain, hp, sta, ...)
#define OS_IPC_HOOK_ADD(domain, hp, sta, func)
#define OS_IPC_HOOK_DEL(domain, hp, sta, func)
#else
#define OS_IPC_HOOK_DEFINE(domain, hp, sta, ...) void (*gs_os_hook_##domain##_##hp##_##sta)(__VA_ARGS__) = OS_NULL
#define OS_IPC_HOOK_DECLARE(domain, hp, sta, ...) extern void (*gs_os_hook_##domain##_##hp##_##sta)(__VA_ARGS__)
#define OS_IPC_HOOK_CALL(domain, hp, sta, ...) \
do \
{ \
if (gs_os_hook_##domain##_##hp##_##sta) \
{ \
gs_os_hook_##domain##_##hp##_##sta(__VA_ARGS__); \
} \
} while (0);
#define OS_IPC_HOOK_ADD(domain, hp, sta, func) \
(!(gs_os_hook_##domain##_##hp##_##sta)) ? ((gs_os_hook_##domain##_##hp##_##sta = func), OS_TRUE) : (OS_FALSE)
#define OS_IPC_HOOK_DEL(domain, hp, sta, func) (gs_os_hook_##domain##_##hp##_##sta = OS_NULL), OS_TRUE
#endif关键理解:
- 关闭开关时(未定义
OS_USING_IPC_HOOK):5 个宏全部展开为空,零开销; - 开启开关时:
OS_IPC_HOOK_DEFINE定义全局函数指针变量并初始化为OS_NULL(在源文件用);OS_IPC_HOOK_DECLARE用extern声明该指针(在头文件用);OS_IPC_HOOK_CALL用do{...}while(0)包裹的空指针判断后调用;OS_IPC_HOOK_ADD用三元表达式:指针为空则赋func并返回OS_TRUE,否则返回OS_FALSE(防止重复注册);OS_IPC_HOOK_DEL置OS_NULL注销并返回OS_TRUE;##连接符把domain/hp/sta拼成唯一标识符(如gs_os_hook_sem_wait_success),__VA_ARGS__作为回调函数指针类型参数。
3.2 Hook 三层命名空间
| 层级 | 参数 | 含义 | 示例 |
|---|---|---|---|
| 域 (domain) | domain | IPC 对象类型 | sem(信号量)、mutex(互斥锁)、mq(消息队列) |
| Hook 点 (hp) | hp | 操作类型 | wait(等待操作)、post(释放操作) |
| 状态 (sta) | sta | 操作结果 | success(成功)、block(阻塞)、timeout(超时) |
3.3 Hook 展开示例
以信号量 wait 操作的 success 状态为例:
// 声明 (os_sem_trace.h)
OS_IPC_HOOK_DECLARE(sem, wait, success, os_semaphore_id);
// 展开为: extern void (*gs_os_hook_sem_wait_success)(os_semaphore_id);
// 调用
OS_IPC_HOOK_CALL(sem, wait, success, sem);
// 展开为:
// do {
// if (gs_os_hook_sem_wait_success) {
// gs_os_hook_sem_wait_success(sem);
// }
// } while (0);四、信号量追踪
信号量是 IPC Trace 最典型的应用场景。OneOS 在信号量操作的关键路径上预设了多个 Hook 点。
4.1 信号量 Hook 点
定义在 os_sem_trace.h:
| 操作 | Hook 点 | 状态 | 触发时机 |
|---|---|---|---|
wait | sem_wait | success | 等待成功(获取到信号量) |
wait | sem_wait | fail | 等待失败 |
wait | sem_wait | block | 等待阻塞(信号量不可用) |
wait | sem_wait | timeout | 等待超时 |
post | sem_post | success | 释放成功 |
post | sem_post | full | 信号量已满(达到最大值) |
post | sem_post | wake | 释放后唤醒等待任务 |
4.2 信号量 Hook 调用宏
信号量专用封装宏 OS_SEM_HOOK_CALL,受 OS_USING_SEM_TRACE 控制。完整源码(os_sem_trace.h):
#ifdef OS_USING_SEM_TRACE
#define OS_SEM_HOOK_CALL(sem, hp, sta, ...) OS_IPC_HOOK_CALL(sem, hp, sta, __VA_ARGS__)
#else
#define OS_SEM_HOOK_CALL(sem, hp, sta, ...)
#endif关键理解:开启 OS_USING_SEM_TRACE 时,OS_SEM_HOOK_CALL 透传给 OS_IPC_HOOK_CALL;关闭时展开为空。sem/hp/sta 分别对应信号量、操作点(wait/post)、状态(success/fail/block/timeout/full/wake)。
使用示例:
// 在 os_semaphore_wait 成功时调用
OS_SEM_HOOK_CALL(sem, wait, success, sem);
// 在 os_semaphore_post 唤醒任务时调用
OS_SEM_HOOK_CALL(sem, post, wake, sem);五、IPC Trace 核心 API 详解
5.1 ipc_trace_rec_init —— 初始化追踪记录
函数原型:
os_err_t ipc_trace_rec_init(os_ipc_trace_t *ipct);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_rec_init(os_ipc_trace_t *ipct)
{
os_ubase_t i = 0;
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
ipct->ipc_obj = 0;
ipct->trace.total_cnt = 0;
ipct->trace.valid_cnt = 0;
for (i = 0; i < OS_IPC_TRACE_ITEM_NUM; i++)
{
ipct->trace.record[i].desc = 0;
ipct->trace.record[i].task = 0;
}
rb_ring_buff_init(&ipct->trace.rb, (uint8_t *)ipct->trace.record, sizeof(ipct->trace.record));
return OS_SUCCESS;
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL)
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}ipct 为空则打印错误并返回 OS_INVAL。
第二步:清空计数与关联(无条件执行)
ipct->ipc_obj = 0;
ipct->trace.total_cnt = 0;
ipct->trace.valid_cnt = 0;把关联的 IPC 对象地址、总计数、有效计数全部清零。
第三步:清零记录数组(循环)
for (i = 0; i < OS_IPC_TRACE_ITEM_NUM; i++)
{
ipct->trace.record[i].desc = 0;
ipct->trace.record[i].task = 0;
}把 record[] 每个条目的 desc、task 清零(irq 未显式清零,因为它会被 rb_ring_buff_get 按读取覆盖)。
第四步:初始化环形缓冲区(无条件执行)
rb_ring_buff_init(&ipct->trace.rb, (uint8_t *)ipct->trace.record, sizeof(ipct->trace.record));让 rb 指向 record[] 数组,缓冲区容量为 sizeof(ipct->trace.record) = OS_IPC_TRACE_ITEM_NUM × sizeof(os_ipc_trace_item_t) = 10 × 12 = 120 字节(32 位平台)。
条件走向总结:
ipc_trace_rec_init() 条件走向总结:
├── ipct == OS_NULL → 打印错误,返回 OS_INVAL
└── ipct 非空
├── 清零 ipc_obj / total_cnt / valid_cnt
├── 清零 record[] 全部 desc、task
├── rb_ring_buff_init 挂接 record[](容量 120 字节)
└── 返回 OS_SUCCESS5.2 ipc_trace_rec_enqueue —— 追踪数据入队
函数原型:
os_err_t ipc_trace_rec_enqueue(os_ipc_trace_t *ipct, os_ipc_trace_item_t *trace_data);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_rec_enqueue(os_ipc_trace_t *ipct, os_ipc_trace_item_t *trace_data)
{
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
rb_ring_buff_put_force(&ipct->trace.rb, (uint8_t *)trace_data, sizeof(os_ipc_trace_item_t));
ipct->trace.total_cnt++;
if (ipct->trace.valid_cnt < OS_IPC_TRACE_ITEM_NUM)
{
ipct->trace.valid_cnt++;
}
return OS_SUCCESS;
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL || trace_data == OS_NULL)
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}第二步:强制写入环形缓冲区(无条件执行)
rb_ring_buff_put_force(&ipct->trace.rb, (uint8_t *)trace_data, sizeof(os_ipc_trace_item_t));put_force 表示缓冲区满时覆盖最旧数据(环形缓冲不丢新记录)。写入 sizeof(os_ipc_trace_item_t) = 12 字节。
第三步:更新计数(条件 valid_cnt < OS_IPC_TRACE_ITEM_NUM)
ipct->trace.total_cnt++;
if (ipct->trace.valid_cnt < OS_IPC_TRACE_ITEM_NUM)
{
ipct->trace.valid_cnt++;
}total_cnt 无条件自增(累计全部入队次数,含被覆盖的);valid_cnt 只在未满时自增(表示当前缓冲区有效条目数,上限 OS_IPC_TRACE_ITEM_NUM=10)。
条件走向总结:
ipc_trace_rec_enqueue() 条件走向总结:
├── ipct 或 trace_data 为空 → 打印错误,返回 OS_INVAL
└── 参数有效
├── rb_ring_buff_put_force 写入 12 字节(满则覆盖最旧)
├── total_cnt++(无条件)
├── valid_cnt < 10 → valid_cnt++
└── 返回 OS_SUCCESS数值演算(环形缓冲):设已连续入队 12 次、OS_IPC_TRACE_ITEM_NUM=10:
| 状态 | total_cnt | valid_cnt | 说明 |
|---|---|---|---|
| 前 10 次入队后 | 10 | 10 | 缓冲已满 |
| 第 11 次入队 | 11 | 10 | put_force 覆盖第 1 条,valid_cnt<10 不成立 |
| 第 12 次入队 | 12 | 10 | 覆盖第 2 条,valid_cnt 保持 10 |
即 total_cnt 反映总操作量,valid_cnt 反映缓冲区当前保留的最近条目数(恒 ≤ 10)。
5.3 ipc_trace_rec_dequeue —— 追踪数据出队
函数原型:
os_err_t ipc_trace_rec_dequeue(os_ipc_trace_t *ipct, os_ipc_trace_item_t *trace_data);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_rec_dequeue(os_ipc_trace_t *ipct, os_ipc_trace_item_t *trace_data)
{
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
if (0 == ipct->trace.valid_cnt)
{
/* os_kprintf("IPC TRACE : Trace Queue Empty\r\n"); */
return OS_FAILURE;
}
if (rb_ring_buff_get(&ipct->trace.rb, (uint8_t *)trace_data, sizeof(os_ipc_trace_item_t)) <
sizeof(os_ipc_trace_item_t))
{
return OS_FAILURE;
}
ipct->trace.total_cnt--;
if (ipct->trace.valid_cnt >= 1)
{
ipct->trace.valid_cnt--;
}
return OS_SUCCESS;
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL || trace_data == OS_NULL)
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}第二步:队列空判断(条件 valid_cnt == 0)
if (0 == ipct->trace.valid_cnt)
{
/* os_kprintf("IPC TRACE : Trace Queue Empty\r\n"); */
return OS_FAILURE;
}valid_cnt == 0 表示缓冲区无有效数据,返回 OS_FAILURE(该打印被注释,源码原样保留)。
第三步:读取环形缓冲区(条件读取不足 12 字节)
if (rb_ring_buff_get(&ipct->trace.rb, (uint8_t *)trace_data, sizeof(os_ipc_trace_item_t)) <
sizeof(os_ipc_trace_item_t))
{
return OS_FAILURE;
}调用 rb_ring_buff_get 读出 12 字节;若实际读出不足 12 字节(异常)返回 OS_FAILURE。
第四步:更新计数(条件 valid_cnt >= 1)
ipct->trace.total_cnt--;
if (ipct->trace.valid_cnt >= 1)
{
ipct->trace.valid_cnt--;
}出队成功则 total_cnt--、valid_cnt--(仅当 valid_cnt ≥ 1)。
条件走向总结:
ipc_trace_rec_dequeue() 条件走向总结:
├── ipct 或 trace_data 为空 → 打印错误,返回 OS_INVAL
├── valid_cnt == 0 → 返回 OS_FAILURE(队列空)
├── rb_ring_buff_get 读出不足 12 字节 → 返回 OS_FAILURE
└── 正常读出
├── total_cnt--;valid_cnt ≥ 1 → valid_cnt--
└── 返回 OS_SUCCESS5.4 ipc_trace_rec_add_kobj —— 关联 IPC 对象
函数原型:
os_err_t ipc_trace_rec_add_kobj(os_ipc_trace_t *ipct, os_ubase_t ipc_obj);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_rec_add_kobj(os_ipc_trace_t *ipct, os_ubase_t ipc_obj)
{
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
ipct->ipc_obj = ipc_obj;
return OS_SUCCESS;
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL)
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}第二步:保存 IPC 对象地址(无条件执行)
ipct->ipc_obj = ipc_obj;把 ipc_obj(通常是信号量等 IPC 对象的地址)存入 ipct->ipc_obj。之后 ipc_trace_hook 用它做匹配过滤。
条件走向总结:
ipc_trace_rec_add_kobj() 条件走向总结:
├── ipct == OS_NULL → 打印错误,返回 OS_INVAL
└── ipct 非空 → ipct->ipc_obj = ipc_obj;返回 OS_SUCCESS5.5 ipc_trace_rec_del_kobj —— 解除关联并重置
函数原型:
os_err_t ipc_trace_rec_del_kobj(os_ipc_trace_t *ipct);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_rec_del_kobj(os_ipc_trace_t *ipct)
{
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
ipc_trace_rec_init(ipct);
return OS_SUCCESS;
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL)
if (OS_NULL == ipct)
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}第二步:重置追踪记录(无条件执行)
ipc_trace_rec_init(ipct);直接复用 ipc_trace_rec_init,把 ipc_obj、计数、record[]、环形缓冲区全部重置(ipc_obj 被清零即解除关联)。
条件走向总结:
ipc_trace_rec_del_kobj() 条件走向总结:
├── ipct == OS_NULL → 打印错误,返回 OS_INVAL
└── ipct 非空 → ipc_trace_rec_init(ipct)(含清零 ipc_obj);返回 OS_SUCCESS5.6 ipc_trace_hook —— 追踪 Hook 入口
函数原型:
os_err_t ipc_trace_hook(os_ipc_trace_t *ipct, os_ubase_t ipc_obj, os_ipc_trace_item_t *trace_data);完整源码(os_ipc_trace.c):
os_err_t ipc_trace_hook(os_ipc_trace_t *ipct, os_ubase_t ipc_obj, os_ipc_trace_item_t *trace_data)
{
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}
if (ipct->ipc_obj != ipc_obj)
{
return OS_FAILURE;
}
return ipc_trace_rec_enqueue(ipct, trace_data);
}分步详解:
第一步:参数校验(条件 ipct == OS_NULL || trace_data == OS_NULL)
if ((OS_NULL == ipct) || (OS_NULL == trace_data))
{
os_kprintf("IPC TRACE : Invalid Parameter\r\n");
return OS_INVAL;
}第二步:IPC 对象匹配(条件 ipct->ipc_obj != ipc_obj)
if (ipct->ipc_obj != ipc_obj)
{
return OS_FAILURE;
}只有触发 Hook 的 ipc_obj 与已关联的 ipct->ipc_obj 相同时才记录,实现"只追踪指定对象"。
第三步:入队(无条件执行)
return ipc_trace_rec_enqueue(ipct, trace_data);匹配通过后调用 ipc_trace_rec_enqueue 把该条追踪数据写入环形缓冲区。
条件走向总结:
ipc_trace_hook() 条件走向总结:
├── ipct 或 trace_data 为空 → 打印错误,返回 OS_INVAL
├── ipct->ipc_obj != ipc_obj → 返回 OS_FAILURE(对象不匹配,不记录)
└── 匹配 → 返回 ipc_trace_rec_enqueue(ipct, trace_data)(入队并返回结果)六、配置参数
| 配置项 | 位置 | 默认值 | 说明 |
|---|---|---|---|
OS_USING_IPC_TRACE | Kconfig | n | 启用 IPC Trace 功能 |
OS_IPC_TRACE_ITEM_NUM | os_ipc_trace.h | 10 | 每个 IPC 对象的追踪记录数 |
OS_USING_SEM_TRACE | 条件编译 | n | 启用信号量追踪 |
OS_SEM_WAIT_HOOK | 条件编译 | n | 启用信号量 wait 操作的 Hook |
OS_SEM_POST_HOOK | 条件编译 | n | 启用信号量 post 操作的 Hook |
OS_USING_IPC_HOOK | Kconfig | n | 全局 IPC Hook 开关 |
七、使用示例
7.1 信号量追踪示例
#include <oneos_config.h>
#include <os_ipc_trace.h>
#include <os_ipc_hook.h>
#include <os_sem_trace.h>
/* 定义追踪对象 */
static os_ipc_trace_t g_sem_trace;
/* 信号量 wait 成功时的回调 */
static void sem_wait_success_hook(os_semaphore_id sem)
{
os_ipc_trace_item_t trace_item = {
.desc = 0x01, /* 编码: wait_success */
.task = os_task_self(),
.irq = 0,
};
ipc_trace_hook(&g_sem_trace, (os_ubase_t)sem, &trace_item);
}
/* 信号量 post 唤醒时的回调 */
static void sem_post_wake_hook(os_semaphore_id sem)
{
os_ipc_trace_item_t trace_item = {
.desc = 0x10, /* 编码: post_wake */
.task = os_task_self(),
.irq = 0,
};
ipc_trace_hook(&g_sem_trace, (os_ubase_t)sem, &trace_item);
}
/* 初始化追踪 */
void my_trace_init(void)
{
/* 初始化追踪记录 */
ipc_trace_rec_init(&g_sem_trace);
/* 注册 Hook 回调 */
OS_IPC_HOOK_ADD(sem, wait, success, sem_wait_success_hook);
OS_IPC_HOOK_ADD(sem, post, wake, sem_post_wake_hook);
}7.2 追踪数据分析
/* 读取并分析追踪数据 */
void my_trace_analyze(void)
{
os_ipc_trace_item_t item;
os_kprintf("=== IPC Trace Analysis ===\r\n");
os_kprintf("Total operations: %d\r\n", g_sem_trace.trace.total_cnt);
os_kprintf("Valid records: %d\r\n", g_sem_trace.trace.valid_cnt);
/* 出队所有记录 */
while (ipc_trace_rec_dequeue(&g_sem_trace, &item) == OS_SUCCESS)
{
os_kprintf(" desc:0x%lx task:%p irq:%lu\r\n",
item.desc, (void *)item.task, item.irq);
}
}7.3 关联 IPC 对象
os_semaphore_id my_sem;
void my_sem_trace_init(void)
{
/* 创建信号量 */
my_sem = os_semaphore_create(OS_NULL, "trace_sem", 1, 1);
/* 关联追踪对象 */
ipc_trace_rec_add_kobj(&g_sem_trace, (os_ubase_t)my_sem);
}7.4 完整追踪流程
void task_producer(void *arg)
{
while (1)
{
os_semaphore_wait(my_sem, OS_WAIT_FOREVER);
/* 临界区操作 */
os_semaphore_post(my_sem);
os_task_tsleep(100);
}
}
void task_consumer(void *arg)
{
while (1)
{
os_semaphore_wait(my_sem, OS_WAIT_FOREVER);
/* 临界区操作 */
os_semaphore_post(my_sem);
os_task_tsleep(200);
}
}
/* 运行一段时间后,通过 Shell 命令查看追踪数据 */
/* 可以分析出信号量的竞争情况和等待时间 */📝 本节小结
核心要点回顾
- 非侵入式设计:IPC Trace 通过 Hook 注入机制实现追踪,不改动内核 IPC 操作的主体逻辑,仅在被追踪点回调。
- Hook 宏体系:
OS_IPC_HOOK_DEFINE/DECLARE/CALL/ADD/DEL五个宏,受OS_USING_IPC_HOOK控制;关闭时全部展开为空、零开销;开启时用##拼接标识符(如gs_os_hook_sem_wait_success)。 - 三层命名空间:域
domain(sem/mutex/mq)→ Hook 点hp(wait/post)→ 状态sta(success/fail/block/timeout/full/wake),粒度精确到"哪个对象、哪个操作、哪个结果"。 - 信号量追踪:
os_sem_trace.h在OS_USING_SEM_TRACE下声明 7 个 Hook 点(wait 4 个 + post 3 个),经OS_SEM_HOOK_CALL透传调用。 - 环形缓冲区:
record[OS_IPC_TRACE_ITEM_NUM=10]复用为环形缓冲,rb_ring_buff_put_force满时覆盖最旧数据;total_cnt累计总操作数、valid_cnt表示当前有效条数(恒 ≤ 10)。 - 对象匹配过滤:
ipc_trace_rec_add_kobj绑定要追踪的 IPC 对象,ipc_trace_hook仅当ipct->ipc_obj == ipc_obj才入队,实现"只追指定对象"。 - 生命周期:
init(清空并挂环形缓冲)→add_kobj(绑定对象)→ Hook 回调入队 →dequeue(读取分析)→del_kobj(复用 init 重置)。 - 应用价值:追踪数据可用于分析死锁、优先级反转、资源竞争等并发疑难问题。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 在源码结构体中加中文注释 | 违反"逐字一致"规范 → 保留源码英文注释,中文解释写正文 |
只开 OS_USING_IPC_TRACE 不开 OS_USING_IPC_HOOK | Hook 宏全部为空 → 同时开启二者才能追踪 |
信号量追踪不开 OS_USING_SEM_TRACE | OS_SEM_HOOK_CALL 为空 → 先开 OS_USING_SEM_TRACE 及 OS_SEM_WAIT_HOOK/OS_SEM_POST_HOOK |
忘记 ipc_trace_rec_add_kobj 绑定对象 | ipc_trace_hook 因 ipc_obj 不匹配永远返回 OS_FAILURE,无任何记录 |
混淆 total_cnt 与 valid_cnt | total_cnt 是累计总操作数(含被覆盖的);valid_cnt 是缓冲区当前保留条数(≤10) |
分析时用 total_cnt 当当前条目 | 应遍历 valid_cnt 条,或用 dequeue 读到 OS_FAILURE 为止 |
忽略 ipc_trace_rec_init 先初始化 | rb 未挂接 record[] 就入队会异常 → 先 init 再 add_kobj |
并发读写 trace 不加锁 | 若多个任务同时入队可能竞争 → 结合调度/中断保护使用 |
下一步
- 7.1 文件系统:OneOS 已覆盖内核调试工具,下一章进入常用组件详解。文件系统是嵌入式产品数据持久化的核心组件,掌握 VFS 抽象与 FAT/LittleFS 的使用,可与本章的追踪、回溯能力配合完成复杂系统问题定位。