2. 任务创建与删除:API详解

好,咱们直接进入正题。任务创建和删除,是玩转鸿蒙实时操作系统的基本功。说白了,你写的代码最终都要跑在任务里,不会创建任务,那啥也干不了。

我个人习惯,拿到一个新系统,第一件事就是看它的任务管理API。因为任务调度的好坏,直接决定了系统的实时性和稳定性。今天咱们就掰开揉碎,把 ohos_task_createohos_task_delete 这两个核心API讲透。

2.1 ohos_task_create:任务的诞生

这个函数,就是给系统“生”一个任务。你想想看,系统里原本空荡荡的,你调用它,就多了一个能独立运行的小单元。

函数原型

UINT32 ohos_task_create(
    UINT32 *taskID,          // 输出参数,任务创建成功后,这里会存放任务的ID
    const TSK_INIT_PARAM_S *taskInitParam, // 任务初始化参数结构体指针
    TSK_ENTRY_FUNC taskEntry, // 任务入口函数,任务跑什么代码,就靠它
    UINT32 taskParam         // 传递给任务入口函数的参数
);

嗯,这里要注意,taskID 是个指针。我见过不少新手直接传个 NULL 进去,结果任务创建成功了,却不知道它的ID是多少,后面想删除或操作它都没办法。所以,一定得先定义一个 UINT32 变量,把它的地址传进去。

参数配置详解

真正有讲究的,是 TSK_INIT_PARAM_S 这个结构体。咱们把它拆开看:

成员 说明 我的建议
pfnTaskEntry 任务入口函数,跟上面的 taskEntry 是同一个东西 函数签名必须是 UINT32 (*)(UINT32)
uwStackSize 任务栈大小,单位是字节 别抠门,但也别浪费。我一般给1024或2048起步
usTaskPrio 任务优先级,数值越小优先级越高 0-31,0留给系统用,咱们用1-31就行
pcName 任务名字,调试时很有用 起个有意义的名字,比如 "SensorTask"
uwResved 保留参数,填0 别乱填,否则可能出奇怪的问题
⚠️ 我曾经踩过的坑: 栈大小给得太小,任务一运行就栈溢出,系统直接挂掉。调试了半天才发现是栈不够用。所以,如果你的任务里有局部大数组或者深层递归,栈大小一定要给足。

返回值处理

函数返回一个 UINT32 类型的错误码。成功就是 LOS_OK(值为0),失败就是各种错误码。我习惯这样处理:

UINT32 taskID;
TSK_INIT_PARAM_S taskParam = {0};

// 填充参数
taskParam.pfnTaskEntry = MyTaskFunc;
taskParam.uwStackSize = 2048;
taskParam.usTaskPrio = 10;
taskParam.pcName = "MyTask";

UINT32 ret = ohos_task_create(&taskID, &taskParam, MyTaskFunc, 0);
if (ret != LOS_OK) {
    // 创建失败,打印错误码
    printf("Task create failed, error code: 0x%x\n", ret);
    return ret;
}
// 创建成功,taskID 就是新任务的ID
printf("Task created, ID: %d\n", taskID);

你想想看,如果不检查返回值,任务创建失败了你还傻乎乎地往下跑,后面用 taskID 去操作一个不存在的任务,那不乱套了吗?

2.2 ohos_task_delete:任务的终结

有生就有死。任务完成了它的使命,或者系统要回收资源,就得把它删掉。

函数原型

UINT32 ohos_task_delete(UINT32 taskID);

就这么简单,传入你要删除的任务ID就行。但简单归简单,这里面的门道可不少。

💡 核心要点: 任务不能删除自己!如果你在任务A里调用 ohos_task_delete(A的ID),系统会返回错误。正确的做法是,任务A调用 ohos_task_delete 删除任务B,或者任务A自己调用 ohos_task_exit 来结束自己。

返回值处理

同样,返回值很重要:

UINT32 ret = ohos_task_delete(taskID);
if (ret == LOS_OK) {
    printf("Task %d deleted successfully\n", taskID);
} else if (ret == LOS_ERRNO_TSK_NOT_CREATED) {
    printf("Task %d does not exist\n", taskID);
} else if (ret == LOS_ERRNO_TSK_DELETE_SELF) {
    printf("Cannot delete self\n");
} else {
    printf("Delete failed, error: 0x%x\n", ret);
}

我记得有一次,我在一个中断服务函数里尝试删除一个任务,结果系统直接报错。后来查文档才知道,中断上下文里不能调用任务删除API。所以,删除操作一定要在任务上下文里做。

2.3 实战:创建与删除的完整示例

光说不练假把式。咱们写个完整的例子,把创建和删除串起来:

#include "los_task.h"

UINT32 g_taskID;

UINT32 MyTaskFunc(UINT32 param)
{
    printf("MyTask is running, param = %d\n", param);
    
    // 模拟工作,延时10个tick
    LOS_TaskDelay(10);
    
    printf("MyTask is done, deleting itself...\n");
    
    // 注意:这里不能调用 ohos_task_delete(g_taskID)
    // 因为不能删除自己
    // 正确的做法是调用 ohos_task_exit()
    ohos_task_exit();
    return 0; // 这行不会执行到,但为了编译通过
}

void CreateAndDeleteDemo(void)
{
    TSK_INIT_PARAM_S taskParam = {0};
    
    taskParam.pfnTaskEntry = MyTaskFunc;
    taskParam.uwStackSize = 1024;
    taskParam.usTaskPrio = 15;
    taskParam.pcName = "DemoTask";
    
    UINT32 ret = ohos_task_create(&g_taskID, &taskParam, MyTaskFunc, 42);
    if (ret != LOS_OK) {
        printf("Create failed: 0x%x\n", ret);
        return;
    }
    
    printf("Created task ID: %d\n", g_taskID);
    
    // 等一会儿,让MyTask跑完
    LOS_TaskDelay(20);
    
    // 如果MyTask还没自己退出,可以强制删除
    ret = ohos_task_delete(g_taskID);
    if (ret == LOS_OK) {
        printf("Task deleted from outside\n");
    } else {
        printf("Delete failed or task already exited: 0x%x\n", ret);
    }
}
📌 我的小技巧: 在实际项目中,我很少让任务自己删除自己。更常见的做法是,设计一个“任务管理器”任务,专门负责创建和删除其他任务。这样逻辑清晰,也方便调试。

2.4 避坑指南

最后,总结几个我亲身经历过的坑,你遇到了可以少走弯路:

  • 栈溢出检测: 鸿蒙内核有栈溢出检测机制,但默认可能没开。我建议在调试阶段打开,能帮你快速定位栈大小问题。
  • 优先级反转: 创建任务时,优先级别设得太极端。我曾经把两个任务的优先级设成1和2,结果高优先级任务一直占着CPU,低优先级任务饿死了。
  • 任务名重复: 虽然系统允许同名任务,但调试时你会疯掉的。所以,每个任务的名字最好唯一。
  • 删除后的资源释放: 任务删除后,它占用的栈空间和TCB会被系统回收。但如果你在任务里动态申请了内存(比如 malloc),记得在退出前释放掉,否则就内存泄漏了。

嗯,关于任务创建和删除,核心内容就这些。说白了,就是两个API,但用好了,你的系统就稳了一半。下一节咱们聊聊任务状态和优先级,那才是调度的精髓所在。