任务管理

更正文档 贡献说明

概述

任务可以认为是一段独享CPU的运行程序,而应用是完成特定功能的多个任务的集合。任务管理就是为多任务环境中的每个任务分配一个上下文(context)(上下文(context)是指当任务被调度执行的所必不可少的一组数据,包括前任务的CPU指令地址(PC指针),当前任务的栈空间,当前任务的CPU寄存器状态等),在任务相继执行过程中,将切出任务的信息保存在任务上下文中,将切入任务的上下文信息恢复,使其得以执行。为维护任务上下文、状态、栈等相关信息,操作系统内核为每个任务定义了一组数据结构,即任务控制块(Task Control Block),来存放这些信息。 任务调度负责将处理器资源分配给关键任务,让其优先运行。所以系统中的每个任务需要根据关键程度分配不同的优先级,那些执行关键操作的任务被赋予高优先级,而一些非关键性任务赋予低优先级。当系统发生调度时,高优先级任务可以抢占低优先级任务的处理器资源得到调度执行。系统在无任务可调度时,就运行空闲任务,其优先级最低。 任务被创建时,需要为任务指定执行体入口地址、栈大小、优先级等信息,创建过程中内核为任务分配任务控制块(TCB)。任务栈空间可以在任务创建时由用户程序指定,也可以由内核根据用户指定大小来动态分配。操作系统内核提供基于软件的栈溢出检测方法,用户可根据需要配置或关闭该功能。

任务状态

任务状态是反映当前系统中任务所处的状况,操作系统内核需要维护所有任务的当前状态。AliOS Things为了充分描述任务在系统中所处的状态以及引发状态迁移的条件差异,将任务状态分为就绪状态、挂起状态、休眠状态、阻塞状态、运行状态和删除状态。当任务通过aos_task_create()创建时,任务处于挂起状态,当任务通过aos_task_del()删除时,任务处于删除状态,具体的转化过程如下图:

(1)阻塞状态是指因等待资源而处于等待状态,如调用aos_mutex_lock()获取互斥量时互斥量已经被锁定、调用aos_queue_recv()获取队列数据时队列为空、调用aos_sem_wait()等待信号量时信号量计数为0、调用aos_evnet_get()获取事件时,事件还未发生; (2)挂起状态是因任务被其他或自身调用挂起函数aos_task_suspend()后,将无条件地停止运行,被挂起的任务只能通过其他任务调用恢复函数aos_task_resume()使其恢复到就绪状态; (3)休眠状态是因任务在调用任务休眠函数aos_msleep()后,进入一种延迟执行的状态,直到休眠时间到达任务被重新调度恢复运行。 (4)删除状态是因任务运行完成调用任务退出函数aos_task_exit()或被调用任务删除函数aos_task_del()时被设置的一种状态。 (5)就绪状态是在任务被创建或任务解除阻塞或延迟到期时,任务被置为就绪状态。只有当任务处于就绪状态时,才能被系统调度进入运行状态。 (6)运行状态是获取处理器执行权的就绪任务所处的状态,单处理器系统,任意时刻只有一个任务可以运行。

AliOS Things允许任务处于组合状态,如阻塞挂起状态:任务在阻塞状态下,被其他任务挂起,则进入阻塞挂起状态。该状态下,若任务被恢复则保持阻塞状态;若任务解除阻塞则保持挂起状态。 用户可通过tasklist命令查看任务状态,任务状态描述符号和含义如下表:

状态符号

描述

RDY

任务已在就绪队列或已被调度运行,处于就绪状态或运行状态

PEND

任务因等待资源或事件的发生而处于阻塞状态

SUS

任务因被其他或自身调用挂起函数aos_task_suspend()后所处的挂起状态

SLP

任务处于休眠状态

PEND_SUS

任务在阻塞状态下,被其他任务挂起,处于阻塞挂起状态

SLP_SUS

任务在休眠状态下,被其他任务挂起,处于休眠挂起状态

DELETED

任务处于删除状态

任务调度

任务调度是为多任务环境中的就绪任务分配处理器资源。AliOS Things操作系统内核支持两种调度策略:

  • 基于优先级的抢占式调度

