一、任务简介
关于任务的相关介绍,之前文章有比较详细的介绍,这里不做过多解释,可以参考如下文章:FreeRTOS学习二(任务)_t_guest的博客-CSDN博客
而LiteOS的主要特性可以总结为如下几点:
- LiteOS的任务模块可以给用户提供多个任务,实现了任务之间的切换和通信,帮助用户管理业务程序流程。
- LiteOS中的任务是抢占式调度机制,高优先级的任务可以打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度,同时支持时间片轮转调度方式。
- LiteOS的任务默认有32个优先级(0-31),最高优先级为0,最低优先级为31.
二、任务
任务状态
任务状态通常分为一下四种:
- 就绪(Ready):该任务在就绪列表中,只等待CPU
- 运行(Running):该任务正在执行。
- 阻塞(Blocked):该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或等待读写事件等。
- 退出态(Dead):任务运行结束,等待系统回收资源。
名词介绍
任务ID:在任务创建时通过参数返回给用户,作为任务的一个非常重要的标识
任务优先级:优先级标识任务执行的优先顺序
任务入口函数:每个新任务得到调度后将执行的函数
任务控制块TCB:每个任务都含有一个任务控制快(TCB-Task Control Block)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。TCB可以反映出每个任务的运行情况。
任务栈:每个任务都拥有一个独立的栈空间,称为任务栈。
任务上下文:任务在运行过程中使用到的一些资源,如寄存器等,我们称为任务上下文。LitsOS在任务挂起的时候会将本任务的任务上下文信息,保存在自己的任务栈里面,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行被挂起时被打断的代码。
任务切换:任务切换包含获取就绪列表中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。
任务状态迁移
就绪态->运行态:任务创建后进入就绪态,发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态,但此刻该任务依旧在就绪列表中。
运行态->阻塞态:任务运行因挂起、读信号量等待等,在就绪列表中被删除进入阻塞态。
阻塞态->就绪态(阻塞态->运行态):阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务就会被加入就绪列表,从而由阻塞态变成就绪态。此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务由就绪态变成运行态。
就绪态->阻塞态:任务在就绪态被挂起,进而进入阻塞态。
运行态->就绪态:有更高优先级任务创建或恢复后,发生任务切换而进入就绪列表。
运行态->退出态:任务运行结束,内核自动将此任务删除,此时由运行态变为退出态。
阻塞态->退出态:阻塞的任务调用删除接口,任务状态由阻塞态变成退出态。
三、 实操
LiteOS-m的内核代码是从CMSIS-RTOS2接口中封装而来。
1.什么是CMSIS-RTOS2接口
CMSIS是Cortex微控制器软件接口标准(Cortex Microcontroller Software Interface Standard)是ARM和一些编译器厂家以及半导体厂家共同遵循的一套标准,是由ARM专门针对Cortex-M系列提出的标准。在该标准的约定下,ARM和芯片厂商会提供一些通用的API接口来访问Cortex内核以及一些专用外设,以减少更换芯片以及开发工具等移植工作所带来的金钱以及时间上的消耗。
CMSIS-RTOS2(CMSIS-RTOS API Version 2)是Arm® Cortex®-M 处理器的通用的RTOS接口。为需要RTOS功能的软件组件提供了标准化的API。
CMSIS-RTOS2是一个通用的API,它与底层的RTOS内核无关,写应用程序的程序员在用户代码中调用CMSIS-RTOS2 API函数,可以更方便地将应用程序从一个RTOS到另一个RTOS,使用CMSIS-RTOS2 API的中间件也可以避免很多不必要的移植工作。
官方API参考:Main Page
SDK中内核的源码文件在kernel/liteos_m/components/cmsis中。
2.常用函数
osThreadNew
函数功能:
创建一个新的任务。
函数原型:
osThreadId_t osThreadNew(osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
参数:
func:线程的回调函数
argument:作为启动参数传递给线程函数的指针。一般为NULL
attr:线程属性。线程的相关属性都在这里设置,包括线程堆栈大小,优先级等等。主要看osThreadAttr_t数据类型。
typedef struct {
/** Thread name */
const char *name;
/** Thread attribute bits */
uint32_t attr_bits;
/** Memory for the thread control block */
void *cb_mem;
/** Size of the memory for the thread control block */
uint32_t cb_size;
/** Memory for the thread stack */
void *stack_mem;
/** Size of the thread stack */
uint32_t stack_size;
/** Thread priority */
osPriority_t priority;
/** TrustZone module of the thread */
TZ_ModuleId_t tz_module;
/** Reserved */
uint32_t reserved;
} osThreadAttr_t;
name | 线程的名称 指向具有线程对象的可读字符串 默认值为:NULL |
attr_bits | 属性位,可以设置线程对象的选项。 osThreadDetached(0):在分离模式下创建线程(默认) osThreadJoinable(1):在可连接模式下创建线程 |
cb_mem | 内存控制块位置 指向线程控制块对象的内存位置。静态内存分配时使用 默认值:NULL(动态内存分配) |
cb_size | 为控制块提供的内存大小 内存块的大小与cb_mem一起传递。必须大于或等于线程控制块的大小。(静态内存分配时使用) |
stack_mem | 内存的堆栈位置 指向线程堆栈的内存位置的指针,必须64字节对齐。静态内存分配时使用。 默认值:NULL(动态内存分配) |
stack_size | 堆栈大小 由stack_mem指定的堆栈大小。即给创建的线程分配的堆栈大小 |
priority | 线程优先级。 默认值:osPriorityNormal(24) 注:这里的优先级与liteos的优先级不同。Liteos优先级是31最低,0最高。这里0最低,38最高。 |
tz_module | TrustZone模块标识符 线程上下文管理标识符为线程分配上下文内存。以非安全状态运行的RTOS内核调用由头文件TZ_context.h定义的接口函数。对于根本不使用安全调用的线程,可以安全地设置为零。 |
reserved | 保留 默认值:0 |
返回值:
线程ID,可以供其他函数调用。
实例:
attr.name = "thread1";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 1;
attr.priority = 25;
g_thread1_id = osThreadNew((osThreadFunc_t)thread1, NULL, &attr);
if (g_thread1_id == NULL)
{
LOG_E("Falied to create thread1!");
}
osThreadYield
函数功能:
将控制权交给下一个具有相同优先级并处于 READY 状态的线程。如果在状态 READY 中没有其他具有相同优先级的线程,则当前线程继续执行并且不发生线程切换。osThreadYield 不会将线程设置为状态 BLOCKED 。因此,即使状态为 READY 的线程可用,也不会调度具有较低优先级的线程。该函数不能从中断服务程序调用。
函数原型:
osStatus_t osThreadYield(void)
参数:
无
返回值:
osOK:成功
其他值:失败
实例:
osThreadYield();
osThreadGetId
函数功能:
获取线程ID
函数原型:
osThreadGetId
参数:
无
返回值:
线程ID
实例:
osThreadId_t temp_t2_id = osThreadGetId();
osThreadGetName
函数功能:
获取线程的名字。名字是线程在创建是设置的。
函数原型:
const char *osThreadGetName(osThreadId_t thread_id)
参数:
thread_id:线程ID。通过osThreadGetId或osThreadNew获得。
返回值:
线程的ID。错误时返回NULL
实例:
osThreadId_t temp_t2_id = osThreadGetId();
const char *temp_name = osThreadGetName(temp_t2_id);
osThreadGetStackSize
函数功能:
获取线程总栈大小
函数原型:
uint32_t osThreadGetStackSize(osThreadId_t thread_id)
参数:
线程ID。通过osThreadGetId或osThreadNew获得。
返回值:
线程总栈大小
实例:
osThreadId_t temp_t2_id = osThreadGetId();
osThreadGetStackSize(temp_t2_id);
osThreadGetStackSpace
函数功能:
获取线程剩余栈大小
函数原型:
uint32_t osThreadGetStackSpace(osThreadId_t thread_id)
参数:
线程ID。通过osThreadGetId或osThreadNew获得。
返回值:
线程剩余栈大小
实例:
osThreadId_t temp_t2_id = osThreadGetId();
osThreadGetStackSpace(temp_t2_id);
osKernelGetTickCount
函数功能:
获取系统时钟的Tick数。通常Tick数周期为1ms。
函数原型:
uint32_t osKernelGetTickCount(void)
参数:
无
返回值:
Tick数
实例:
osKernelGetTickCount()
osDelay
函数功能:
线程挂起时间。
函数原型:
osStatus_t osDelay(uint32_t ticks)
参数:
挂起ticks数。真正的挂起时间为ticks*10ms
返回值:
osOK:正常
osError:异常
实例:
osDelay(200);
osDelayUntil
函数功能:
线程挂起,直到ticks数为止。
注:调用该函数后,线程会挂起,直到系统的ticks数,到达设置的ticks数为止。例如这里设置ticks数为1000,那么线程运行到osDelayUntil函数后会查询当前的系统ticks数,如果小于1000则挂起等待,直到系统ticks数等于1000后,才开始继续往下执行。
函数原型:
osStatus_t osDelayUntil(uint32_t ticks)
参数:
ticks数
返回值:
osOK:正常
osError:异常
实例:
osDelayUntil(1000);
osThreadTerminate
函数功能:
删除线程。线程终止后,所有的资源都会返回到系统。
注:该函数不能在中断服务中调用
函数原型:
osStatus_t osThreadTerminate(osThreadId_t thread_id)
参数:
线程ID
返回值:
osOK:成功
其他值:异常。含义参考如下:
typedef enum {
/** Operation completed successfully */
osOK = 0,
/** Unspecified error */
osError = -1,
/** Timeout */
osErrorTimeout = -2,
/** Resource error */
osErrorResource = -3,
/** Incorrect parameter */
osErrorParameter = -4,
/** Insufficient memory */
osErrorNoMemory = -5,
/** Service interruption */
osErrorISR = -6,
/** Reserved. It is used to prevent the compiler from optimizing enumerations. */
osStatusReserved = 0x7FFFFFFF
} osStatus_t;
实例:
osThreadId_t temp_t2_id = osThreadGetId();
osStatus_t ret = osThreadTerminate(temp_t2_id);
四、综合实例
这里我们创建两个线程,并且分别打印两个线程的栈大小、名字、剩余栈大小。且任务一使用创建时osThreadNew返回的任务ID,而任务2使用osThreadGetId获取的任务ID。看看效果是否相同。
#define LOG_I(fmt, args...) printf("<%8ld> - [APP]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...) printf("<%8ld>-[APP_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);
osThreadId_t g_thread1_id = NULL;
osThreadId_t g_thread2_id = NULL;
/*****任务一*****/
void thread1(void)
{
LOG_I("thread 1 start");
const char *temp_name = osThreadGetName(g_thread1_id);
int sum = 0;
osDelayUntil(1000);
while (1)
{
LOG_I("this is Thread 1,name:[%s],thread stack size:[%ld],left stack:[%ld],sum:%d", temp_name,osThreadGetStackSize(g_thread1_id),osThreadGetStackSpace(g_thread1_id),sum);
osDelay(100);
if(sum++ > 10)
break;
}
LOG_I("thread 1 break");
osThreadTerminate(g_thread1_id);
}
/*****任务二*****/
void thread2(void)
{
LOG_I("thread 2 start");
osThreadId_t temp_t2_id = osThreadGetId();
const char *temp_name = osThreadGetName(temp_t2_id);
while (1)
{
LOG_I("this is Thread 2,name:[%s],thread stack size:[%ld],left stack:[%ld]", temp_name,osThreadGetStackSize(temp_t2_id),osThreadGetStackSpace(temp_t2_id));
osDelay(100);
}
LOG_I("thread 2 end");
}
void Hello_World(void)
{
osThreadAttr_t attr;
LOG_I("hello!!!!!!!!!!!!!!!!!!!!!!!!!");
attr.name = "thread1";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 1;
attr.priority = 25;
g_thread1_id = osThreadNew((osThreadFunc_t)thread1, NULL, &attr);
if (g_thread1_id == NULL)
{
LOG_E("Falied to create thread1!");
}
else
{
LOG_I("thread1 id:0x%.8x",*(uint32_t *)(g_thread1_id));
}
attr.name = "thread2";
attr.stack_size = 1024 * 2;
g_thread2_id = osThreadNew((osThreadFunc_t)thread2, NULL, &attr);
if (g_thread2_id == NULL)
{
LOG_E("Falied to create thread2!");
}
else
{
LOG_I("thread2 id:0x%.8x",*(uint32_t *)(g_thread2_id));
}
}
SYS_RUN(Hello_World);
从结果可以看到如下几点信息:
- 线程1在开始运行后,遇到了osDelayUntil,直接挂起。等到系统ticks到达1000后才继续往下运行。
- 线程1中使用的线程ID是创建线程时osThreadNew返回的。而线程2中使用的线程ID是通过osThreadGetId函数获取的。都能达到预期的效果。
- osThreadGetStackSize和osThreadGetStackSpace会分别打印出栈总大小和剩余栈大小。
- 线程1在运行10次后,就删除了自身,并且释放了所有的资源。
参考链接:
CMSIS-RTOS2 文档翻译 之 参考(CMSIS-RTOS2 API 之 线程管理) - 爱码网