1. 程式人生 > 其它 >uCOS-III 學習記錄(8)——支援多優先順序

uCOS-III 學習記錄(8)——支援多優先順序

參考內容:《[野火]uCOS-III核心實現與應用開發實戰指南——基於STM32》第 12 章。

目錄

本篇內容主要是對過往函式的一些修改,因此,一些細節將不會贅述。

0 資料型別定義和巨集定義

0.1 臨界段巨集定義(os.h)

#define  OS_CRITICAL_ENTER()                    CPU_CRITICAL_ENTER()
#define  OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT()
#define  OS_CRITICAL_EXIT()                     CPU_CRITICAL_EXIT()
#define  OS_CRITICAL_EXIT_NO_SCHED()            CPU_CRITICAL_EXIT()

0.2 任務控制塊 TCB 定義(os.h)

型別定義:

/*----------------------TCB---------------------------*/
/* TCB 重新命名為大寫字母格式 */
typedef struct os_tcb	OS_TCB;

/* TCB 資料型別宣告 */
struct os_tcb{
	CPU_STK			*StkPtr;
	CPU_STK_SIZE	StkSize;
	
	OS_TICK			TaskDelayTicks;		/* 任務延時週期個數 */
	
	OS_PRIO			Prio;				/* 任務優先順序(8位整型,最多支援255個優先順序) */
	
	OS_TCB			*NextPtr;			/* 就緒列表雙向連結串列的下一個指標 */
	OS_TCB			*PrevPtr;			/* 就緒列表雙向連結串列的前一個指標 */
};

全域性變數定義:

OS_EXT 	OS_TCB			*OSTCBCurPtr;
OS_EXT	OS_TCB			*OSTCBHighRdyPtr;

0.3 任務就緒列表定義(os.h)

型別定義:

/*---------------------OS_RDY_LIST----------------------------*/
/* 就緒列表重新命名為大寫字母格式 */
typedef struct os_rdy_list	OS_RDY_LIST;

/* 就緒列表資料型別宣告,將 TCB 串成雙向連結串列 */
struct os_rdy_list{
	OS_TCB		*HeadPtr;
	OS_TCB		*TailPtr;
	OS_OBJ_QTY	NbrEntries;		/* 同一個索引下有多少個任務 */
};

全域性變數定義:

OS_EXT	OS_RDY_LIST		OSRdyList[OS_CFG_PRIO_MAX];

0.4 優先順序相關變數定義(os.h)

OS_EXT	OS_PRIO			OSPrioCur;		/* 當前優先順序 */
OS_EXT	OS_PRIO			OSPrioHighRdy;	/* 最高優先順序 */

巨集定義:

#define  OS_PRIO_INIT           (OS_CFG_PRIO_MAX)

1 系統初始化 OSInit()(os_core.c)

該函式用於系統的初始化,說白了就是初始化各種全域性變數的地方。該函式完成的工作有:

  • 初始化 TCB 相關的全域性變數。
  • 初始化優先順序相關的全域性變數。
  • 初始化優先順序表。
  • 初始化就緒列表。
  • 初始化空閒任務。
/* OS 系統初始化,用於初始化全域性變數 */
void OSInit (OS_ERR *p_err)
{
	/* 系統用一個全域性變數 OSRunning 來指示系統的執行狀態。系統初始化時,預設為停止狀態,即 OS_STATE_OS_STOPPED */
	OSRunning = OS_STATE_OS_STOPPED;
	
	OSTCBCurPtr 	= (OS_TCB *) 0; /* 指向當前正在執行的任務的 TCB 指標 */
	OSTCBHighRdyPtr = (OS_TCB *) 0; /* 指向就緒任務中優先順序最高的任務的 TCB */
	
	OSPrioCur 		= (OS_PRIO)0;	/* 初始化當前優先順序 */
	OSPrioHighRdy 	= (OS_PRIO)0;	/* 初始化最高優先順序 */
	
	OS_PrioInit();		/* 初始化優先順序表 */
	
	OS_RdyListInit();  /* 初始化就緒列表 */
	
	OS_IdleTaskInit(p_err); /* 初始化空閒任務 */
	
	if (*p_err != OS_ERR_NONE) {
		return;
	}
}

