ESP32移植Openharmony设备开发---(3)任务调度

任务调度

官方文档:OpenAtom OpenHarmony

基本概念

从系统角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,各任务的运行相互独立。

OpenHarmony LiteOS-M的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。任务模块具有如下特性:

  1. 支持多任务。
  2. 一个任务表示一个线程。
  3. 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
  4. 相同优先级任务支持时间片轮转调度方式。
  1. 共有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_TaskCreate:创建任务,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务。
LOS_TaskDelete:删除指定的任务。

控制任务状态

LOS_TaskResume:恢复挂起的任务,使该任务进入ready状态。
LOS_TaskSuspend:挂起指定的任务,然后切换任务。
LOS_TaskJoin:挂起当前任务,等待指定任务运行结束并回收其任务控制块资源
LOS_TaskDelay:任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态。传入参数为Tick数目。
LOS_Msleep:任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态。传入参数为毫秒数。
LOS_TaskYield:当前任务时间片设置为0,释放CPU,触发调度运行就绪任务队列中优先级最高的任务。

控制任务调度

LOS_TaskLock:锁任务调度,但任务仍可被中断打断。
LOS_TaskUnlock:解锁任务调度。
LOS_Schedule:触发任务调度。

控制任务优先级

LOS_CurTaskPriSet:设置当前任务的优先级。
LOS_TaskPriSet:设置指定任务的优先级。
LOS_TaskPriGet:获取指定任务的优先级。

获取任务信息

LOS_CurTaskIDGet:获取当前任务的ID。
LOS_NextTaskIDGet:获取任务就绪队列中优先级最高的任务的ID。
LOS_NewTaskIDGet:等同LOS_NextTaskIDGet。
LOS_CurTaskNameGet:获取当前任务的名称。
LOS_TaskNameGet:获取指定任务的名称。
LOS_TaskStatusGet:获取指定任务的状态。
LOS_TaskInfoGet:获取指定任务的信息,包括任务状态、优先级、任务栈大小、栈顶指针SP、任务入口函数、已使用的任务栈大小等。
LOS_TaskIsRunning:获取任务模块是否已经开始调度运行。

任务信息维测

LOS_TaskSwitchInfoGet:获取任务切换信息,需要开启编译控制宏:LOSCFG_BASE_CORE_EXC_TSK_SWITCH。

新建工程文件夹002_LOS_Task,文件夹下有两个文件:

  1. BUILD.gn
  2. 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键,即可观察到实验现象:

  1. 应用程序启动
    • 应用程序从Example_TskCaseEntry函数开始执行。
  1. 锁定任务调度
    • 调用LOS_TaskLock()锁定任务调度,防止创建任务期间发生任务切换。
  1. 获取当前任务信息
    • 获取当前任务的ID,并打印当前任务的优先级。
  1. 创建高优先级任务
    • 初始化任务参数,设置任务入口函数为Example_TaskHi,优先级为TSK_PRIOR_HI(4),任务名为“TaskHi”。
    • 调用LOS_TaskCreate()创建高优先级任务,并存储任务ID到g_taskHiId
  1. 创建低优先级任务
    • 初始化任务参数,设置任务入口函数为Example_TaskLo,优先级为TSK_PRIOR_LO(5),任务名为“TaskLo”。
    • 调用LOS_TaskCreate()创建低优先级任务,并存储任务ID到g_taskLoId
  1. 解锁任务调度
    • 调用LOS_TaskUnlock()解锁任务调度,此时系统开始调度任务。
  1. 任务调度与执行
    • 由于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.”。
  1. 任务恢复与结束
    • 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 : 新建线程的函数入口
usTaskPrio : 线程优先级,0-31
uwArg : 线程函数传参
stackAddr : 堆栈起始地址

uwStackSize : 堆栈大小,当传入值为0时,使用默认值LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE
pcName : 线程名称

uwResved : 保留参数可以省略

UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam);

taskID : 指针,指向线程id
taskInitParam : 新建线程的相关属性以及线程的函数入口和参数等

UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam);

taskID : 指针,指向线程id
taskInitParam : 新建线程的相关属性以及线程的函数入口和参数等

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 : 类型为指向TSK_INFO_S结构体的指针。通过这个指针,函数会填充有关任务的各种信息。

BOOL LOS_TaskIsRunning(VOID);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值