1. 程式人生 > 其它 >uCOS-III 學習記錄(11)——任務管理

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 的狀態,更新全域性變數的值。

4.2.3 第一次在 Task2 中

  • flag2 = 1。
  • 執行到阻塞函式 OSTimeDly:將 Task2 的 TCB 插入到時基列表中(TickCtrMatch = 2),將就緒列表中的 TCB 移除(同時在優先順序表中的相應位置,即優先順序 2 的位置清零),然後啟動任務排程。
  • 執行任務排程 OSSched:任務排程器先找到最高優先順序,然後再找到最高優先順序的任務 TCB。如果發現該任務就是當前任務,則不進行任務切換。在本案例中發現最高優先順序為 3,對應任務是 Task3,不是當前任務,則發起任務切換(發起 PendSV 異常)。
  • PendSV 異常處理程式:儲存 Task2 的狀態,載入 Task3 的狀態,更新全域性變數的值。

4.2.4 第一次在 Task3 中

  • flag3 = 1。
  • 執行到阻塞函式 OSTimeDly:將 Task3 的 TCB 插入到時基列表中(TickCtrMatch = 2),將就緒列表中的 TCB 移除(同時在優先順序表中的相應位置,即優先順序 3 的位置清零),然後啟動任務排程。
  • 執行任務排程 OSSched:任務排程器先找到最高優先順序,然後再找到最高優先順序的任務 TCB。如果發現該任務就是當前任務,則不進行任務切換。在本案例中發現最高優先順序為 31,對應任務是 空閒任務,不是當前任務,則發起任務切換(發起 PendSV 異常)。
  • PendSV 異常處理程式:儲存 Task3 的狀態,載入空閒任務的狀態,更新全域性變數的值。

4.2.5 在空閒任務中 SysTick 發起中斷

  • 執行 OSTimeTick:時基計數器加一(OSTickCtr = 1),檢查時基列表(OSCfg_TickWheel[1]),發現各個任務的延時未到期(TickCtrMatch = 2)。最後發起任務排程,發現不用進行任務切換,空閒任務繼續執行。
  • 再次發起中斷,執行 OSTimeTick:時基計數器加一(OSTickCtr = 2),檢查時基列表(OSCfg_TickWheel[2]),發現各個任務的延時已到期(TickCtrMatch = 2),將它們全部置為就緒態。置為就緒態的過程是:在時基列表中刪除對應 TCB,在就緒列表中加入對應 TCB(同時將 Task2、Task3 的優先順序在優先順序表中的相應位置重新置位)。最後發起任務排程,發現最高優先順序為 2,對應的是 Task2,切換到 Task2 執行。

4.2.6 第二次在 Task2 中

  • 將全域性變數 flag2 由 1 變成 0。
  • 執行到阻塞函式 OSTimeDly:將 Task2 的 TCB 插入到時基列表中(TickCtrMatch = 4),將就緒列表中的 TCB 移除(同時在優先順序表中的相應位置,即優先順序 1 的位置清零),然後啟動任務排程。
  • 執行任務排程 OSSched:任務排程器先找到最高優先順序,然後再找到最高優先順序的任務 TCB。如果發現該任務就是當前任務,則不進行任務切換。在本案例中發現最高優先順序為 3,對應任務是 Task3,不是當前任務,則發起任務切換(發起 PendSV 異常)。
  • PendSV 異常處理程式:儲存 Task2 的狀態,載入 Task3 的狀態,更新全域性變數的值。

4.2.7 第二次在 Task3 中

  • 將全域性變數 flag3 由 1 變成 0。
  • 執行到阻塞函式 OSTimeDly:將 Task3 的 TCB 插入到時基列表中(TickCtrMatch = 4),將就緒列表中的 TCB 移除(同時在優先順序表中的相應位置,即優先順序 1 的位置清零),然後啟動任務排程。
  • 執行任務排程 OSSched:任務排程器先找到最高優先順序,然後再找到最高優先順序的任務 TCB。如果發現該任務就是當前任務,則不進行任務切換。在本案例中發現最高優先順序為 31,對應任務是 空閒任務,不是當前任務,則發起任務切換(發起 PendSV 異常)。
  • PendSV 異常處理程式:儲存 Task3 的狀態,載入空閒任務的狀態,更新全域性變數的值。

4.2.8 在空閒任務中 SysTick 發起中斷

  • 執行 OSTimeTick:時基計數器加一(OSTickCtr = 3),檢查時基列表(OSCfg_TickWheel[3]),發現各個任務的延時未到期(TickCtrMatch = 4)。最後發起任務排程,發現不用進行任務切換,空閒任務繼續執行。
  • 再次發起中斷,執行 OSTimeTick:時基計數器加一(OSTickCtr = 4),檢查時基列表(OSCfg_TickWheel[4]),發現各個任務的延時已到期(TickCtrMatch = 4),將它們全部置為就緒態。置為就緒態的過程是:在時基列表中刪除對應 TCB,在就緒列表中加入對應 TCB(同時將 Task2、Task3 的優先順序在優先順序表中的相應位置重新置位)。最後發起任務排程,發現最高優先順序為 2,對應的是 Task2,切換到 Task2 執行。

4.2.9 第三次在 Task2 中

  • 執行 OSTaskResume:恢復 Task1,使 Task1 的掛起計數器減一,將 Task1 的 TCB 加入到就緒列表中。注意,由於 TCB 不存在於時基列表中,因此不用刪除。之後發起任務排程,發現最高優先順序為 1,對應的是 Task1,切換到 Task1 執行。

4.2.10 第二次在 Task1 中

  • 將全域性變數 flag1 由 1 變成 0。
  • 執行到任務掛起函式 OSTaskSuspend:掛起自身任務,掛起計數器加一,移出就緒列表,進行任務排程。
  • 執行任務排程 OSSched:任務排程器先找到最高優先順序,然後再找到最高優先順序的任務。 TCB。如果發現該任務就是當前任務,則不進行任務切換。在本案例中發現最高優先順序為 2,對應任務是 Task2,不是當前任務,則發起任務切換(發起 PendSV 異常)。
  • PendSV 異常處理程式:儲存 Task1 的狀態,載入 Task2 的狀態,更新全域性變數的值。

如此反覆,不再贅述。

4.3 實驗現象

實驗現象如下圖所示,符合以上分析: