uCOS-III 学习记录(11)——任务管理

Posted Mount256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uCOS-III 学习记录(11)——任务管理相关的知识,希望对你有一定的参考价值。

参考内容:《[野火]uCOS-III内核实现与应用开发实战指南——基于STM32》第 15、16 和 21 章。

从本文开始,是 uCOS 的 API 应用。

文章目录

1 任务状态

在 uCOS 中,任务状态分为以下几种,任务就是在这几种状态中来回变化的:

  • 就绪(OS_TASK_STATE_RDY:该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
  • 延时(OS_TASK_STATE_DLY:该任务处于延时调度状态。
  • 等待(OS_TASK_STATE_PEND:任务调用 OSQPend()、OSSemPend() 这类等待函数,系统就会设置一个超时时间让该任务处于等待状态,如果超时时间设置为 0,任务的状态,无限期等下去,直到事件发生。如果超时时间为 N(N>0),在 N 个时间内任务等待的事件或信号都没发生,就退出等待状态转为就绪状态。(现阶段忽视)
  • 运行(OS_TASK_STATE_PEND_TIMEOUT:该状态表明任务正在执行,此时它占用处理器,uCOS 调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态,其实运行态的任务也是处于就绪列表中的。
  • 挂起(OS_TASK_STATE_SUSPENDED:任务通过调用 OSTaskSuspend() 函数能够挂起自己或其他任务,调用 OSTaskResume() 是使被挂起的任务回复运行的唯一的方法。挂起一任务意味着该任务再被恢复运行以前不能够取得 CPU 的使用权,类似强行暂停一个任务。
  • 延时+挂起(OS_TASK_STATE_DLY_SUSPENDED:任务先产生一个延时,延时没结束的时候被其他任务挂起,挂起的效果叠加,当且仅当延时结束并且挂起被恢复了,该任务才能够再次运行。
  • 等待+挂起(OS_TASK_STATE_PEND_SUSPENDED:任务先等待一个事件或信号的发生(无限期等待),还没等待到就被其他任务挂起,挂起的效果叠加,当且仅当任务等待到事件或信号并且挂起被恢复了,该任务才能够再次运行。(现阶段忽视)
  • 超时等待+挂起(OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:任务在指定时间内等待事件或信号的产生,但是任务已经被其他任务挂起。(现阶段忽视)
  • 删除(OS_TASK_STATE_DEL:任务被删除后的状态,任务被删除后将不再运行,除非重新创建任务。

在 os.h 中宏定义了任务的状态值:

/* 系统状态 */
#define  OS_STATE_OS_STOPPED                    (OS_STATE)(0u)
#define  OS_STATE_OS_RUNNING                    (OS_STATE)(1u)
	
/* 任务状态 */
#define	 OS_TASK_STATE_BIT_DLY					(OS_STATE)(0x01u)	/* 挂起位      				*/
#define	 OS_TASK_STATE_BIT_PEND					(OS_STATE)(0x02u)	/* 等待位      				*/
#define	 OS_TASK_STATE_BIT_SUSPENDED			(OS_STATE)(0x04u)	/* 延时/超时位 				*/
	
#define  OS_TASK_STATE_RDY						(OS_STATE)(   0u)	/* 0 0 0  就绪 				*/
#define  OS_TASK_STATE_DLY						(OS_STATE)(   1u)	/* 0 0 1  延时/超时 			*/
#define  OS_TASK_STATE_PEND						(OS_STATE)(   2u)	/* 0 1 0  等待	 			*/
#define  OS_TASK_STATE_PEND_TIMEOUT				(OS_STATE)(   3u)	/* 0 1 1  等待+超时 			*/
#define  OS_TASK_STATE_SUSPENDED				(OS_STATE)(   4u)	/* 1 0 0  挂起 				*/
#define  OS_TASK_STATE_DLY_SUSPENDED			(OS_STATE)(   5u)	/* 1 0 1  挂起+延时/超时 	*/
#define  OS_TASK_STATE_PEND_SUSPENDED			(OS_STATE)(   6u)	/* 1 1 0  挂起+等待		 	*/
#define  OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED	(OS_STATE)(   7u)	/* 1 1 1  挂起+超时+等待 	*/
#define  OS_TASK_STATE_DEL						(OS_STATE)( 255u)	

2 修改和添加相关代码

2.1 修改 TCB(os.h)

TCB 中增加两个成员:

  • TaskState:标志任务的状态。
  • SuspendCtr:记录任务被挂起了几次。一个任务挂起多少次就要被恢复多少次才能重新运行。
struct os_tcb
	CPU_STK			*StkPtr;
	CPU_STK_SIZE	StkSize;
	
	OS_PRIO			Prio;				/* 任务优先级 */
	
	OS_TCB			*NextPtr;			/* 就绪列表双向链表的下一个指针 */
	OS_TCB			*PrevPtr;			/* 就绪列表双向链表的前一个指针 */
	
	OS_TCB			*TickNextPtr;		/* 指向链表的下一个 TCB 节点 */
	OS_TCB			*TickPrevPtr;		/* 指向链表的上一个 TCB 节点 */
	OS_TICK_SPOKE	*TickSpokePtr;		/* 用于回指到链表根部 */
	OS_TICK			TickCtrMatch;		/* 该值等于时基计数器 OSTickCtr 的值加上 TickRemain 的值 */
	OS_TICK			TickRemain;			/* 设置任务还需要等待多少个时钟周期 */
	
	OS_TICK			TimeQuanta;			/* 任务需要多少个时间片 */
	OS_TICK			TimeQuantaCtr;		/* 任务剩余的时间片个数 */
	
	OS_STATE		TaskState;			/* 表示任务的状态 */
	
#if OS_CFG_TASK_SUSPENDED_EN > 0u
	OS_NESTING_CTR	SuspendCtr;			/* 任务挂起函数 OSTaskSuspend() 计数器 */
#endif
;

2.2 添加宏定义和数据类型

在 os_cfg.h 中添加宏定义,用于使能任务挂起和删除功能,这两个功能可以开启也可以关闭:

/* 使能任务挂起功能 */
#define OS_CFG_TASK_SUSPENDED_EN          	1u

/* 使能任务删除功能 */
#define OS_CFG_TASK_DEL_EN					1u

在 os_type.h 中增加数据类型:

typedef   CPU_INT08U      OS_NESTING_CTR;

3 任务管理的函数

任务的挂起与恢复函数在很多时候都是很有用的,比如我们想暂停某个任务运行一段时间,但是我们又需要在其恢复的时候继续工作,那么删除任务是不可能的,因为删除了任务的话,任务的所有的信息都是不可能恢复的了,删除是完完全全删除了,里面的资源都被系统释放掉,但是挂起任务就不会这样。调用挂起任务函数,仅仅是将任务进入挂起态,其内部的资源都会保留下来,同时也不会参与系统中任务的调度,当调用恢复函数的时候,整个任务立即从挂起态进入就绪态,并且参与任务的调度,如果该任务的优先级是当前就绪态优先级最高的任务,那么立即会按照挂起前的任务状态继续执行该任务。也就是说,挂起任务之前是什么状态,都会被系统保留下来,在恢复的瞬间,继续执行。

删除任务是说任务将返回并处以删除(休眠)状态,任务的代码不再被 uCOS 调用,删除任务不是删除代码。删除任务和挂起任务有些相似,但最大的不同就是删除任务 TCB 的操作。我们知道在任务创建的时候,需要给每个任务分配一个 TCB,TCB 存储有关这个任务重要的信息,对任务间有至关重要的作用,挂起任务根本不会动 TCB,但删除任务就会把 TCB 进行初始化,这样关于任务的任何信息都被抹去。注意,删除任务并不会释放任务的栈空间。

以上所提及的三个函数,都属于 uCOS 的 API 函数,方便用户进行调用。

3.1 任务挂起函数 OSTaskSuspend()(os_task.c)

该函数用于将一个任务挂起,被挂起的任务就位于挂起态了,它的 TCB 将会被移出就绪列表,而且不会参与任何任务调度,除非有其他任务主动将这个任务恢复,即从挂起态转为就绪态,否则它没有机会获得运行权。

该函数完成的工作是:

  • 如果任务 TCB 是空的,则默认要挂起的任务是自己。
  • 如果任务挂起的是自己,则判断下调度器是否锁住,如果锁住则退出返回错误码,没有锁则继续往下执行。

接下来根据任务的不同状态,执行不同的操作:

  • 如果任务在就绪状态,则将任务的状态改为挂起态,挂起计数器置 1,然后从就绪列表删除。
  • 如果任务在延时状态,则将任务的状态改为延时加挂起态,挂起计数器置 1,不用改变 TCB 的位置,即还是在延时的时基列表。
  • 如果任务在等待状态,则将任务的状态改为等待加挂起态,挂起计数器置 1,不用改变 TCB 的位置,即还是在等待列表等待。等待列表目前仍未实现。
  • 如果任务在等待加超时态,则将任务的状态改为等待加超时加挂起态,挂起计数器置 1,不用改变 TCB 的位置,即还在等待和时基这两个列表中。等待列表目前仍未实现。
  • 如果任务处于挂起态,或者是挂起加其他态,则将挂起计数器加一操作,不用改变 TCB 的位置。
  • 其他状态则无效,退出返回状态无效错误码。
  • 最后,改变了任务状态后,需要进行任务切换。
/* 任务挂起函数 */
#if OS_CFG_TASK_SUSPENDED_EN > 0u
void OSTaskSuspend (OS_TCB *p_tcb, OS_ERR *p_err)

	CPU_SR_ALLOC();	
	CPU_CRITICAL_ENTER();
	
	if (p_tcb == (OS_TCB *)0)
	
		p_tcb = OSTCBCurPtr;
	
	
	if (p_tcb == OSTCBCurPtr)
	
		if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)	/* 如果调度器锁住则不能挂起自己 */
		
			CPU_CRITICAL_EXIT();
			*p_err = OS_ERR_SCHED_LOCKED;
			return;
		
	
	
	*p_err = OS_ERR_NONE;
	
	switch (p_tcb->TaskState)
	
		case OS_TASK_STATE_RDY:
			OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
			p_tcb->TaskState  = OS_TASK_STATE_SUSPENDED;
			p_tcb->SuspendCtr = (OS_NESTING_CTR)1;
			OS_RdyListRemove (p_tcb);
			OS_CRITICAL_EXIT_NO_SCHED();
			break;
		
		case OS_TASK_STATE_DLY:
			p_tcb->TaskState  = OS_TASK_STATE_DLY_SUSPENDED;
			p_tcb->SuspendCtr = (OS_NESTING_CTR)1;
			CPU_CRITICAL_EXIT();
			break;
		
		case OS_TASK_STATE_PEND:
			p_tcb->TaskState  = OS_TASK_STATE_PEND_SUSPENDED;
			p_tcb->SuspendCtr = (OS_NESTING_CTR)1;
			CPU_CRITICAL_EXIT();
			break;
		
		case OS_TASK_STATE_PEND_TIMEOUT:
			p_tcb->TaskState  = OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED;
			p_tcb->SuspendCtr = (OS_NESTING_CTR)1;
			CPU_CRITICAL_EXIT();
			break;
		
		case OS_TASK_STATE_SUSPENDED:
		case OS_TASK_STATE_DLY_SUSPENDED:
		case OS_TASK_STATE_PEND_SUSPENDED:
		case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
			p_tcb->SuspendCtr++;
			CPU_CRITICAL_EXIT();
			break;
		
		default:
			CPU_CRITICAL_EXIT();
			*p_err = OS_ERR_STATE_INVALID;
			return;
	
	
	OSSched();	/* 任务切换 */
	
	CPU_CRITICAL_EXIT();

#endif

3.2 任务恢复函数 OSTaskResume()(os_task.c)

该函数用于恢复一个处在挂起态的任务。比如,A 任务处在挂起加延时态,B 任务处在挂起加等待态,C 任务处于挂起加等待加延时态,那么当别的任务恢复它们后,A 任务处在延时态,B 任务处在等待态,C 任务处于等待加延时态。言下之意,就是把挂起态给去掉了。

需要注意的是,任务可以挂其自身,但不能恢复自身,因为自己都被挂起了,没有机会被运行了,又怎么能自己恢复自己呢!只有当别的任务进行恢复操作时,任务才能从挂起态恢复过来。

该函数根据任务的不同状态,执行不同的操作:

  • 只要任务没有处于挂起态的,退出返回任务没有被挂起的错误码。
  • 如果任务在挂起状态,则递减挂起计数器 SuspendCtr,如果 SuspendCtr 等于 0,则将任务的状态改为就绪态,并让任务就绪。
  • 如果任务在延时加挂起态,则递减挂起计数器 SuspendCtr,如果 SuspendCtr 等于 0,则将任务的状态改为延时态。
  • 如果任务在延时加等待态,则递减挂起计数器 SuspendCtr,如果 SuspendCtr 等于 0,则将任务的状态改为等待态。
  • 如果任务在等待加超时加挂起态,则递减挂起计数器 SuspendCtr,如果 SuspendCtr 等于 0,则将任务的状态改为等待加超时态。
  • 其他状态则无效,退出返回状态无效错误码。
  • 最后,改变了任务状态后,需要进行任务切换。
/* 任务恢复函数 */
#if OS_CFG_TASK_SUSPENDED_EN > 0u
void OSTaskResume (OS_TCB *p_tcb, OS_ERR *p_err)

	CPU_SR_ALLOC();
	CPU_CRITICAL_ENTER();
	
	*p_err = OS_ERR_NONE;
	
	switch (p_tcb->TaskState)
	
		case OS_TASK_STATE_RDY:
		case OS_TASK_STATE_DLY:
		case OS_TASK_STATE_PEND:
		case OS_TASK_STATE_PEND_TIMEOUT:
			CPU_CRITICAL_EXIT();
			*p_err = OS_ERR_TASK_NOT_SUSPENDED;
			break;
		
		case OS_TASK_STATE_SUSPENDED:
			OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
			p_tcb->SuspendCtr--;
			if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0)
			
				p_tcb->TaskState = OS_TASK_STATE_RDY;
				OS_TaskRdy (p_tcb);
			
			OS_CRITICAL_EXIT_NO_SCHED();
			break;
		
		case OS_TASK_STATE_DLY_SUSPENDED:
			p_tcb->SuspendCtr--;
			if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0)
			
				p_tcb->TaskState = OS_TASK_STATE_DLY;
			
			CPU_CRITICAL_EXIT();
			break;
			
		case OS_TASK_STATE_PEND_SUSPENDED:
			p_tcb->SuspendCtr--;
			if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0)
			
				p_tcb->TaskState = OS_TASK_STATE_PEND;
			
			CPU_CRITICAL_EXIT();
			break;
			
		case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
			p_tcb->SuspendCtr--;
			if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0)
			
				p_tcb->TaskState = OS_TASK_STATE_PEND_TIMEOUT;
			
			CPU_CRITICAL_EXIT();
			break;
			
		default:
			CPU_CRITICAL_EXIT();
			*p_err = OS_ERR_STATE_INVALID;
			return;
	
	
	OSSched();	/* 任务切换 */
	
	CPU_CRITICAL_EXIT();

#endif

3.3 任务删除函数 OSTaskDel()(os_task.c)

任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用 uCOS 中的任务删除 API 函数接口显式地将其删除。

该函数用于将任务删除。所谓删除,就是将被删除的任务 TCB 移出就绪列表,同时清空该任务 TCB 的所有信息。

该函数完成的工作有:

  • 如果发现删除的是空闲任务,则返回错误码,因为空闲任务不能被删除,系统必须至少有一个任务在运行,当没有其他用户任务运行的时候,系统就会运行空闲任务。
  • 如果任务 TCB 是空的,则默认要删除的任务是自己。

然后根据任务的不同状态,执行不同的操作:

  • 如果任务处于就绪态,则从就绪列表移除。
  • 如果任务处于挂起态,则什么都不用做。
  • 如果任务在延时态或者是延时加挂起态,则从时基列表移除。
  • 如果任务在等待态或是等待加其他态,则从时基列表和等待列表移除。等待列表目前仍未实现。
  • 其他状态则无效,退出返回状态无效错误码。

最后:

  • 清空 TCB 至默认值。
  • 修改任务的状态为删除态,即处于休眠。
  • 进行任务调度。
/* 任务删除函数 */
#if OS_CFG_TASK_DEL_EN > 0u
void OSTaskDel (OS_TCB *p_tcb, OS_ERR *p_err)

	CPU_SR_ALLOC();
	
	if (p_tcb == &OSIdleTaskTCB)	/* 不能删除空闲任务 */
	
		*p_err = OS_ERR_TASK_DEL_IDLE;
		return;
	
	
	if (p_tcb == (OS_TCB *)0)
	
		CPU_CRITICAL_ENTER();
		p_tcb = OSTCBCurPtr;
		CPU_CRITICAL_EXIT();
	
	
	OS_CRITICAL_ENTER();
	
	switch (p_tcb->TaskState)
	
		case OS_TASK_STATE_RDY:
			OS_RdyListRemove (p_tcb);
			break;
		
		case OS_TASK_STATE_SUSPENDED:
			break;
		
		case OS_TASK_STATE_DLY:
		case OS_TASK_STATE_DLY_SUSPENDED:
			OS_TickListRemove (p_tcb);
			break;
		
		case OS_TASK_STATE_PEND:
		case OS_TASK_STATE_PEND_TIMEOUT:
		case OS_TASK_STATE_PEND_SUSPENDED:
		case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
			OS_TickListRemove (p_tcb);
		
		default:
			OS_CRITICAL_EXIT();
			*p_err = OS_ERR_STATE_INVALID;
			return;
	
	
	OS_TaskInitTCB (p_tcb);
	p_tcb->TaskState = OS_TASK_STATE_DEL;
	
	OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
	
	OSSched();	/* 任务切换 */
	
	*p_err = OS_ERR_NONE;

#endif

4 任务管理的应用

4.1 主函数 main()(app.c)

修改两个任务:

  • Task1:两次的阻塞延时改为两次的挂起任务自身。
  • Task2:增加恢复任务 Task1。
  • Task3 不用修改。
#include "ARMCM3.h"
#include "os.h"

#define  TASK1_STK_SIZE       128
#define  TASK2_STK_SIZE       128
#define  TASK3_STK_SIZE       128

static   CPU_STK   Task1Stk[TASK1_STK_SIZE];
static   CPU_STK   Task2Stk[TASK2_STK_SIZE];
static   CPU_STK   Task3Stk[TASK3_STK_SIZE];

static   OS_TCB    Task1TCB;
static   OS_TCB    Task2TCB;
static   OS_TCB    Task3TCB;

uint32_t flag1;
uint32_t flag2;
uint32_t flag3;

void Task1 (void *p_arg);
void Task2 (void *p_arg);
void Task3 (void *p_arg);

/* 软件延时 */
void delay(uint32_t count);

int main (void)

	OS_ERR err;
	
	/* 初始化相关的全局变量,创建空闲任务 */
	OSInit(&err);
	
	/* CPU 初始化:初始化时间戳 */
	CPU_Init();
	
	/* 关中断,因为此时 OS 未启动,若开启中断,那么 SysTick 将会引发中断 */
	CPU_IntDis();
	
	/* 初始化 SysTick,配置 SysTick 为 10ms 中断一次,Tick = 10ms */
	OS_CPU_SysTickInit(10);
	
	/* 创建任务 */
	OSTaskCreate ((OS_TCB*)      &Task1TCB, 
	              (OS_TASK_PTR)  Task1, 
	              (void *)       0,
				  (OS_PRIO)		 1,
	              (CPU_STK*)     &Task1Stk[0],
	              (CPU_STK_SIZE) TASK1_STK_SIZE,
				  (OS_TICK)		 0,
	              (OS_ERR *)     &err);

	OSTaskCreate ((OS_TCB*)      &Task2TCB, 
	              (OS_TASK_PTR)  Task2, 
	              (void *)       0,
				  (OS_PRIO)		 2,
	              (CPU_STK*)     &Task2Stk[0],
	              (CPU_STK_SIZE) TASK2_STK_SIZE,
				  (OS_TICK)		 0,
	              (OS_ERR *)     &err);
				  
	OSTaskCreate ((OS_TCB*)      &Task3TCB, 
	              (OS_TASK_PTR)  Task3, 
	              (void *)       0,
				  (OS_PRIO)		 3,
	              (CPU_STK*)     &Task3Stk[0],
	              (CPU_STK_SIZE) TASK3_STK_SIZE,
				  (OS_TICK)		 0,
	              (OS_ERR *)     &err);
	
	/* 启动OS,将不再返回 */				
	OSStart(&err);


/* 软件延时 */
void delay (uint32_t count)

	for(; count!=0; count--);



void Task1 (void *p_arg)

	OS_ERR	err;
	
	for (;;)
	
		flag1 = 1;
		OSTaskSuspend (&Task1TCB, &err);
		flag1 = 0;
		OSTaskSuspend (&Task1TCB, &err);
	


void Task2 (void *p_arg)

	OS_ERR	err;
	
	for (;;)
	
		flag2 = 1;
		OSTimeDly (2);		
		flag2 = 0;
		OSTimeDly (2);
		OSTaskResume (&Task1TCB, &err);
	


void Task3 (void *p_arg)

	for (;;)
	
		flag3 = 1;
		OSTimeDly (2);		
		flag3 = 0;
		OSTimeDly (2);
	

4.2 运行过程

4.2.1 在主函数中

  • 系统初始化:初始化各种全局变量,初始化优先级表,初始化就绪列表,初始化时基列表,初始化空闲任务(包括初始化空闲任务栈和空闲任务 TCB)。
  • CPU 初始化:暂为空。
  • 关中断:因为此时 OS 未启动,若开启中断,那么 SysTick 将会引发中断,打断初始化流程。
  • 初始化 SysTick:配置 SysTick 为 10ms 中断一次,Tick = 10ms。
  • 创建任务:包括创建任务栈和任务 TCB,以及将 TCB 插入到就绪列表中,在优先级表对应位置置位。
  • 启动系统:先找到最高优先级,然后开始运行最高优先级对应的任务(最高优先级为 1,即为 Task1),启动第一次任务切换(此时将完成最后的初始化流程,即有关 PendSV 的中断优先级配置,接着触发 PendSV 异常,发起任务切换),将 CPU 占有权交给任务 Task1。

4.2.2 第一次在 Task1 中

  • flag1 = 1。
  • 执行到任务挂起函数 OSTaskSuspend:挂起自身任务,挂起计数器加一,移出就绪列表,进行任务调度。
  • 执行任务调度 OSSched:任务调度器先找到最高优先级,然后再找到最高优先级的任务。 TCB。如果发现该任务就是当前任务,则不进行任务切换。在本案例中发现最高优先级为 2,对应任务是 Task2,不是当前任务,则发起任务切换(发起 PendSV 异常)。
  • PendSV 异常处理程序:保存 Task1 的状态,加载 Task2 的状态,更新全局变量的值。

uCOS-III 学习记录——任务时间片运行

uCOS-III 学习记录——支持多优先级

uCOS-III 学习记录(10)——时间片轮转调度

uCOS-III 学习记录——优先级表

uCOS-III 学习记录——临界段

uCOS-III 学习记录——时基列表