FreeRTOS学习笔记(九):任务的删除和释放

一、任务的删除和释放

场景描述:
在freeRTOS中
删除任务时,只会自动释放内核本身分配给任务的内存。应用程序(而不是内核)分配给任务的内存或任何其他资源必须是删除任务时由应用程序显式释放。怎么理解这句话?

  • 好像在某个任务中我申请了一大块内存,但是没释放就把任务删除,这块内存在任务删除之后不会自动释放的,所以我们应该在删除任务之前就把任务中的这些资源释放掉,然后再进行删除,否则很容易造成内存泄漏。

解决措施

在FreeRTOS中,任务删除时内核仅负责回收其自身为任务分配的核心资源(如任务控制块TCB、任务栈等),而应用程序在任务中手动申请的资源(如动态内存、文件句柄、硬件资源等)不会被自动释放。这会导致内存泄漏或资源残留,因此需要通过主动释放资源的方式解决。以下是具体的解决思路和方法:

一、核心原则:谁申请,谁释放

任务中手动分配的资源(如malloc的内存、打开的文件、创建的互斥锁等),必须在任务被删除前显式释放,避免依赖内核自动处理。

二、具体解决方法

1. 在任务删除前主动释放资源(推荐)

在调用vTaskDelete()删除任务前,先在任务内部或外部释放该任务申请的所有资源。

示例:任务内部释放资源后自删除

void vMyTask(void *pvParameters) {
    // 任务中申请资源:动态内存、文件句柄等
    int *pBuffer = (int*)malloc(1024);  // 应用程序分配的内存
    FILE *f = fopen("data.txt", "r");   // 应用程序打开的文件

    while (1) {
        // 任务业务逻辑
        if (/* 需要删除任务的条件 */) {
            // 1. 先释放所有应用程序资源
            free(pBuffer);  // 释放动态内存
            fclose(f);      // 关闭文件
            pBuffer = NULL; // 避免野指针

            // 2. 再删除任务(内核释放自身资源)
            vTaskDelete(NULL);  // 参数为NULL表示删除当前任务
        }
    }
}
2. 由其他任务释放资源(适用于外部删除场景)

如果任务是被其他任务通过vTaskDelete(xTaskHandle)删除的,需确保:

  • 被删除任务的资源句柄(如内存指针、文件描述符)能被其他任务访问(如通过全局变量、消息队列传递)。
  • 其他任务在删除目标任务后,显式释放这些资源。

示例:外部任务释放资源

// 全局变量存储被管理的资源句柄
int *g_pTaskBuffer = NULL;
TaskHandle_t xMyTaskHandle = NULL;

// 被删除的任务
void vMyTask(void *pvParameters) {
    g_pTaskBuffer = (int*)malloc(1024);  // 申请资源,句柄存入全局变量
    while (1) {
        // 任务逻辑
    }
}

// 负责删除任务并释放资源的任务
void vManagerTask(void *pvParameters) {
    // 创建目标任务
    xTaskCreate(vMyTask, "MyTask", 1024, NULL, 1, &xMyTaskHandle);

    while (1) {
        if (/* 需要删除vMyTask的条件 */) {
            // 1. 先删除任务(内核释放自身资源)
            vTaskDelete(xMyTaskHandle);
            xMyTaskHandle = NULL;

            // 2. 再释放应用程序资源(通过全局句柄)
            if (g_pTaskBuffer != NULL) {
                free(g_pTaskBuffer);
                g_pTaskBuffer = NULL;
            }
        }
    }
}
3. 使用任务清理钩子函数(Hook)

FreeRTOS提供了任务删除钩子函数vApplicationTaskDeleteHook()(需在配置文件中开启configUSE_TASK_DELETE_HOOK),可在任务被删除时自动执行资源释放逻辑。
适用场景:资源句柄可通过任务控制块(TCB)的pvParameters传递,或通过任务句柄关联。

步骤:

  1. FreeRTOSConfig.h中开启配置:
    #define configUSE_TASK_DELETE_HOOK 1  // 启用任务删除钩子
    
  2. 实现钩子函数,释放资源:
    // 任务参数结构体:包含任务申请的资源句柄
    typedef struct {
        int *pBuffer;
        FILE *f;
    } TaskResources_t;
    
    // 任务实现:申请资源并存入参数结构体
    void vMyTask(void *pvParameters) {
        TaskResources_t *pRes = (TaskResources_t*)pvParameters;
        pRes->pBuffer = (int*)malloc(1024);
        pRes->f = fopen("data.txt", "r");
    
        while (1) {
            // 任务逻辑
            if (/* 删除条件 */) {
                vTaskDelete(NULL);  // 触发钩子函数
            }
        }
    }
    
    // 任务删除钩子函数:自动释放资源
    void vApplicationTaskDeleteHook(TaskHandle_t xTaskToDelete) {
        // 通过任务参数获取资源句柄(假设参数是TaskResources_t类型)
        TaskResources_t *pRes = (TaskResources_t*)pvTaskGetThreadLocalStoragePointer(xTaskToDelete, 0);
        if (pRes != NULL) {
            free(pRes->pBuffer);  // 释放内存
            fclose(pRes->f);      // 关闭文件
            free(pRes);           // 释放参数结构体本身
        }
    }
    
    // 创建任务时传递资源参数
    void vCreateTask() {
        TaskResources_t *pRes = (TaskResources_t*)malloc(sizeof(TaskResources_t));
        xTaskCreate(vMyTask, "MyTask", 1024, pRes, 1, NULL);
        // 将资源句柄存入任务的线程本地存储(方便钩子函数访问)
        vTaskSetThreadLocalStoragePointer(xCreatedTask, 0, pRes);
    }
    
