05-任务创建删除与挂起恢复.md 10 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第5~6章 + 配套代码01/02/03/06" author: "尚硅谷研究院" date: 2026-07-12 url: ""

created: 2026-07-12

任务创建、删除、挂起与恢复

用生活理解:可以把 RTOS 想象成一个"公司",每个任务就是一个"员工"。创建任务就是招人,删除就是开除,挂起就是让员工休假(随时能叫回来),调度器就是 CEO —— 决定谁在什么时候干活。

API 速查

函数 作用 通俗理解
xTaskCreate() 动态创建任务 公司招人,HR 帮忙找工位(内存)
xTaskCreateStatic() 静态创建任务 公司招人,但工位你自己提前准备好
vTaskDelete() 删除任务 开除员工,工位回收
vTaskSuspend() 挂起任务 让员工休假,保留工位
vTaskResume() 恢复任务 休假员工叫回来上班
vTaskSuspendAll() 暂停调度器 CEO 说"都别动,听我讲两句"
xTaskResumeAll() 恢复调度器 CEO 讲完了,大家继续工作

示例 1:动态创建和删除任务(项目 01)

情景

有 4 个任务:

  • start_task(主管):负责招人(创建 task1/2/3),招完就辞职(删除自己)
  • task1(员工1):每 500ms 闪一次 LED1
  • task2(员工2):每 500ms 闪一次 LED2
  • task3(员工3):按 KEY1 就开除 task1

核心代码

#include "FreeRTOS.h"
#include "task.h"

/* ========== 任务配置:相当于每个员工的工牌编号 ========== */
#define START_TASK_STACK 128      // 主管的办公桌大小(128个Word=512字节)
#define START_TASK_PRIORITY 1     // 优先级1(数字越小优先级越低)
TaskHandle_t start_task_handle;   // 主管的工牌(句柄)

#define TASK1_STACK 128
#define TASK1_PRIORITY 2          // 比主管高一点
TaskHandle_t task1_handle;

#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;

#define TASK3_STACK 128
#define TASK3_PRIORITY 4          // 最高优先级
TaskHandle_t task3_handle;

