2. 任务创建与删除:API详解
好,咱们直接进入正题。任务创建和删除,是玩转鸿蒙实时操作系统的基本功。说白了,你写的代码最终都要跑在任务里,不会创建任务,那啥也干不了。
我个人习惯,拿到一个新系统,第一件事就是看它的任务管理API。因为任务调度的好坏,直接决定了系统的实时性和稳定性。今天咱们就掰开揉碎,把 ohos_task_create 和 ohos_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就行。但简单归简单,这里面的门道可不少。
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,但用好了,你的系统就稳了一半。下一节咱们聊聊任务状态和优先级,那才是调度的精髓所在。