4. 避免动态资源分配(从根源减少泄漏风险)

在实时系统中,尽量使用静态内存分配(如全局数组、static变量)替代malloc,或通过内存池(Memory Pool)管理资源,减少动态分配带来的泄漏风险。
例如,用静态数组替代动态内存:

void vMyTask(void *pvParameters) {
    int aBuffer[1024];  // 静态分配,任务删除后自动回收(栈上资源)
    while (1) {
        // 任务逻辑,无需手动释放aBuffer
        if (/* 删除条件 */) {
            vTaskDelete(NULL);  // 栈内存随任务销毁自动释放
        }
    }
}

三、总结

解决任务删除导致的资源泄漏,核心是显式释放应用程序资源,具体方式可根据场景选择:

  • 简单场景:任务内部释放资源后自删除。
  • 外部删除场景:通过全局句柄由其他任务释放。
  • 复杂场景:使用任务删除钩子函数统一管理资源释放。
  • 最佳实践:减少动态资源分配,优先使用静态内存或内存池。

通过以上方法,可确保任务删除时所有资源被正确回收,避免内存泄漏和系统不稳定。

二、钩子函数

在编程中,钩子函数(Hook Function) 是一种特殊的回调函数,由开发者定义,在特定事件发生时(如系统运行到某个阶段、触发特定操作)被框架、库或操作系统自动调用。它的作用是“挂钩”到系统的固定执行流程中,允许开发者在不修改原有代码的情况下,插入自定义逻辑,扩展或改变系统的行为。

核心特点:

  1. 被动触发:钩子函数不会被开发者主动调用,而是由框架/系统在特定事件发生时自动执行。
  2. 扩展能力:无需修改框架源码,通过定义钩子函数即可扩展功能(遵循“开闭原则”)。
  3. 特定场景:钩子函数的调用时机和参数由框架预先定义,开发者只需按规则实现即可。

举例说明:

1. FreeRTOS 中的钩子函数(结合实时系统场景)

FreeRTOS 提供了多种钩子函数,允许开发者在系统关键节点插入自定义逻辑,例如:

  • 任务切换钩子(vApplicationTickHook:每个系统时钟节拍(Tick)触发一次,可用于统计任务运行时间。
  • 任务删除钩子(vApplicationTaskDeleteHook:任务被删除时触发,可用于释放任务相关资源(如前面提到的内存释放)。
  • 空闲任务钩子(vApplicationIdleHook:系统进入空闲状态时触发,可用于低功耗处理。

示例:FreeRTOS 空闲任务钩子

// 配置 FreeRTOS 启用空闲钩子(在 FreeRTOSConfig.h 中)
#define configUSE_IDLE_HOOK 1

// 实现钩子函数:系统空闲时执行
void vApplicationIdleHook(void) {
    // 自定义逻辑:例如进入低功耗模式
    printf("系统空闲,进入低功耗...\n");
    // 注意:空闲钩子不能阻塞(需快速执行)
}

当 FreeRTOS 没有任务需要运行时(进入空闲状态),会自动调用 vApplicationIdleHook

2. C 语言库中的钩子(如内存分配钩子)

一些 C 库允许通过钩子函数监控内存分配/释放,例如:

#include <stdlib.h>
#include <stdio.h>

// 定义内存分配钩子:malloc 被调用时触发
void *my_malloc_hook(size_t size, const void *caller) {
    printf("分配 %zu 字节内存\n", size);
    // 调用原始 malloc(避免递归)
    return malloc(size);
}

int main() {
    // 设置钩子:将 malloc 的调用转发到 my_malloc_hook
    malloc_hook = my_malloc_hook;

    // 当调用 malloc 时,钩子函数会自动执行
    int *p = (int*)malloc(100);
    free(p);
    return 0;
}
3. 图形界面中的钩子(如按钮点击事件)

在 GUI 框架中,按钮点击、窗口关闭等事件本质上也是钩子函数:

// 伪代码:按钮点击钩子
void on_button_click(Button *btn) {
    printf("按钮被点击了!\n");
}

int main() {
    Button *btn = create_button("点击我");
    // 注册钩子:当按钮被点击时,自动调用 on_button_click
    set_button_click_hook(btn, on_button_click);
    run_gui();  // 框架运行时,点击按钮会触发钩子
    return 0;
}

钩子函数的作用:

  • 扩展系统功能:在不修改框架源码的情况下,添加自定义逻辑(如日志、统计、监控)。
  • 资源管理:在特定事件(如任务删除、内存释放)时自动处理资源(避免泄漏)。
  • 事件响应:对系统或用户触发的事件(如时钟、点击)做出反应。

简单说,钩子函数就像“预埋的接口”,框架提供“触发时机”,开发者提供“执行逻辑”,两者配合实现灵活的功能扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值