3.5.7 k_recycle_task_init 函数详解
3.5.7 k_recycle_task_init 函数详解
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐⭐☆☆(中级)
前置知识:
- 3.5.5 k_sched_init 函数详解
- 3.5.6 k_timer_module_init 函数详解
- 任务创建与调度基础知识
🎯 学习目标
- 理解回收任务的设计目的
- 掌握
k_recycle_task_init的初始化流程 - 理解回收任务入口
_k_recycle_task_entry的工作循环 - 了解回收任务被唤醒的三种触发场景
- 理解为什么回收任务在
os_kernel_init中创建
一、函数概述
1.1 函数定位
k_recycle_task_init 创建 recycle 回收任务,负责在后台异步回收已退出/已删除任务的资源(栈内存、任务控制块、用户自定义数据)。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 函数名 | k_recycle_task_init |
| 源文件 | kernel/source/os_task.c(第 261-285 行) |
| 调用位置 | os_kernel_init() 第 276 行 |
| 回收任务名 | "recycle" |
| 回收任务栈大小 | 默认 512 字节(OS_RECYCLE_TASK_STACK_SIZE) |
| 回收任务优先级 | 0(最低优先级) |
| 入口函数 | _k_recycle_task_entry |
1.3 为什么需要回收任务?
当一个任务退出或被销毁时,如果它占用了动态分配的资源(栈内存、任务控制块),或者注册了 cleanup 回调,这些资源不能直接在任务自己的上下文中释放——因为任务正在"自杀",释放自己的栈等于站在自己脚下砍树。
回收任务提供了一个安全的异步清理机制:
- 待回收资源挂入回收链表
- 唤醒回收任务
- 回收任务在自己的栈空间中安全释放资源
1.4 在 os_kernel_init 中的位置
void os_kernel_init(void)
{
_k_run_init_call(OS_INIT_LEVEL_PRE_KERNEL_1);
_k_show_sys_info();
k_tickq_init(); // 3.5.4
k_sched_init(); // 3.5.5
k_timer_module_init(); // 3.5.6
k_recycle_task_init(); // ← 这里(3.5.7)
k_idle_task_init(); // 下一步
_k_sys_task_init(); // 最后
}回收任务必须在调度器(k_sched_init)之后创建,因为它会被加入就绪队列;在空闲任务(k_idle_task_init)之前创建,因为空闲任务是最后保底的任务。
二、数据结构
2.1 静态变量
来源: kernel/source/os_task.c 第 41-48 行
/* The stack and control block of recycle-task */
static OS_TASK_STACK_DEFINE(gs_os_recycle_task_stack, OS_RECYCLE_TASK_STACK_SIZE);
static os_task_t gs_os_recycle_task;
static os_list_node_t gs_os_task_resource_list_head = OS_LIST_INIT(gs_os_task_resource_list_head);
static OS_DEFINE_SPINLOCK(gs_os_task_resource_list_lock);
static os_list_node_t gs_os_task_recycle_list_head = OS_LIST_INIT(gs_os_task_recycle_list_head);| 变量 | 作用 |
|---|---|
gs_os_recycle_task_stack | 回收任务的栈空间,默认 512 字节 |
gs_os_recycle_task | 回收任务的控制块(静态分配) |
gs_os_task_resource_list_head | 通用资源回收链表(备用) |
gs_os_task_resource_list_lock | 资源回收链表的自旋锁 |
gs_os_task_recycle_list_head | 任务资源回收链表(实际使用) |
2.2 任务控制块中的相关字段
来源: kernel/include/os_dummy.h 第 36-97 行
struct dummy_task
{
// ... 栈指针、状态等 ...
uint8_t object_alloc_type_dummy; // 分配类型:静态/动态(栈)/动态(对象)
os_list_node_t resource_node_dummy; // 回收链表节点 ← 关键
void (*cleanup_dummy)(void *user_data); // 用户清理回调
void *user_data_dummy; // 用户私有数据
char name_dummy[OS_NAME_MAX + 1]; // 任务名
};分配类型标志位
object_alloc_type 是位掩码,可以同时标记多种分配方式:
| 宏 | 值 | 含义 |
|---|---|---|
OS_KOBJ_ALLOC_TYPE_STATIC | 0 | 全部静态分配 |
OS_KDATA_ALLOC_TYPE_DYNAMIC | bit 0 | 栈内存动态分配 |
OS_KOBJ_ALLOC_TYPE_DYNAMIC | bit 1 | 任务控制块动态分配 |
回收任务根据这两个标志位决定是否需要释放栈内存和任务控制块。
三、函数详解
3.1 k_recycle_task_init
来源: kernel/source/os_task.c 第 261-285 行
void k_recycle_task_init(void)
{
os_err_t ret;
os_task_id tid;
tid = os_task_create((os_task_dummy_t *)&gs_os_recycle_task,
OS_TASK_STACK_BEGIN_ADDR(gs_os_recycle_task_stack),
OS_RECYCLE_TASK_STACK_SIZE,
OS_RECYCLE_TASK_NAME,
_k_recycle_task_entry,
OS_NULL,
0U);
OS_ASSERT_EX((tid != OS_NULL), "Why initialize recycle task failed?");
ret = os_task_startup(tid);
#ifdef OS_USING_ASSERT
OS_ASSERT_EX((OS_SUCCESS == ret), "Why startup recycle task failed?");
#else
OS_UNREFERENCE(ret);
#endif
return;
}分步解析:
| 步骤 | 代码 | 作用 |
|---|---|---|
| 1 | os_task_create(...) | 创建回收任务,绑定静态栈和控制块 |
| 2 | OS_ASSERT_EX(...) | 断言任务创建成功(调试模式) |
| 3 | os_task_startup(tid) | 启动任务,将其加入就绪队列 |
| 4 | OS_ASSERT_EX(...) | 断言任务启动成功(调试模式) |
参数详解:
| 参数 | 值 | 含义 |
|---|---|---|
| 任务控制块 | &gs_os_recycle_task | 使用静态分配的 os_task_t |
| 栈地址 | gs_os_recycle_task_stack | 静态分配的 512 字节栈 |
| 栈大小 | OS_RECYCLE_TASK_STACK_SIZE | 默认 512 字节 |
| 任务名 | "recycle" | OS_RECYCLE_TASK_NAME 宏 |
| 入口函数 | _k_recycle_task_entry | 回收任务主循环 |
| 入口参数 | OS_NULL | 无参数 |
| 优先级 | 0U | 最低优先级 |
注意
回收任务优先级为 0(最低),这意味着它只在没有其他就绪任务时才运行。资源回收不紧急,低优先级确保它不抢占正常业务任务。
3.2 _k_recycle_task_entry(回收任务主循环)
来源: kernel/source/os_task.c 第 193-258 行
static void _k_recycle_task_entry(void *arg)
{
os_task_t *iter_task;
os_task_t *current_task;
uint8_t object_alloc_type;
OS_KERNEL_INIT();
OS_UNREFERENCE(arg);
while (1)
{
OS_KERNEL_ENTER();
iter_task = OS_NULL;
while (1)
{
if (os_list_empty(&gs_os_task_recycle_list_head))
{
break;
}
iter_task = os_list_first_entry(&gs_os_task_recycle_list_head, os_task_t, resource_node);
os_list_del(&iter_task->resource_node);
OS_KERNEL_EXIT();
OS_KERN_LOG(KERN_INFO, TASK_TAG, "Recycle task(%s)", iter_task->name);
/*
* iter_task memory maybe in user_data, in this case, iter_task memory will be freed at cleanup function.
* So, object_alloc_type of iter_task is stored at the temporary variable, avoiding illegal memory access.
*/
object_alloc_type = iter_task->object_alloc_type;
if (OS_NULL != iter_task->cleanup)
{
iter_task->cleanup(iter_task->user_data);
}
#ifdef OS_USING_HEAP
if (OS_KDATA_ALLOC_TYPE_DYNAMIC == (object_alloc_type & OS_KDATA_ALLOC_TYPE_DYNAMIC))
{
OS_KERNEL_FREE(iter_task->stack_begin);
}
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (object_alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
OS_KERNEL_FREE(iter_task);
}
#endif
OS_KERNEL_ENTER();
}
/* Suspend myself */
current_task = _k_task_self();
k_readyq_remove(current_task);
current_task->state &= ~OS_TASK_STATE_READY;
current_task->state |= OS_TASK_STATE_SUSPEND;
OS_KERNEL_EXIT_SCHED();
}
}3.2.1 工作流程图
_k_recycle_task_entry 启动
↓
OS_KERNEL_INIT() ← 只执行一次,设置任务为 ready 状态
↓
┌── while(1) ──────────────────────────────────────────┐
│ │
│ OS_KERNEL_ENTER() ← 关调度锁,进入临界区 │
│ ↓ │
│ ┌── 内层 while(1) ──────────────┐ │
│ │ │ │
│ │ 回收链表为空? ──是──→ break │ │
│ │ │否 │ │
│ │ 取出链表第一个任务 │ │
│ │ 从链表删除 │ │
│ │ ↓ │ │
│ │ OS_KERNEL_EXIT() ← 开调度锁 │ │
│ │ ↓ │ │
│ │ 保存 object_alloc_type │ │
│ │ (防止 cleanup 后内存非法访问)│ │
│ │ ↓ │ │
│ │ cleanup != NULL? │ │
│ │ ├─ 是 → cleanup(user_data) │ │
│ │ └─ 否 → 跳过 │ │
│ │ ↓ │ │
│ │ 栈是动态分配的? │ │
│ │ └─ 是 → OS_KERNEL_FREE │ │
│ │ ↓ │ │
│ │ 任务控制块是动态分配的? │ │
│ │ └─ 是 → OS_KERNEL_FREE │ │
│ │ ↓ │ │
│ │ OS_KERNEL_ENTER() ← 关调度锁 │ │
│ │ ↓ │ │
│ └── 回到内层循环开头 ────────────┘ │
│ │
│ /* 链表已空,挂起自己 */ │
│ k_readyq_remove(current_task) │
│ state = SUSPEND │
│ OS_KERNEL_EXIT_SCHED() ← 开调度锁并触发调度 │
│ ↓ │
│ (等待被 _k_wakeup_recycle_task 唤醒) │
│ ↓ │
└── 被唤醒后回到 while(1) 开头 ─────────────────────────┘3.2.2 关键设计点
1. 为什么先保存 object_alloc_type?
object_alloc_type = iter_task->object_alloc_type;代码注释已经说明:cleanup 回调可能会释放 iter_task 指向的内存(当任务控制块嵌在 user_data 中时)。如果在 cleanup 之后再访问 iter_task->object_alloc_type,就是非法内存访问。因此必须提前保存到局部变量。
2. 资源回收的三个步骤
| 顺序 | 操作 | 条件 |
|---|---|---|
| 1 | 调用 cleanup(user_data) | cleanup != NULL |
| 2 | 释放栈内存 | object_alloc_type & OS_KDATA_ALLOC_TYPE_DYNAMIC |
| 3 | 释放任务控制块 | object_alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC |
顺序很重要:先 cleanup(用户可能需要用栈上的数据),再释放栈,最后释放控制块。
3. 为什么挂起自己而不是删除?
回收任务不能删除自己——那样就没人回收资源了。它把自己挂起(SUSPEND),等待下次有资源需要回收时被 _k_wakeup_recycle_task 唤醒。
3.3 _k_wakeup_recycle_task(唤醒回收任务)
来源: kernel/source/os_task.c 第 287-297 行
static void _k_wakeup_recycle_task(void)
{
if (OS_TASK_STATE_SUSPEND == gs_os_recycle_task.state)
{
gs_os_recycle_task.state &= ~OS_TASK_STATE_SUSPEND;
gs_os_recycle_task.state |= OS_TASK_STATE_READY;
k_readyq_put(&gs_os_recycle_task);
}
return;
}逻辑:
- 仅当回收任务处于
SUSPEND状态时才唤醒 - 清除
SUSPEND标志,设置READY标志 - 加入就绪队列
为什么不每次都唤醒?
如果回收任务正在运行(处理回收链表),state 不是 SUSPEND,此时不需要再次唤醒。回收任务的内层循环会持续处理直到链表为空。
四、触发场景
回收任务在以下三种场景被触发:
4.1 任务主动退出(k_task_exit)
来源: kernel/source/os_task.c 第 318-325 行
if ((OS_KDATA_ALLOC_TYPE_DYNAMIC == (current_task->object_alloc_type & OS_KDATA_ALLOC_TYPE_DYNAMIC)) ||
(OS_KOBJ_ALLOC_TYPE_DYNAMIC == (current_task->object_alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC)) ||
(OS_NULL != current_task->cleanup))
{
os_list_add_tail(&gs_os_task_recycle_list_head, ¤t_task->resource_node);
_k_wakeup_recycle_task();
}任务调用 k_task_exit() 退出自己时,将自身资源挂入回收链表。
4.2 任务被销毁(k_task_destroy)
来源: kernel/source/os_task.c 第 750-753 行
if (task == current_task)
{
os_list_add_tail(&gs_os_task_recycle_list_head, &task->resource_node);
_k_wakeup_recycle_task();当销毁的是当前正在运行的任务时,同样挂入回收链表。
4.3 任务反初始化(_k_task_deinit)
来源: kernel/source/os_task.c 第 480-484 行
if (((OS_ALLOC_TYPE_STATIC != task->object_alloc_type) || (OS_NULL != task->cleanup)) != 0)
{
os_list_add_tail(&gs_os_task_recycle_list_head, &task->resource_node);
_k_wakeup_recycle_task();
}当任务被反初始化且存在动态资源或 cleanup 回调时触发。
五、生命周期示意图
时间线 ─────────────────────────────────────────────────────→
os_kernel_init()
│
├─ k_recycle_task_init() ← 创建回收任务,加入就绪队列
│ │
│ └─ _k_recycle_task_entry 开始运行
│ │
│ ├─ 回收链表为空 → 挂起自己(SUSPEND)
│ │
│ │ ... 系统运行中 ...
│ │
│ ├─ 某任务退出 → _k_wakeup_recycle_task()
│ │ │
│ │ └─ 回收任务被唤醒(READY)
│ │ │
│ │ ├─ 处理回收链表中的所有任务
│ │ │ ├─ cleanup()
│ │ │ ├─ 释放栈内存
│ │ │ └─ 释放任务控制块
│ │ │
│ │ └─ 链表为空 → 再次挂起
│ │
│ └─ ... 循环往复 ...
│
└─ 系统永远运行,回收任务永不退出💡 本节总结
重点回顾
- 设计目的:安全地异步回收已退出/已销毁任务的资源
- 初始化流程:
os_task_create→os_task_startup,创建优先级为 0 的 "recycle" 任务 - 主循环逻辑:从
gs_os_task_recycle_list_head逐个取任务 →cleanup→ 释放栈 → 释放控制块 → 链表空后挂起 - 三段式回收:cleanup 回调 → 释放栈内存 → 释放控制块,顺序不可颠倒
- 三种触发场景:
k_task_exit、k_task_destroy(销毁自己)、_k_task_deinit
关键符号说明
| 符号 | 来源 | 说明 |
|---|---|---|
gs_os_recycle_task | os_task.c | 回收任务控制块(静态分配) |
gs_os_task_recycle_list_head | os_task.c | 待回收任务链表头 |
OS_RECYCLE_TASK_NAME | os_task.h | 回收任务名 "recycle" |
OS_RECYCLE_TASK_STACK_SIZE | Kconfig | 回收任务栈大小,默认 512 |
_k_wakeup_recycle_task | os_task.c | 唤醒回收任务 |
在 os_kernel_init 中的位置
os_kernel_init()
├─ _k_run_init_call(PRE_KERNEL_1)
├─ _k_show_sys_info()
├─ k_tickq_init() ← 3.5.4
├─ k_sched_init() ← 3.5.5
├─ k_timer_module_init() ← 3.5.6
├─ k_recycle_task_init() ← 这里(3.5.7)
├─ k_idle_task_init() ← 下一步
└─ _k_sys_task_init()📚 扩展阅读
- k_sched_init 函数详解 - 就绪队列与调度
- 3.5.8 k_idle_task_init 函数详解 - 空闲任务初始化
下一步
接下来请学习:
- 3.5.8 k_idle_task_init 函数详解 - 空闲任务初始化