/* ========== 启动 FreeRTOS ========== */
void freertos_start(void)
{
    // 第一步:创建主管任务(start_task)
    xTaskCreate(
        (TaskFunction_t)start_task,          // 任务函数 = 员工要干的活
        (char *)"start_task",                // 任务名字(调试用)
        (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 栈大小(办公桌尺寸)
        (void *)NULL,                        // 传给任务的参数
        (UBaseType_t)START_TASK_PRIORITY,    // 优先级
        (TaskHandle_t *)&start_task_handle); // 工牌(句柄)

    // 第二步:启动调度器 = CEO 开始上班
    vTaskStartScheduler();
    // 注意:vTaskStartScheduler 不会返回!
    // CEO 一旦开始工作,就永远不停止
}

/* ========== 主管任务 ========== */
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();  // 进入临界区 = "关门开大会,谁都不许打扰"

    // 主管负责创建三个员工
    xTaskCreate((TaskFunction_t)task1, (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK, NULL,
                (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2, (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK, NULL,
                (UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handle);
    xTaskCreate((TaskFunction_t)task3, (char *)"task3",
                (configSTACK_DEPTH_TYPE)TASK3_STACK, NULL,
                (UBaseType_t)TASK3_PRIORITY, (TaskHandle_t *)&task3_handle);

    // 主管的使命完成,辞职(删除自己)
    vTaskDelete(NULL);  // NULL = 删除调用者自己

    taskEXIT_CRITICAL();  // 退出临界区
}

/* ========== task1:每500ms闪一次LED1 ========== */
void task1(void *pvParameters)
{
    while (1)  // 所有任务基本都是死循环(不停干活)
    {
        LED_Toggle(LED1_Pin);  // 翻转LED1
        vTaskDelay(500);       // 延时500ms = "睡500ms,让出CPU"
    }
}

/* ========== task2:每500ms闪一次LED2 ========== */
void task2(void *pvParameters)
{
    while (1)
    {
        LED_Toggle(LED2_Pin);
        vTaskDelay(500);
    }
}

/* ========== task3:按KEY1删除task1 ========== */
void task3(void *pvParameters)
{
    uint8_t key = 0;
    while (1)
    {
        key = Key_Detect();       // 读按键
        if (key == KEY1_PRESS)
        {
            if (task1_handle != NULL)  // 防止重复删除
            {
                vTaskDelete(task1_handle);  // 删除task1
                task1_handle = NULL;        // 标记已删除
            }
        }
        vTaskDelay(500);
    }
}

关键点理解

vTaskDelay vs HAL_Delay

  • vTaskDelay(500):告诉调度器"我睡500ms,这期间让别人干活"——主动让出CPU
  • HAL_Delay(500):死等500ms,期间CPU空转——浪费CPU
  • 在 RTOS 中永远用 vTaskDelay 而不是 HAL_Delay

vTaskDelete(NULL):传入 NULL 表示删除自己。任务执行完后如果不删除自己,调度器会一直给它分配时间片。


示例 2:静态创建任务(项目 02)

动态 vs 静态的区别

方式 通俗理解 内存谁管
动态创建 你只管招人,工位(内存)让操作系统去准备 FreeRTOS 从堆里自动分配
静态创建 你招人,还得自己给他准备好工位 用户提前声明数组和结构体

静态创建的特殊要求

// ======== 必须提前准备的"硬件资源" ========
StackType_t task1_stack[TASK1_STACK];  // 手动准备办公桌(栈空间)
StaticTask_t task1_tcb;                // 手动准备工牌(TCB结构体)

// ======== 空闲任务也需要手动准备! ========
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
StaticTask_t idle_task_tcb;

// FreeRTOS 会通过这个接口找你要空闲任务的资源
void vApplicationGetIdleTaskMemory(
    StaticTask_t **ppxIdleTaskTCBBuffer,
    StackType_t **ppxIdleTaskStackBuffer,
    uint32_t *pulIdleTaskStackSize)
{
    *ppxIdleTaskTCBBuffer = &idle_task_tcb;       // "空闲任务的工牌在这儿"
    *ppxIdleTaskStackBuffer = idle_task_stack;     // "空闲任务的办公桌在这儿"
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

// ======== 静态创建任务 ========
void freertos_start(void)
{
    start_task_handle = xTaskCreateStatic(
        (TaskFunction_t)start_task,
        (char *)"start_task",
        (uint32_t)START_TASK_STACK,
        (void *)NULL,
        (UBaseType_t)START_TASK_PRIORITY,
        (StackType_t *)start_task_stack,     // ⚠️ 把准备好的栈传进去
        (StaticTask_t *)&start_task_tcb);    // ⚠️ 把准备好的TCB传进去
    vTaskStartScheduler();
}

为什么要静态创建?

  • 资源受限的系统:内存很小,不允许动态分配
  • 安全关键系统:不允许 malloc 失败的情况
  • 确定性的需要:静态分配在编译时就确定了内存布局

示例 3:任务挂起与恢复(项目 03)

情景

4 个按键分别对应不同操作:

  • KEY1 → 挂起 task1(让 task1 休假)
  • KEY2 → 恢复 task1(叫 task1 回来上班)
  • KEY3 → 挂起调度器(CEO 说全部暂停!)
  • KEY4 → 恢复调度器(CEO 说继续干活!)

核心代码

void task3(void *pvParameters)
{
    uint8_t key = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            vTaskSuspend(task1_handle);  // 让 task1 休假
        }
        else if (key == KEY2_PRESS)
        {
            vTaskResume(task1_handle);   // 叫 task1 回来
        }
        else if (key == KEY3_PRESS)
        {
            vTaskSuspendAll();           // CEO:全部暂停!
        }
        else if (key == KEY4_PRESS)
        {
            xTaskResumeAll();            // CEO:继续工作!
        }
        vTaskDelay(500);
    }
}

挂起 vs 阻塞 vs 删除

状态 怎么进去 怎么出来 还占内存吗
挂起态 vTaskSuspend() vTaskResume() 占着,随时恢复
阻塞态 vTaskDelay() 或等队列/信号量 时间到或资源可用 占着
删除态 vTaskDelete() 回不来了 回收了

打个比方

  • 挂起 = 员工长期休假(工位保留,被动——领导让休的)
  • 阻塞 = 员工去茶水间接水(一会儿就回来,主动行为)
  • 删除 = 员工被开除了(工位回收,回不来了)

vTaskSuspendAll 的特殊性

vTaskSuspendAll() 只是暂停任务切换,但中断不受影响。这就像 CEO 说"都别动",但快递员(中断)来了照样可以送快递。而 taskENTER_CRITICAL() 是直接把公司大门锁了,连快递都进不来。


示例 4:任务状态查询(项目 06)

关键 API 理解

// 获取任务优先级 = 查这个员工的职级
UBaseType_t prio = uxTaskPriorityGet(task1_handle);

// 设置任务优先级 = 给员工升职
vTaskPrioritySet(task1_handle, 4);

// 获取系统中任务总数 = 统计公司有多少员工
UBaseType_t count = uxTaskGetNumberOfTasks();

// 获取所有任务状态 = 打印花名册
TaskStatus_t status[10];
uxTaskGetSystemState(status, count, NULL);
for (i = 0; i < count; i++) {
    printf("任务名:%s 编号:%d 状态:%d 优先级:%d\r\n",
           status[i].pcTaskName,
           status[i].xTaskNumber,
           status[i].eCurrentState,    // 0=运行 1=就绪 2=阻塞 3=挂起
           status[i].uxCurrentPriority);
}

// 获取栈历史剩余最小值 = 检查办公桌还剩多少空间
// 用来判断任务栈是否设得太大(浪费)或太小(会溢出)
UBaseType_t remain = uxTaskGetStackHighWaterMark(task2_handle);
// 如果 remain ≈ 0,说明栈快爆了,得加大 STACK 值

// 以表格形式打印所有任务状态
char info[500];
vTaskList(info);
printf("%s\r\n", info);
// 输出示例:
// Task Name   State  Priority  Stack  Num
// task1       X      2         45     1
// task2       B      3         120    2
// IDLE        R      0         80     3
// 状态: X=运行  B=阻塞  R=就绪  S=挂起  D=删除

配套代码

  • code/01_动态创建任务和删除/
  • code/02_静态创建任务和删除/
  • code/03_任务挂起与恢复/
  • code/06_任务状态查询/
  • code/07_任务时间统计/

后续章节:[[06-中断管理与时间管理]]