2 任務建立函式 OSTaskCreate()(os_task.c)

該函式完成的工作有:

  • 初始化任務 TCB,將 TCB 初始化為預設值。
  • 初始化任務棧。
  • 在任務 TCB 中記錄任務的優先順序。
  • 在任務 TCB 中記錄棧頂指標。
  • 在任務 TCB 中記錄棧的大小。
  • 在優先順序表中將對應的優先順序位置置 1。
  • 將任務 TCB 加入就緒列表中。即:將任務 TCB 放到 OSRdyList[優先順序] 中,如果同一個優先順序有多個任務,那麼這些任務的 TCB 就會被放到 OSRdyList[優先順序] 串成一個雙向連結串列。

注意:

  • 以上工作位於臨界段內。
/* 任務建立函式 */
void OSTaskCreate( 	OS_TCB 			*p_tcb,  		/* TCB指標 */
					OS_TASK_PTR 	p_task,  		/* 任務函式名 */
					void 			*p_arg,  		/* 任務的形參 */
					OS_PRIO			prio,			/* 任務優先順序 */
					CPU_STK 		*p_stk_base, 	/* 任務棧的起始地址 */
					CPU_STK_SIZE 	stk_size,		/* 任務棧大小 */
					OS_ERR 			*p_err )		/* 錯誤碼 */
{
	CPU_STK		*p_sp;
	CPU_SR_ALLOC();
	
	OS_TaskInitTCB (p_tcb);
	
	p_sp = OSTaskStkInit ( 	p_task,
							p_arg,
							p_stk_base,
							stk_size );  /* 任務棧初始化函式 */
	p_tcb->Prio		= prio;		/* 任務優先順序儲存在 TCB 的 prio 中*/
	p_tcb->StkPtr 	= p_sp;    	/* 剩餘棧的棧頂指標 p_sp 儲存到任務控制塊 TCB 的 StkPtr 中 */
	p_tcb->StkSize 	= stk_size; /* 將任務棧的大小儲存到任務控制塊 TCB 的成員 StkSize 中 */
	
	OS_CRITICAL_ENTER();	/* 進入臨界段 */
	
	/* 將任務新增到就緒列表 */
	OS_PrioInsert (p_tcb->Prio);
	OS_RdyListInsertTail (p_tcb);
	
	OS_CRITICAL_EXIT();		/* 退出臨界段 */
	
	*p_err = OS_ERR_NONE;		/* 函式執行到這裡表示沒有錯誤 */
}

2.1 初始化任務控制塊 OS_TaskInitTCB()(os_task.c)

該函式完成的工作是將任務 TCB 的每一個成員都賦值為預設值。其中 OS_PRIO_INIT 是任務 TCB 初始化的時候給的預設的一個優先順序,
巨集展開等於 OS_CFG_PRIO_MAX,這是一個不會被 OS 使用到的優先順序。

/* 初始化任務 TCB */
void OS_TaskInitTCB (OS_TCB *p_tcb)
{
	p_tcb->StkPtr 			= (CPU_STK   *)	0;
	p_tcb->StkSize 			= (CPU_STK_SIZE)0;
	
	p_tcb->TaskDelayTicks 	= (OS_TICK    )	0;		
	
	p_tcb->Prio 			= (OS_PRIO    )	OS_PRIO_INIT;				
	
	p_tcb->NextPtr 			= (OS_TCB    *)	0;			
	p_tcb->PrevPtr 			= (OS_TCB    *)	0;			
}

3 空閒任務初始化 OS_IdleTaskInit()(os_core.c)

該函式為空閒任務建立了一個 TCB,並初始化了空閒任務的棧。注意,空閒任務的優先順序是最低的,等於 (OS_CFG_PRIO_MAX - 1u),這意味著在系統沒有任何使用者任務執行的情況下,空閒任務就會被執行。

