任务调度
官方文档:OpenAtom OpenHarmony
基本概念
从系统角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,各任务的运行相互独立。
OpenHarmony LiteOS-M的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。任务模块具有如下特性:
- 支持多任务。
- 一个任务表示一个线程。
- 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
- 相同优先级任务支持时间片轮转调度方式。
- 共有32个优先级[0-31],最高优先级为0,最低优先级为31。
任务状态
任务有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
任务状态通常分为以下四种:
就绪(Ready) | 该任务在就绪队列中,只等待CPU。 |
运行(Running) | 该任务正在执行。 |
阻塞(Blocked) | 该任务不在就绪队列中。包含任务被挂起(suspend状态)、任务被延时(delay状态)、任务正在等待信号量、读写队列或者等待事件等。 |
退出态(Dead) | 该任务运行结束,等待系统回收资源。 |
系统会同时存在多个任务,因此就绪态和阻塞态的任务分别会加入就绪队列和阻塞队列。队列只是相同状态任务的合集,加入队列的先后与任务状态迁移的顺序无关。运行态任务仅存在一个,不存在运行态队列。
任务状态迁移说明
就绪态→运行态 | 任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,同时该任务从就绪队列中移出。 |
运行态→阻塞态 | 正在运行的任务发生阻塞(挂起、延时、读信号量等)时,将该任务插入到对应的阻塞队列中,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪队列中最高优先级任务。 |
阻塞态→就绪态 | (阻塞态→运行态的前置条件) 阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪队列,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,该任务由就绪态变成运行态。 |
就绪态→阻塞态 | 任务也有可能在就绪态时被阻塞(挂起),此时任务状态由就绪态变为阻塞态,该任务从就绪队列中移出,不会参与任务调度,直到该任务被恢复。 |
运行态→就绪态 | 有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪队列中。 |
运行态→退出态 | 运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及Invalid状态。例如,任务运行结束但是没有自删除,对外呈现的就是Invalid状态,即退出态。 |
阻塞态→退出态 | 阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。 |
任务ID
在任务创建时通过参数返回给用户。系统中任务ID号是唯一的,是任务的重要标识。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。
任务优先级
优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务,就绪队列中最高优先级的任务将得到执行。
任务入口函数
新任务得到调度后将执行的函数。该函数由用户实现,在任务创建时,通过任务创建结构体设置。
任务栈
每个任务都拥有一个独立的栈空间,我们称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。
任务上下文
任务在运行过程中使用的一些资源,如寄存器等,称为任务上下文。当这个任务挂起时,其他任务继续执行,可能会修改寄存器等资源中的值。如果任务切换时没有保存任务上下文,可能会导致任务恢复后出现未知错误。因此在任务切换时会将切出任务的任务上下文信息,保存在自身的任务栈中,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行挂起时被打断的代码。
任务控制块(TCB)
每个任务都含有一个任务控制块(TCB)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。TCB可以反映出每个任务运行情况。
任务切换
任务切换包含获取就绪队列中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。
OpenHarmony LiteOS-M内核的任务管理模块提供下面几种功能,接口详细信息可以查看API参考。
任务管理模块接口
功能分类 | 接口描述 |
创建和删除任务 | LOS_TaskCreateOnly:创建任务,并使该任务进入suspend状态。 |
控制任务状态 | LOS_TaskResume:恢复挂起的任务,使该任务进入ready状态。 |
控制任务调度 | LOS_TaskLock:锁任务调度,但任务仍可被中断打断。 |
控制任务优先级 | LOS_CurTaskPriSet:设置当前任务的优先级。 |
获取任务信息 | LOS_CurTaskIDGet:获取当前任务的ID。 |
任务信息维测 | LOS_TaskSwitchInfoGet:获取任务切换信息,需要开启编译控制宏:LOSCFG_BASE_CORE_EXC_TSK_SWITCH。 |
新建工程文件夹002_LOS_Task,文件夹下有两个文件:
- BUILD.gn
- LOS_Task_example.c
BUILD.gn
小tips:该gn文件是工程同目录下的BUILD.gn文件
因为用到了liteos-m的任务调度,所以需要引用liteos-m内核库,具体可以参考上节课程中的BUILD.gn的写法,并做一点修改---添加一个参数include_dir,该参数表示项目所引用的库的路径,以后移植第三方库可以通过此方式进行引用
LOS_Task_example.c
/*
* Copyright (c) 2022 Hunan OpenValley Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "los_task.h"
#include "ohos_run.h"
UINT32 g_taskHiId;
UINT32 g_taskLoId;
#define TSK_PRIOR_HI 4
#define TSK_PRIOR_LO 5
UINT32 Example_TaskHi(UINT32 arg)
{
UINT32 ret;
printf("Enter TaskHi Handler.\n");
/* 延时100个Ticks,延时后该任务会挂起,执行剩余任务中最高优先级的任务(TaskLo任务) */
ret = LOS_TaskDelay(100);
if (ret != LOS_OK) {
printf("Delay TaskHi Failed.\n");
return LOS_NOK;
}
/* 100个Ticks时间到了后,该任务恢复,继续执行 */
printf("TaskHi LOS_TaskDelay Done.\n");
/* 挂起自身任务 */
ret = LOS_TaskSuspend(g_taskHiId);
if (ret != LOS_OK) {
printf("Suspend TaskHi Failed.\n");
return LOS_NOK;
}
printf("TaskHi LOS_TaskResume Success.\n");
return ret;
}
/* 低优先级任务入口函数 */
UINT32 Example_TaskLo(UINT32 arg)
{
UINT32 ret;
printf("Enter TaskLo Handler.\n");
/* 延时100个Ticks,延时后该任务会挂起,执行剩余任务中最高优先级的任务 */
ret = LOS_TaskDelay(100);
if (ret != LOS_OK) {
printf("Delay TaskLo Failed.\n");
return LOS_NOK;
}
printf("TaskHi LOS_TaskSuspend Success.\n");
/* 恢复被挂起的任务g_taskHiId */
ret = LOS_TaskResume(g_taskHiId);
if (ret != LOS_OK) {
printf("Resume TaskHi Failed.\n");
return LOS_NOK;
}
return ret;
}
/* 任务测试入口函数,创建两个不同优先级的任务 */
UINT32 Example_TskCaseEntry(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S initParam = {0};
/* 锁任务调度,防止新创建的任务比本任务高而发生调度 */
LOS_TaskLock();
printf("LOS_TaskLock() Success!\n");
UINT32 curTaskID = LOS_CurTaskIDGet();
TSK_INFO_S taskInfo;
ret = LOS_TaskInfoGet(curTaskID, &taskInfo);
if (ret == LOS_OK) {
printf("curTask pro = %d\n", taskInfo.usTaskPrio);
}
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskHi;
initParam.usTaskPrio = TSK_PRIOR_HI;
initParam.pcName = "TaskHi";
initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
initParam.uwResved = LOS_TASK_ATTR_JOINABLE; /* detach 属性 允许使用LOS_TaskJoin */
/* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskHiId, &initParam);
if (ret != LOS_OK) {
LOS_TaskUnlock();
printf("Example_TaskHi create Failed!\n");
return LOS_NOK;
}
printf("Example_TaskHi create Success!\n");
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskLo;
initParam.usTaskPrio = TSK_PRIOR_LO;
initParam.pcName = "TaskLo";
initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
/* 创建低优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskLoId, &initParam);
if (ret != LOS_OK) {
LOS_TaskUnlock();
printf("Example_TaskLo create Failed!\n");
return LOS_NOK;
}
printf("Example_TaskLo create Success!\n");
/* 解锁任务调度,此时会发生任务调度,执行就绪队列中最高优先级任务 */
LOS_TaskUnlock();
ret = LOS_TaskJoin(g_taskHiId, NULL);
if (ret != LOS_OK) {
printf("Join Example_TaskHi Failed!\n");
} else {
printf("Join Example_TaskHi Success!\n");
}
return LOS_OK;
}
OHOS_APP_RUN(Example_TskCaseEntry);
代码分析:在OS_Thread_example函数中,通过osThreadNew()函数创建了threadHi和threadLo两个不同优先级的任务,包括任务延迟、挂起、恢复等操作,threadHi和threadLo启动后会输出打印日志,通过打印日志可熟悉任务优先级调度机制。
编译并烧录
修改工作文件夹级的BUILD.gn文件,将编译的文件夹指定为新建的002_LOS_Task
在源码根目录下使用hb工具对写好的代码进行编译
选择mini级系统
同理 产品选择esp公司下的esp32
选择完毕后在源码根目录下执行hb build -f 进行编译
编译完成后会有如下界面,并且编译后的代码固件位于:out\esp32\esp32
配置软件,如下图,包括升级的镜像,配置,确保COM口是有的点击START
按住boot键不松开,再按一下EN键,0.5秒后松开boot键,可以看到升级成功:
验证结果
打开串口工具->选择COM5->打开串口
按下ESP32开发板上的EN键,即可观察到实验现象:
- 应用程序启动:
-
- 应用程序从
Example_TskCaseEntry
函数开始执行。
- 应用程序从
- 锁定任务调度:
-
- 调用
LOS_TaskLock()
锁定任务调度,防止创建任务期间发生任务切换。
- 调用
- 获取当前任务信息:
-
- 获取当前任务的ID,并打印当前任务的优先级。
- 创建高优先级任务:
-
- 初始化任务参数,设置任务入口函数为
Example_TaskHi
,优先级为TSK_PRIOR_HI
(4),任务名为“TaskHi”。 - 调用
LOS_TaskCreate()
创建高优先级任务,并存储任务ID到g_taskHiId
。
- 初始化任务参数,设置任务入口函数为
- 创建低优先级任务:
-
- 初始化任务参数,设置任务入口函数为
Example_TaskLo
,优先级为TSK_PRIOR_LO
(5),任务名为“TaskLo”。 - 调用
LOS_TaskCreate()
创建低优先级任务,并存储任务ID到g_taskLoId
。
- 初始化任务参数,设置任务入口函数为
- 解锁任务调度:
-
- 调用
LOS_TaskUnlock()
解锁任务调度,此时系统开始调度任务。
- 调用
- 任务调度与执行:
-
- 由于
TaskHi
的优先级高于TaskLo
,并且任务调度已解锁,所以首先执行TaskHi
。 TaskHi
打印进入任务的消息:“Enter TaskHi Handler.”。TaskHi
延迟100个ticks,期间调度器选择其他就绪任务执行。TaskLo
开始执行,并打印进入任务的消息:“Enter TaskLo Handler.”。TaskLo
延迟100个ticks,期间TaskHi
仍处于延迟状态。TaskLo
的延迟结束后,TaskLo
恢复TaskHi
。TaskHi
的延迟结束后,TaskHi
恢复执行,并打印延迟完成的消息:“TaskHi LOS_TaskDelay Done.”。TaskHi
挂起自身,并打印挂起成功的消息:“TaskHi LOS_TaskResume Success.”。
- 由于
- 任务恢复与结束:
-
TaskLo
恢复TaskHi
,并打印恢复成功的消息:“TaskHi LOS_TaskSuspend Success.”。TaskHi
被恢复后,由于其优先级较高,重新获得CPU执行权。TaskHi
执行完毕后,Example_TskCaseEntry
函数通过LOS_TaskJoin
等待TaskHi
结束,并打印成功消息:“Join Example_TaskHi Success!”。
API参考
任务调度 | |
typedef struct tagTskInitParam { TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function >*/ UINT16 usTaskPrio; /**< Task priority > */ UINT32 uwArg; /**< Task parameters >*/ UINTPTR stackAddr; /**< Task stack memory >*/ UINT32 uwStackSize; /**< Task stack size >*/ CHAR *pcName; /**< Task name >*/ UINT32 uwResved; /**< Reserved >*/ } TSK_INIT_PARAM_S; | pfnTaskEntry : 新建线程的函数入口 uwStackSize : 堆栈大小,当传入值为0时,使用默认值LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE uwResved : 保留参数可以省略 |
UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam); | taskID : 指针,指向线程id |
UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam); | taskID : 指针,指向线程id |
UINT32 LOS_TaskDelete(UINT32 taskID); | taskID : 指针,指向线程id |
UINT32 LOS_TaskResume(UINT32 taskID); | taskID : 指针,指向线程id |
UINT32 LOS_TaskSuspend(UINT32 taskID); | taskID : 指针,指向线程id |
UINT32 LOS_TaskJoin(UINT32 taskID, UINTPTR *retval); | taskID : 指针,指向线程id retval :线程taskID结束后的返回值 |
UINT32 LOS_TaskDelay(UINT32 tick); | tick: 所延迟的tick时间 |
VOID LOS_Msleep(UINT32 mSecs); | mSecs:所延时的ms时间 |
UINT32 LOS_TaskYield(VOID); | |
VOID LOS_TaskLock(VOID); | |
VOID LOS_TaskUnlock(VOID); | |
UINT32 LOS_CurTaskPriSet(UINT16 taskPrio); | taskPrio:任务优先级 |
UINT32 LOS_TaskPriSet(UINT32 taskID, UINT16 taskPrio); | taskID : 指针,指向线程id taskPrio:任务优先级 |
UINT16 LOS_TaskPriGet(UINT32 taskID); | taskID : 指针,指向线程id |
UINT32 LOS_CurTaskIDGet(VOID); | |
UINT32 LOS_NextTaskIDGet(VOID); | |
CHAR *LOS_CurTaskNameGet(VOID); | |
CHAR* LOS_TaskNameGet(UINT32 taskID); | taskID : 指针,指向线程id |
UINT32 LOS_TaskStatusGet(UINT32 taskID, UINT32* taskStatus); | taskID : 指针,指向线程id taskStatus : 指向UINT32类型变量的指针, 该指针指向的变量会被设置为指定任务的状态值。 |
UINT32 LOS_TaskInfoGet(UINT32 taskID, TSK_INFO_S *taskInfo); | taskID : 指针,指向线程id taskInfo : 类型为指向 |
BOOL LOS_TaskIsRunning(VOID); |