该调度策略下,每个任务优先级都维护了一个FIFO模式的就绪队列(ready queue),里面包含了当前所有可运行的任务列表,此列表中的任务都处于就绪状态,当处理器可用时,最高优先级的就绪队列的第一个任务得到处理器被执行。当有一个高优先级任务进入就绪队列,正在运行的低优先级任务会立即被唤出,将处理器执行权交给高优先级任务。此种调度机制存在一个潜在问题,如果存在多个优先级相同的任务,其中一个任务强占处理器,则其他同等优先级的任务将无法被执行。时间片轮转调度可以避免这个问题。

  • 基于时间片的轮转调度

时间片轮转调度使用时间片控制每个任务的执行时间,同等优先级的任务依次获得处理器被调度执行,每个任务可以运行的时间片是固定的,当任务的时间片用完后,该任务被放在对应优先级就绪队列的队尾,然后调度就绪队列第一个位置上的任务来执行。

优先级分配

AliOS Things操作系统内核允许的任务优先级分配范围默认为0~62,值越大表示优先级越低,其中空闲任务的优先级是61,即系统最低优先级。

任务栈保护

任务栈保护采用栈统计的方法在任务切换时,对当前任务栈的使用情况进行统计,当检测到任务栈使用量超过了预先分配的空间,则抛出异常。

空闲任务

空闲任务是一个无限循环函数,在进入循环前或循环体内允许用户配置钩子函数。

任务管理函数

AliOS Things操作系统内核提供了任务创建、任务删除、任务延迟、获取任务名称等任务相关的服务函数,提供给应用程序调用,具体描述如下:

函数名

描述

aos_task_create()

任务创建函数(推荐)

aos_task_new()

任务创建函数(兼容3.1)

aos_task_new_ext()

任务创建函数(兼容3.1)

aos_task_exit()

任务退出函数

aos_task_delete()

任务删除函数

aos_task_resume()

任务恢复函数

aos_task_suspend()

任务挂起函数

aos_task_yield()

任务让出CPU函数

aos_task_self()

获取当前任务的具柄

aos_task_name_get()

获取任务名称

常用配置

说明

任务优先级最大值: 默认62, 最大不能超过256,如需修改,在yaml中修改RHINO_CONFIG_PRI_MAX配置

def_config:
  RHINO_CONFIG_PRI_MAX: 127

说明

任务栈溢出检测: 默认关闭, 如需修改,在yaml中修改RHINO_CONFIG_TASK_STACK_OVF_CHECK配置

def_config:
  RHINO_CONFIG_TASK_STACK_OVF_CHECK: 1

说明

空闲任务栈大小: 默认100Bytes, 如需修改,在yaml中修改RHINO_CONFIG_IDLE_TASK_STACK_SIZE配置

def_config:
  RHINO_CONFIG_IDLE_TASK_STACK_SIZE: 1024

说明

时间片轮转调度策略: 默认使能, 如需修改,在yaml中修改RHINO_CONFIG_SCHED_RR配置

def_config:
  RHINO_CONFIG_SCHED_RR: 0

说明

完全公平调度策略: 默认关闭, 如需修改,在yaml中修改RHINO_CONFIG_SCHED_CFS配置

def_config:
  RHINO_CONFIG_SCHED_CFS: 1

API说明

使用示例

示例代码参考example/task_example.c,该示例使用任务管理函数来控制任务的执行状态,具体场景为任务2因等待某个信号量进入阻塞状态,而此时被任务1将其挂起,则任务2仍然是处于阻塞状态,如果在此过程中等到信号量,则任务2会解除阻塞进入挂起状态;如果未等到信号量,则任务2恢复状态后仍然处于阻塞状态。 示例说明如下:

  1. 在t0时刻,任务task1、task2是通过aos_task_create()函数调用被创建,之后task1进入就绪状态,而task2处于挂起状态。

  1. Task1得到运行后,在t1时刻调用aos_task_resume()将task2恢复,task2进入就绪状态,之后task1通过调用aos_msleep()进入休眠状态,task2因为task1休眠而获得CPU执行权,task2运行后因等待信号量进入阻塞状态。

  1. Task1在t2时刻因延迟到期得到运行,并调用aos_task_suspend()将task2挂起,task2此时的状态为阻塞挂起。之后task1通过调用aos_msleep()进入休眠状态。

  1. Task2在t3时刻因延迟到期得到运行,并调用aos_task_resume()将task2恢复,此时task2的状态为阻塞状态。之后task1通过调用aos_msleep()进入休眠状态。

  1. Task1在t4时刻因延迟到期得到运行,并调用aos_sem_signal()释放信号量,这时task2因等到信号量而进入就绪状态。待到task1再次进入休眠转改后task2得到运行,进入运行状态。