/* 空閒任務初始化函式 */ 
void OS_IdleTaskInit (OS_ERR *p_err)
{
	OSIdleTaskCtr = (OS_IDLE_CTR) 0;		/* 計數器清零 */
	
	OSTaskCreate ((OS_TCB*)      &OSIdleTaskTCB, 
	              (OS_TASK_PTR)  OS_IdleTask, 
	              (void *)       0,
				  (OS_PRIO)		(OS_CFG_PRIO_MAX - 1u),
	              (CPU_STK *)    OSCfg_IdleTaskStkBasePtr,
	              (CPU_STK_SIZE) OSCfg_IdleTaskStkSize,
	              (OS_ERR *)     &p_err);		/* 建立空閒任務 */
}

4 系統啟動 OSStart()(os_core.c)

該函式完成的工作有:

  • 找到優先順序表的最高優先順序,並將其賦值給 OSPrioHighRdy,再賦值給 OSPrioCur。
  • 根據最高優先順序,找到對應的任務 TCB 連結串列,將其頭指標賦值給 OSTCBHighRdyPtr,再賦值給 OSTCBCurPtr。
  • 啟動任務切換。
/* 系統啟動函式 */
void OSStart (OS_ERR *p_err)
{
	if ( OSRunning == OS_STATE_OS_STOPPED )
	{
		OSPrioHighRdy = OS_PrioGetHighest();
		OSPrioCur = OSPrioHighRdy;
		
		OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; 
		OSTCBCurPtr = OSTCBHighRdyPtr;
		
		OSStartHighRdy(); 						/* 啟動任務切換,不會返回 */
		*p_err = OS_ERR_FATAL_RETURN;			/* 執行至此處,說明發生了致命錯誤 */
	}
	else{
		*p_err = OS_STATE_OS_RUNNING;
	}
}

5 可懸起系統呼叫中斷服務程式 PendSV_Handler()(os_cpu_a.s)

該函式完成的工作有:

  • 儲存舊任務的暫存器狀態。
  • 使 OSPrioCur = OSPrioHighRdy。
  • 使 OSTCBCurPtr = OSTCBHighRdyPtr。
  • 恢復新任務的暫存器狀態。
;**********PendSVHandler異常**********
PendSV_Handler PROC
	
	CPSID	I					; 關中斷,防止上下文切換

	MRS		R0, PSP				; 將 PSP 載入到 R0,MRS 是 ARM 32 位資料載入指令,
								; 功能是載入特殊功能暫存器的值到通用暫存器
	CBZ		R0, OS_CPU_PendSVHandler_nosave ; 判斷 R0,如果值為 0 則跳轉到 OS_CPU_PendSVHandler_nosave
	                                        ; 進行第一次任務切換的時候,R0 肯定為 0
	
	STMDB	R0!, {R4-R11}		; 手動儲存 R4-R11 暫存器到當前任務棧中,而其他暫存器會被 CPU 自動入棧
	LDR		R1, = OSTCBCurPtr	; 將 OSTCBCurPtr 指標的地址載入到 R1
	LDR		R1, [R1]			; 將 OSTCBCurPtr 指標載入到 R1
	STR		R0, [R1]			; 儲存 R0(任務棧棧頂)的值到 OSTCBCurPtr(->StkPtr) 

OS_CPU_PendSVHandler_nosave

	; 使 OSPrioCur = OSPrioHighRdy
	LDR		R0, = OSPrioCur		; 將 OSPrioCur 指標的地址載入到 R0
	LDR		R1, = OSPrioHighRdy	; 將 OSPrioHighRdy 指標的地址載入到 R1
	LDR		R2, [R1]			; 將 OSPrioCur 指標載入到 R2
	STR		R2, [R0]			; 將 OSPrioHighRdy(R2)存到 OSPrioCur(R0)

	; 使 OSTCBCurPtr = OSTCBHighRdyPtr 
	LDR		R0, = OSTCBCurPtr		; 將 OSTCBCurPtr 指標的地址載入到 R0
	LDR		R1, = OSTCBHighRdyPtr	; 將 OSTCBHighRdyPtr 指標的地址載入到 R1
	LDR		R2, [R1]			; 將 OSTCBCurPtr 指標載入到 R2
	STR		R2, [R0]			; 將 OSTCBHighRdyPtr(R2)存到 OSTCBCurPtr(R0)
	
	LDR     R0, [R2]            ; 載入 OSTCBHighRdyPtr(->StkPtr) 到 R0
	LDMIA   R0!, {R4-R11}       ; 載入需要手動儲存的資訊到 CPU 暫存器 R4-R11,其他暫存器將在返回後由 CPU 自動裝載
	
	MSR     PSP, R0             ; 更新PSP的值,這個時候PSP指向下一個要執行的任務的堆疊的棧底(這個棧底已經加上剛剛手動載入到CPU暫存器R4-R11的偏移)
	ORR     LR, LR, #0x04       ; 確保異常返回使用的堆疊指標是PSP,即LR暫存器的位2要為1
	CPSIE   I                   ; 開中斷
	BX      LR                  ; 異常返回,這個時候任務堆疊中的剩下內容將會自動載入到xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參)
	                            ; 同時PSP的值也將更新,即指向任務堆疊的棧頂。在STM32中,堆疊是由高地址向低地址生長的。
	
	NOP                         ; 為了彙編指令對齊,不然會有警告
	
	
	ENDP   

