4.2.9 任务钩子
4.2.9 任务钩子
📚 本节导读
学习时长: 约 15 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务调度机制、回调函数概念
🎯 学习目标
- 理解任务钩子(Task Hook)的作用和配置方式
- 掌握
os_task_switch_hook_add()和os_task_switch_hook_delete()的用法 - 理解 SMP 和单核模式下钩子函数的签名差异
- 了解任务钩子的典型应用场景
一、任务钩子概述
1.1 什么是任务钩子?
任务钩子(Task Hook)是 OneOS 提供的一种扩展机制,允许用户在每次任务切换时执行自定义的回调函数。通过任务钩子,开发者可以在不修改内核代码的情况下,实现对任务切换行为的监控、追踪和分析。
1.2 配置使能
任务钩子功能默认关闭,需要通过 Kconfig 启用:
config OS_USING_TASK_HOOK
bool "Using task hook"
default n
help
Allows user to register hooks with task module.启用后,os_task.h 中会定义 OS_TASK_HOOK_CALL 宏和钩子函数类型。
注意:启用
OS_USING_CPU_MONITOR会自动启用OS_USING_TASK_HOOK,因为 CPU 监控依赖于任务切换钩子。
二、钩子函数签名
2.1 单核模式
typedef void (*os_task_switch_hook_t)(os_task_id from, os_task_id to);| 参数 | 说明 |
|---|---|
from | 切换前的任务(即将让出 CPU 的任务) |
to | 切换后的任务(即将获得 CPU 的任务) |
2.2 SMP 模式
typedef void (*os_task_switch_hook_t)(os_task_id from, os_task_id to, int32_t cpu);| 参数 | 说明 |
|---|---|
from | 切换前的任务 |
to | 切换后的任务 |
cpu | 发生切换的 CPU 核心编号 |
2.3 OS_TASK_HOOK_CALL 宏
#define OS_TASK_HOOK_CALL(func, argv) \
do \
{ \
if ((func) != OS_NULL) \
{ \
func argv; \
} \
} while (0)该宏在调用钩子函数前检查函数指针是否为空,确保安全调用。当 OS_USING_TASK_HOOK 未启用时,该宏展开为空操作。
三、API 函数
3.1 os_task_switch_hook_add() —— 添加任务切换钩子
os_bool_t os_task_switch_hook_add(os_task_switch_hook_t hook);| 返回值 | 说明 |
|---|---|
OS_TRUE | 添加成功 |
OS_FALSE | 添加失败(已有钩子注册) |
注意:OneOS 当前只支持注册一个任务切换钩子。如果已有钩子注册,再次调用会返回 OS_FALSE。
内部实现:
os_bool_t os_task_switch_hook_add(os_task_switch_hook_t hook)
{
if (OS_NULL == gs_os_task_switch_hook)
{
gs_os_task_switch_hook = hook; /* 注册钩子 */
return OS_TRUE;
}
else
{
return OS_FALSE; /* 已有钩子,无法注册 */
}
}3.2 os_task_switch_hook_delete() —— 删除任务切换钩子
os_bool_t os_task_switch_hook_delete(os_task_switch_hook_t hook);| 返回值 | 说明 |
|---|---|
OS_TRUE | 删除成功 |
OS_FALSE | 删除失败(钩子未找到) |
注意:删除时需传入与注册时相同的函数指针,否则会返回 OS_FALSE。
四、钩子的调用时机
任务切换钩子在 os_task_switch_notify() 函数中被调用,该函数在每次任务切换时执行:
void os_task_switch_notify(void)
{
os_task_t *current_task; /* 切换前任务 */
os_task_t *next_task; /* 切换后任务 */
/* 获取当前任务和下一个任务 */
current_task = g_os_current_task; /* 单核 */
next_task = g_os_next_task;
/* 堆栈溢出检测(如果启用) */
#ifdef OS_USING_OVERFLOW_CHECK
_k_task_stack_check(current_task, next_task);
#endif
/* 调用任务切换钩子 */
#ifdef OS_USING_SMP
OS_TASK_HOOK_CALL(gs_os_task_switch_hook,
(current_task, next_task, current_cpu_index));
#else
OS_TASK_HOOK_CALL(gs_os_task_switch_hook,
(current_task, next_task));
#endif
}注意:
os_task_switch_notify()仅在启用了OS_USING_OVERFLOW_CHECK或OS_USING_TASK_HOOK时才会被编译。这两个宏会触发OS_TASK_SWITCH_NOTIFY宏的定义。
五、使用示例
5.1 任务切换追踪
#include <os_task.h>
/* 任务切换钩子函数 */
void my_task_switch_hook(os_task_id from, os_task_id to)
{
const char *from_name = "NONE";
const char *to_name = "NONE";
if (from != OS_NULL)
{
from_name = os_task_get_name(from);
}
if (to != OS_NULL)
{
to_name = os_task_get_name(to);
}
os_kprintf("Switch: %s → %s\r\n", from_name, to_name);
}
/* 注册钩子 */
void app_init(void)
{
os_task_switch_hook_add(my_task_switch_hook);
}5.2 CPU 使用率统计
#include <os_task.h>
#include <os_clock.h>
typedef struct {
const char *name;
os_tick_t total_ticks;
os_tick_t last_switch_time;
os_bool_t is_active;
} task_cpu_stat_t;
#define MAX_TASK_STAT 10
static task_cpu_stat_t g_task_stats[MAX_TASK_STAT];
static int g_stat_count = 0;
/* 记录任务切换时的 CPU 使用情况 */
void cpu_stat_hook(os_task_id from, os_task_id to)
{
os_tick_t now = os_tick_get_value();
int i;
/* 更新 from 任务的运行时间 */
for (i = 0; i < g_stat_count; i++)
{
if (g_task_stats[i].is_active)
{
g_task_stats[i].total_ticks += (now - g_task_stats[i].last_switch_time);
g_task_stats[i].is_active = OS_FALSE;
break;
}
}
/* 标记 to 任务开始运行 */
for (i = 0; i < g_stat_count; i++)
{
if (os_task_get_id(g_task_stats[i].name) == to ||
(g_task_stats[i].name != OS_NULL &&
strcmp(os_task_get_name(to), g_task_stats[i].name) == 0))
{
g_task_stats[i].is_active = OS_TRUE;
g_task_stats[i].last_switch_time = now;
return;
}
}
/* 新任务,添加到统计列表 */
if (g_stat_count < MAX_TASK_STAT)
{
g_task_stats[g_stat_count].name = os_task_get_name(to);
g_task_stats[g_stat_count].total_ticks = 0;
g_task_stats[g_stat_count].last_switch_time = now;
g_task_stats[g_stat_count].is_active = OS_TRUE;
g_stat_count++;
}
}5.3 任务执行时间监控
#include <os_task.h>
/* 监控任务切换间隔,检测是否存在长时间运行的任务 */
void execution_monitor_hook(os_task_id from, os_task_id to)
{
static os_tick_t last_switch = 0;
os_tick_t now = os_tick_get_value();
os_tick_t elapsed;
if (last_switch != 0)
{
elapsed = now - last_switch;
/* 如果上一次切换间隔超过阈值,发出警告 */
if (elapsed > 100) /* 100 tick = 1s(假设 100Hz) */
{
os_kprintf("WARNING: %s ran for %lu ticks!\r\n",
os_task_get_name(from), elapsed);
}
}
last_switch = now;
}5.4 注销钩子
/* 停止监控 */
void stop_monitoring(void)
{
os_task_switch_hook_delete(cpu_stat_hook);
}六、应用场景
| 场景 | 说明 |
|---|---|
| CPU 使用率监控 | 统计每个任务占用的 CPU 时间,用于性能分析和优化 |
| 任务切换追踪 | 记录任务切换序列,用于调试任务调度相关问题 |
| 执行时间监控 | 检测是否存在长时间占用 CPU 的任务,防止饥饿 |
| 性能基准测试 | 测量任务切换频率和开销 |
| 系统行为分析 | 分析任务间的协作模式,验证系统设计是否合理 |
| 堆栈溢出检测 | 内核已内置 OS_USING_OVERFLOW_CHECK,在钩子调用前检查栈溢出 |
七、注意事项
钩子函数执行时间要短:钩子函数在每次任务切换时都被调用,执行时间过长会显著增加上下文切换开销,影响系统实时性。
不要在钩子中调用阻塞 API:钩子函数运行在内核临界区中,不应调用任何可能导致阻塞或切换的 API(如
os_task_tsleep()、os_sem_wait()等)。只支持一个钩子:当前版本只支持注册一个任务切换钩子函数。如果需要多个功能,可以在钩子函数内部实现分发逻辑。
性能开销:启用任务钩子会增加每次任务切换的开销。在性能敏感的系统中,应谨慎使用。
SMP 注意事项:在 SMP 模式下,钩子函数可能被多个 CPU 核心同时调用,需要确保钩子函数内部的数据访问是线程安全的。
📝 本节小结
任务钩子是 OneOS 提供的一种扩展机制,允许在任务切换时执行自定义回调。通过 os_task_switch_hook_add() 和 os_task_switch_hook_delete() 管理钩子函数。钩子函数在单核模式下接收 from 和 to 两个参数,在 SMP 模式下额外接收 cpu 参数。任务钩子常用于 CPU 使用率监控、任务切换追踪和执行时间分析等场景。使用时需注意钩子函数应尽量简短,避免在其中调用阻塞 API。