该示例可配置到helloworld_demo案例中运行,相关代码的下载、编译和固件烧录均依赖AliOS Things配套的开发工具 ,所以首先需要参考《AliOS Things集成开发环境使用说明之搭建开发环境》,下载安装 。 待开发环境搭建完成后,可以按照以下步骤进行示例的测试。

步骤1 创建或打开工程

打开已有工程

如果用于测试的案例工程已存在,可参考《AliOS Things集成开发环境使用说明之打开工程》打开已有工程。

创建新的工程

组件的示例代码可以通过编译链接到AliOS Things的任意案例(solution)来运行,这里选择helloworld_demo案例。helloworld_demo案例相关的源代码下载可参考《AliOS Things集成开发环境使用说明之创建工程》

步骤2 添加组件

说明

案例下载完成后,需要在helloworld_demo的package.yaml中添加

depends:
  - osal_aos: dev_aos # helloworld_demo中引入osal_aos组件

步骤3 下载组件

在已安装了 的开发环境工具栏中,选择Terminal -> New Terminal启动终端,并且默认工作路径为当前工程的workspace,此时在终端命令行中输入:


aos install osal_aos

上述命令执行成功后,组件源码则被下载到了./components/osal_aos路径中。

步骤4 添加示例

说明

在osal_aos组件的package.yaml中添加example示例代码:

depends:
  - rhino: dev_aos
  - cli: dev_aos # 添加cli依赖
source_file:
  - "*.c"
  - "example/task_example.c" # 添加 task_example.c

步骤5 编译固件

在示例代码已经添加至组件的配置文件,并且helloworld_demo已添加了对该组件的依赖后,就可以编译helloworld_demo案例来生成固件了,具体编译方法可参考《AliOS Things集成开发环境使用说明之编译固件》

步骤6 烧录固件

helloworld_demo案例的固件生成后,可参考《AliOS Things集成开发环境使用说明之烧录固件》来烧录固件。

步骤7 打开串口

固件烧录完成后,可以通过串口查看示例的运行结果,打开串口的具体方法可参考《AliOS Things集成开发环境使用说明之查看日志》

当串口终端打开成功后,可在串口中输入help来查看已添加的测试命令。

步骤8 测试示例

说明

CLI命令行输入:

task_example

说明

关键日志:

[aos_task_example]task1 is running!
[aos_task_example]task1 resume task2!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task2 is running!
[aos_task_example]task1 suspend task2!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task1 resume task2 again!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task1 signal a semphone!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task2 get semphone and is running!

注意事项

  • 时间片轮转调度也是基于优先级策略的,如果有一个高优先级任务就绪了,无论当前任务的时间片是否用完,处理器都会立即去执行高优先级任务,当被打断的任务恢复执行时,它将继续执行剩下的时间片。

  • tasklist查看运行任务的状态标识符为RDY,内核并没有为运行状态定义特定的标识符。

FAQ

  • Q1: 如何避免栈溢出?

答:任务栈必须满足应用程序对函数嵌套调用所需的空间,用户可以通过tasklist查看栈的使用情况,根据需要调整栈大小,这样可以在防止栈溢出的同时,也可避免栈空间分配过大造成浪费。

  • Q2: aos_task_yield()和aos_task_suspend()都能够使任务退出运行状态,使用上有什么差异吗?

答:aos_task_yield()仅仅是让出CPU,它仍然处于就绪状态,因为该操作只是将任务放置到就绪队列的队 尾,若无更高优先级任务在就绪队列中,它就可以被调度执行。而aos_task_suspend()是将任务挂起停 止运行,任务会从就绪队列中去除,只有当任务被其他任务调用aos_task_resume()才能恢复。