6 阻塞延時 OSTimeDly()(os_time.c)

任務呼叫 OSTimeDly() 函式之後,任務就處於阻塞態,需要將任務從就緒列表中移除(此處未實現)。因此需要完成的工作有:

  • 任務 TCB 記錄好延時時間。
  • 在優先順序表中清除相應的位,達到任務不處於就緒態的目的。

注意:

  • 以上工作位於臨界段內。
/* 阻塞延時 */
void OSTimeDly (OS_TICK dly)
{
	CPU_SR_ALLOC();
	OS_CRITICAL_ENTER();	/* 進入臨界段 */
	
	/* 延時時間 */
	OSTCBCurPtr->TaskDelayTicks = dly;
	OS_PrioRemove (OSTCBCurPtr->Prio);
	
	OS_CRITICAL_EXIT();		/* 退出臨界段 */
	
	/* 任務切換 */
	OSSched();
}

7 任務切換 OSSched()(os_core.c)

任務排程函式根據優先順序進行排程。具體完成的工作如下:

  • 查詢最高優先順序。
  • 如果找到的最高優先順序是當前任務,則直接返回,不進行任務切換,否則進行任務切換。

注意:

  • 以上工作位於臨界段內。
/* 任務排程 */
void OSSched (void)
{	
	CPU_SR_ALLOC();
	OS_CRITICAL_ENTER();	/* 進入臨界段 */
	
	OSPrioHighRdy = OS_PrioGetHighest();	
	OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; 
	
	/* 如果最高優先順序的任務是當前任務則直接返回,不進行任務切換 */
	if (OSTCBHighRdyPtr == OSTCBCurPtr)	
	{
		OS_CRITICAL_EXIT();	/* 退出臨界段 */
		return;
	}
	
	OS_CRITICAL_EXIT();		/* 退出臨界段 */
	OS_TASK_SW();			/* 任務切換   */
}

8 SysTick 發起中斷後呼叫 OSTimeTick()(os_time.c)

該函式完成的工作有:

  • 遍歷整個就緒列表,發現有任務在延時,將其延時時間減一。
  • 如果減一後發現任務已經延時結束了,將任務從阻塞態變為就緒態,即在優先順序表中的相應位置置位。

注意:

  • 以上工作位於臨界段內。
void OSTimeTick (void)
{
	OS_PRIO i;
	
	CPU_SR_ALLOC();
	OS_CRITICAL_ENTER();	/* 進入臨界段 */
	
	/* 遍歷整個就緒列表 */
	for ( i = 0u; i < OS_CFG_PRIO_MAX; i++)
	{
		if ( OSRdyList[i].HeadPtr->TaskDelayTicks > 0u )	/* 如果延時未到時,則減 1 */
		{
			OSRdyList[i].HeadPtr->TaskDelayTicks --;
			if (OSRdyList[i].HeadPtr->TaskDelayTicks == 0u)	/* 如果延時時間已到,讓任務就緒 */
			{
				OS_PrioInsert (i);
			}
		}
	}
	
	OS_CRITICAL_EXIT();		/* 退出臨界段 */
	
	/* 任務排程 */
	OSSched();  
}

9 將之前所新增的內容進行運用

9.1 主函式 main()(app.c)

在 app.c 中,我們添加了 3 個任務。注意:

  • 要將任務棧的大小設定得大些,不然可能不夠用。
  • 3 個任務的優先順序分別是 1、2、3,空閒任務佔據了最後一個優先順序,0 優先順序不能使用。
#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);

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_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_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_ERR *)     &err);
	
	/* 啟動OS,將不再返回 */				
	OSStart(&err);
}

void Task1 (void *p_arg)
{
	for (;;)
	{
		flag1 = 1;
		OSTimeDly (2);	
		flag1 = 0;
		OSTimeDly (2);
	}
}

void Task2 (void *p_arg)
{
	for (;;)
	{
		flag2 = 1;
		OSTimeDly (2);		
		flag2 = 0;
		OSTimeDly (2);
	}
}

void Task3 (void *p_arg)
{
	for (;;)
	{
		flag3 = 1;
		OSTimeDly (2);		
		flag3 = 0;
		OSTimeDly (2);
	}
}

現在我們來複原一下整個執行過程。

9.2 執行過程

9.2.1 在主函式中

  • 系統初始化:初始化各種全域性變數,初始化優先順序表,初始化就緒列表,初始化空閒任務(包括初始化空閒任務棧和空閒任務 TCB)。
  • CPU 初始化:暫為空。
  • 關中斷:因為此時 OS 未啟動,若開啟中斷,那麼 SysTick 將會引發中斷,打斷初始化流程。
  • 初始化 SysTick:配置 SysTick 為 10ms 中斷一次,Tick = 10ms。
  • 建立任務:包括建立任務棧和任務 TCB,以及將 TCB 插入到就緒列表中,在優先順序表對應位置置位。
  • 啟動系統:先找到最高優先順序,然後開始執行最高優先順序對應的任務(最高優先順序為 1,即為 Task1),啟動第一次任務切換(此時將完成最後的初始化流程,即有關 PendSV 的中斷優先順序配置,接著觸發 PendSV 異常,發起任務切換),將 CPU 佔有權交給任務 Task1。

9.2.2 在 Task1 中

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

9.2.3 在 Task2 中

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

9.2.4 在 Task3 中

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

請注意,每次任務切換的時間其實非常短,大約只有不到 0.1ms 的長度,不會對 2ms 的延時造成很大的影響。

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

  • 執行 OSTimeTick:遍歷整個就緒列表,發現各個任務的延時未到時(此時為 2),全部減一;減一後(此時為 1)發現延時也未到時,說明任務還未到進入就緒態的時候。最後發起任務排程,發現不用進行任務切換,空閒任務繼續執行。
  • 再次發起中斷,執行 OSTimeTick:遍歷整個就緒列表,發現各個任務的延時未到時(此時為 1),全部減一;減一後(此時為 0)發現延時已到時,說明任務進入就緒態了。將 Task1、Task2、Task3 的優先順序在優先順序表中的相應位置重新置位。最後發起任務排程,發現最高優先順序為 1,對應的是 Task1,切換到 Task1 執行。

9.2.6 又在 Task1 中

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

如此反覆,不再贅述。

9.3 實驗現象

這是執行的模擬結果,實驗現象符合以上分析,三個任務同時執行,就像多執行緒一樣:

看起來三個任務同步執行,效率很高。不過放大後發現,三個任務的上升時間並不是嚴格同時的,而是略有先後:先是 Task1 上升,然後是 Task2,最後是 Task3,這中間的時間差就是任務切換的花費時間,大約是 0.1ms。

同理,三個任務的下降時間也是略有先後的:先是 Task1 下降,然後是 Task2,最後是 Task3,這中間的時間差也是任務切換的花費時間,大約是 0.1ms。

嘗試:

  • 如果將 Task1、Task2、Task3 的優先順序分別改為 3、2、1,那麼實驗現象是:先是 Task3 上升,然後是 Task2,最後是 Task1,對於下降同理。因此,優先順序影響著任務執行的先後順序:先執行 Task3,然後切換到 Task2,最後切換到 Task1。
  • 如果將 Task3 的優先順序改為 2,那麼 Task3 將不會被執行,因為優先順序為 2 的任務有兩個,目前只能執行其中一個(位於頭指標的任務 TCB),我們還未實現任務輪轉的功能。