uCOS-III 學習記錄(1)——任務的創建、切換和OS的啟動


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

前排提醒

  • 每一節標題最后的括號是表明該數據類型或函數位於哪個文件中。
  • 按照 μC/OS-III 中的函數命名規則,以大小的 OS 開頭,表示這是一個外部函數,可以由用戶調用,以 OS_ 開頭的函數表示內部函數,只能由 μC/OS-III 內部使用。緊接着是文件名,表示該函數放在哪個文件,最后是函數功能名稱。例如:OSTaskCreate,說明允許用戶調用,位於 os_task.c 文件中。
  • extern 的巧妙定義 (os.h):
#ifdef 	OS_GLOBALS
	#define OS_EXT
#else
	#define OS_EXT	extern
#endif
  • 在 cpu.h 和 os_type.h 中:
#ifndef CPU_H
#define CPU_H

typedef unsigned short  CPU_INT16U;
typedef unsigned int  	CPU_INT32U;
typedef unsigned char 	CPU_INT08U;

typedef CPU_INT32U  	CPU_ADDR;

/* 堆棧數據類型重定義 */
typedef CPU_INT32U		CPU_STK;
typedef CPU_ADDR		CPU_STK_SIZE;

typedef volatile CPU_INT32U		CPU_REG32;

#endif /* CPU_H */

/************************************************/

#ifndef OS_TYPE_H
#define OS_TYPE_H

#include "cpu.h"

typedef   CPU_INT16U      OS_OBJ_QTY;
typedef   CPU_INT08U      OS_PRIO;
typedef   CPU_INT08U      OS_STATE;

#endif  /* OS_TYPE_H */

0 數據類型聲明

0.1 任務控制塊(OS_TCB)(os.h)

  • 任務控制塊(TCB):用於記錄棧頂指針和棧的大小。
/* TCB 重命名為大寫字母格式 */
typedef struct os_tcb	OS_TCB;

/* TCB 數據類型聲明 */
struct os_tcb{
	CPU_STK			*StkPtr;
	CPU_STK_SIZE	StkSize;
};
  • OSTCBCurPtr:全局變量定義,用於記錄當前正在運行的任務。
OS_EXT 	OS_TCB			*OSTCBCurPtr;
  • OSTCBHighRdyPtr:全局變量定義,指向就緒任務中優先級最高的任務的 TCB。
OS_EXT	OS_TCB			*OSTCBHighRdyPtr;

0.2 就緒列表(OS_RDY_LIST)(os.h)

  • 就緒列表:存儲任務 TCB 的一個雙向鏈表。為了使同一個優先級支持多個任務,uCOS 使用頭尾指針來將 TCB 串成一個雙向鏈表。目前只用到頭指針,用來指向任務的 TCB。
/* 就緒列表重命名為大寫字母格式 */
typedef struct os_rdy_list	OS_RDY_LIST;

/* 就緒列表數據類型聲明,將 TCB 串成雙向鏈表 */
struct os_rdy_list{
	OS_TCB		*HeadPtr;
	OS_TCB		*TailPtr;
};

/* 任務函數名 */
typedef void (*OS_TASK_PTR)(void *p_arg);
  • OSRdyList:全局變量定義,把任務 TCB 指針放到 OSRdyList 數組中。即,使用這個數組,可將各個任務 TCB 組織起來。數組的下標表示任務的優先級。目前用不到這個優先級。
  • OS_CFG_PRIO_MAX:宏定義,表示系統支持多少個優先級,目前這里僅用來表示這個就緒列表可以存多少個任務的 TCB 指針。
OS_EXT	OS_RDY_LIST		OSRdyList[OS_CFG_PRIO_MAX];

其中在 os_cfg.h :
/* 支持最大的優先級 */
#define OS_CFG_PRIO_MAX		32u

0.3 系統狀態 (OSRunning) (os.h)

OSRunning: 全局變量定義,用於指示系統運行狀態。

OS_EXT 	OS_STATE		OSRunning;

目前我們有兩個任務狀態:

/* 任務狀態 */
#define  OS_STATE_OS_STOPPED                    (OS_STATE)(0u)
#define  OS_STATE_OS_RUNNING                    (OS_STATE)(1u)

1 任務的創建

1.1 任務創建函數 OSTaskCreate() (os_task.c)

任務創建函數需要完成的三件事:

  • 創建任務棧:調用函數 OSTaskStkInit()。
  • 填寫 TCB:剩余棧棧頂指針 SP 存入 TCB 的第一個成員 StkPtr。
  • 填寫 TCB:將任務棧的大小存入 TCB 的第二個成員 StkSize。
/* 任務創建函數 */
void OSTaskCreate( 	OS_TCB 			*p_tcb,  		/* TCB指針 */
					OS_TASK_PTR 	p_task,  		/* 任務函數名 */
					void 			*p_arg,  		/* 任務的形參 */
					CPU_STK 		*p_stk_base, 	/* 任務棧的起始地址 */
					CPU_STK_SIZE 	stk_size,		/* 任務棧大小 */
					OS_ERR 			*p_err )		/* 錯誤碼 */
{
	CPU_STK		*p_sp;
	
	p_sp = OSTaskStkInit ( 	p_task,
							p_arg,
							p_stk_base,
							stk_size );  /* 任務棧初始化函數 */
	p_tcb->StkPtr 	= p_sp;    	/* 剩余棧的棧頂指針 p_sp 保存到任務控制塊 TCB 的第一個成員 StkPtr 中 */
	p_tcb->StkSize 	= stk_size; /* 將任務棧的大小保存到任務控制塊 TCB 的成員 StkSize 中 */
	
	*p_err = OS_ERR_NONE;		/* 函數執行到這里表示沒有錯誤 */
}

接下來解釋函數 OSTaskStkInit。

1.1.1 任務棧創建函數 OSTaskStkInit() (os_cpu_c.c)

任務棧用來存儲各寄存器的狀態,以及其他中間數據。

注意:

  • 由於是使用 PendSV 中斷發起任務切換,因此 CPU 在切換新的任務前,會自動將新任務的任務棧按順序出棧寫入到寄存器中,這個順序為:R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSR。所以,CPU 會按這個順序將寄存器壓入舊任務的任務棧中:XPSR、R15(PC)、R14(LR)、R12、R3、R2、R1、R0。
  • 由以上討論可知:1. 部分寄存器仍沒有壓入棧中,需要我們自己在程序中手動壓入;2. 我們在寫程序的時候,入棧和出棧的順序是嚴格確定好的,要按照硬件要求去寫,不能改變。
  • 像 0x14141414u 這些數是方便我們調試的,說白了就是,這樣寫,我們就容易知道這個位置是 R14 的,不是別的寄存器的。這些數字除了方便我們看之外,沒有任何意義,你也可以全部初始化為零,或別的數字。
  • R13 哪去了?R13 是棧指針寄存器(PSP),當然不能壓入棧了,任務運行時要用到,一個隨時改變的值是沒必要壓入的。不過,你可以將 p_stk 視為 R13。

函數完成的事情:

  • 初始化任務棧,先在任務棧中為寄存器預留棧空間,再返回分配好棧空間后的棧指針。
/* 任務棧初始化函數 */
CPU_STK *OSTaskStkInit ( OS_TASK_PTR 	p_task,  		/* 任務名,指示着任務的入口地址 */
						 void			*p_arg,  		/* 任務的形參 */
						 CPU_STK		*p_stk_base, 	/* 任務棧的起始地址 */
						 CPU_STK_SIZE	stk_size ) 		/* 任務棧的大小 */
{
	CPU_STK		*p_stk;
	
	p_stk = &p_stk_base[stk_size];		/* 獲取任務棧的棧頂地址 */
	
	/* 任務第一次運行時,CPU寄存器需要預設數據 */
	/* 首先是異常發生時自動保存的 8 個寄存器 */
	/* R14、R12、R3、R2 和 R1 為了調試方便,需填入與寄存器號相對應的 16 進制數 */
	*--p_stk = (CPU_STK) 0x01000000u;		/* xPSR 的 bit24 必須置 1 		*/
	*--p_stk = (CPU_STK) p_task;			/* R15(PC) 任務的入口地址 		*/
	*--p_stk = (CPU_STK) 0x14141414u;		/* R14(LR)						*/
	*--p_stk = (CPU_STK) 0x12121212u;		/* R12							*/
	*--p_stk = (CPU_STK) 0x03030303u;		/* R3							*/
	*--p_stk = (CPU_STK) 0x02020202u;		/* R2							*/
	*--p_stk = (CPU_STK) 0x01010101u;		/* R1							*/
	*--p_stk = (CPU_STK) p_arg;				/* R0 : 任務形參  				*/
	/* 剩下的是 8 個需要手動加載到 CPU 寄存器的參數,為了調試方便填入與寄存器號相對應的 16 進制數 */
	*--p_stk = (CPU_STK) 0x11111111u;		/* R11							*/
	*--p_stk = (CPU_STK) 0x10101010u;		/* R10							*/
	*--p_stk = (CPU_STK) 0x09090909u;		/* R9							*/
	*--p_stk = (CPU_STK) 0x08080808u;		/* R8							*/
	*--p_stk = (CPU_STK) 0x07070707u;		/* R7							*/
	*--p_stk = (CPU_STK) 0x06060606u;		/* R6							*/
	*--p_stk = (CPU_STK) 0x05050505u;		/* R5							*/
	*--p_stk = (CPU_STK) 0x04040404u;		/* R4							*/
	
	return p_stk;	/* 此時 p_stk 指向剩余棧的棧頂 */
}

如下圖所示,即為任務棧的結構:

image

在創建好任務后,可以啟動 OS 進行任務調度了。

2 內核OS的啟動

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

不過,先等等,系統初始化應在創建任務前完成。

系統初始化完成的事情:

  • 標記系統運行狀態:停止狀態(因為此時未執行函數 OSSTart() )。
  • 初始化 OSTCBCurPtr:指向當前正在運行的任務的 TCB,因為此時沒有任務創建,因此為 0。
  • 初始化 OSTCBHighRdyPtr:指向就緒任務中優先級最高的任務的 TCB。因為本章沒有使用優先級,因此為 0。
/* 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 */
	
	OSRdyListInit();  /* 初始化就緒列表 */
	
	*p_err = OS_ERR_NONE;  /* 函數執行到這里表示沒有錯誤 */
}

注意到有個就緒列表初始化的函數,下面來講解此函數。

2.1.1 就緒列表初始化函數 OS_RdyListInit() (os_core.c)

初始化完成的事情:

  • 遍歷整個就緒列表,將各節點的頭、尾指針清零。這些指針日后將用來存儲 TCB 指針。

注意:

  • 此函數不允許用戶自己調用。
/* 初始化就緒列表 */
void OS_RdyListInit (void)
{
	OS_PRIO		i;
	OS_RDY_LIST	*p_rdy_list;
	
	for ( i = 0u; i < OS_CFG_PRIO_MAX; i++ )
	{
		p_rdy_list = &OSRdyList[i];
		p_rdy_list->HeadPtr = (OS_TCB *) 0;
		p_rdy_list->TailPtr = (OS_TCB *) 0;
	}
}

2.2 啟動系統內核 OSStart() (os_core.c)

現在所有事情准備完畢:系統內核初始化完畢,任務也創建完畢。即可啟動系統 OS,進行任務的切換。

完成的事情:

  • 讓 OSTCBHighRdyPtr 指向第 1 個任務。由於本文尚未用到優先級,因此最高優先級在這里無意義。
  • 啟動任務切換函數 OSStartHighRdy(),並且不再返回本函數。
/* 系統啟動函數 */
void OSStart (OS_ERR *p_err)
{
	if ( OSRunning == OS_STATE_OS_STOPPED )
	{
		OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; /* 手動配置任務 1 先運行 */
		OSStartHighRdy(); 						/* 啟動任務切換,不會返回 */
		*p_err = OS_ERR_FATAL_RETURN;			/* 運行至此處,說明發生了致命錯誤 */
	}
	else{
		*p_err = OS_STATE_OS_RUNNING;
	}
}

下面講解 OSStartHighRdy。不得不說,個人認為,這是 uCOS 最精彩的部分之一,編寫者巧妙地利用中斷達到了預期的功能(雖然這也是現代操作系統進行任務切換的常用方式,但依然讓我體會到了什么是編程的藝術)。

3 任務的切換

3.1 任務切換函數 OSStartHighRdy (ARM匯編) (os_cpu_a.s)

PendSV是可懸起異常,如果我們把它配置最低優先級,那么如果同時有多個異常被觸發,它會在其他異常執行完畢后再執行,而且任何異常都可以中斷它。

uCOS 使用中斷的方式來進行任務切換。在此之前,需要做一些准備。

注意:

  • 常量定義時,前面要空格。

完成的事情:

  • 配置 PendSV 異常的優先級為最低。CM3 內核支持 256 個優先級,因此最低優先級為 0xFF。在寄存器 SCB_SHPR3 中配置其優先級。
  • 因為系統剛啟動,還沒有任務,設置棧指針 PSP 為 0。
  • 觸發 PendSV 異常,開中斷,進行上下文切換。在寄存器 NVIC_INT_CTRL 的位 28 為 PENDSVSET,置位表示 PendSV 異常觸發。
;**********常量**********
    NVIC_INT_CTRL		EQU 	0xE000ED04		; 中斷控制及狀態寄存器 SCB_ICSR
    NVIC_SYSPRI14		EQU		0xE000ED22		; 系統優先級寄存器 SCB_SHPR3:bit 16~23
    NVIC_PENDSV_PRI		EQU		0xFF			; PendSV 優先級的值(最低)
    NVIC_PENDSVSET		EQU		0x10000000		; 觸發 PendSV 異常的值 Bit28:PENDSVSET

;**********開始進行第一次任務切換**********
OSStartHighRdy

	; 配置 PendSV 的優先級為 0XFF,即最低,防止接下來的 PendSV 中斷服務程序進行上下文切換,
	; 即 PendSV 中斷服務程序不允許中斷
	LDR 	R0, = NVIC_SYSPRI14				; 系統優先級寄存器 SCB_SHPR3:bit 16~23	
	LDR		R1, = NVIC_PENDSV_PRI
	STRB	R1, [R0]

	; 設置 PSP 的值為 0,開始第一個任務切換
	; 在任務中,使用的棧指針都是 PSP,后面如果判斷出 PSP 為 0,則表示第一次任務切換
	MOVS	R0, #0		
	MSR		PSP, R0

	; 觸發 PendSV 異常,如果中斷啟用且有編寫 PendSV 異常服務函數的話,
	; 則內核會響應 PendSV 異常,去執行 PendSV 異常服務函數
	LDR		R0, = NVIC_INT_CTRL			; 中斷控制及狀態寄存器 SCB_ICSR 的地址
	LDR		R1, = NVIC_PENDSVSET		; 觸發 PendSV 異常的值 Bit28:PENDSVSET
	STR		R1, [R0]

	; 開中斷
	CPSIE	I

	; 程序永遠不會執行到這
OSStartHang
	B		OSStartHang

3.2 中斷服務程序 PendSV_Handler (ARM匯編) (os_cpu_a.s)

一旦觸發了 PendSV 異常,那么將運行該中斷服務程序。這個程序的結構大體如下:

OS_CPU_PendSVHandler
    CPSID I ; 關中斷
    ;保存上文 
    ;....................... 
    ;切換下文 
    CPSIE I ;開中斷
    BX LR ;異常返回

在看下面的程序之前,撇開系統啟動的話題,不妨想一下,假設我們找到了優先級最高的任務,現在需要切換到這個任務,我們需要做些什么?

  • 首先,需要保存之前任務的寄存器狀態,當前 PSP 指向的是當前任務的棧,因此可先把它們壓到之前任務的棧中。注意,進入中斷前,硬件已自動壓入了一些寄存器的狀態,其他的寄存器需要我們自己手動壓棧。接着需要更新(保存)當前任務的 TCB 內容,回憶一下,這個 TCB 存儲了該任務的棧指針和棧大小等信息,因此可將 PSP 存入 StkPtr。總之,寄存器和 TCB,缺一不可。
  • 其次,需要切換棧,這一點毋庸置疑吧?因此,OSTCBCurPtr 即用來記錄當前運行任務的 TCB 指針需要指向新的 TCB,而這個 TCB 存儲了該任務的棧指針和棧大小等信息,那么我們可以將該任務 TCB 的 StkPtr 傳給 PSP。
  • 再次,找到的優先級最高任務的 TCB 指針存放在 OSTCBHighRdyPtr,所以要更新 OSTCBCurPtr 的內容,使 OSTCBCurPtr = OSTCBHighRdyPtr。
  • 最后,讓新任務棧中的寄存器狀態出棧,加載到寄存器中。我們要手動加載部分寄存器,剩余的寄存器由中斷返回時加載,在這時 PC 值也被更新為新任務的地址了(這個地址不一定是任務入口處,也可能是之前被打斷的地方)。
  • 到此為止,我們修改了什么?PSP、OSTCBCurPtr 和寄存器狀態。

好,讀懂這段代碼應該是順理成章的事情了。總結一下,程序完成的功能有:

  • 關中斷。
  • 先判斷棧指針是否為 0,如果為 0,說明是系統剛啟動,在進行第一次任務切換,之前沒有任務,那么我們不用將寄存器手動壓棧了,跳過這個步驟。至於 CPU 自己壓入的寄存器值,可以不管。
  • 如果不是第一次任務切換,那么需要將寄存器手動壓棧。(保存上文)
  • 將 OSTCBHighRdyPtr(新任務 TCB) 存到 OSTCBCurPtr 中,表明現在運行的任務已改變,得到新任務 TCB。
  • 從新任務 TCB 得到了其棧指針,那么加載新任務的棧指針到 PSP,得到新任務棧的位置。
  • 已得到了新任務的棧,那么將其存儲的寄存器狀態手動出棧,加載到寄存器中。(加載下文) (不得不說,以上三步應該是整個切換過程中最畫龍點睛的地方!)
  • 完成上下文切換,開中斷。
;**********PendSVHandler異常**********
PendSV_Handler
	
	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
	; 使 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                         ; 為了匯編指令對齊,不然會有警告
	
	
	END          	

簡要說明下面這行代碼的意思:在 CM3 中,棧指針分為 MSP 和 PSP,任意時刻只能使用其中一個,MSP為復位后缺省使用的堆棧指針,異常永遠使用MSP,如果手動開啟PSP,那么線程使用PSP,否則也使用MSP。置 LR 的位 2 為 1,那么異常返回后,CPU 將使用 PSP。

ORR     LR, LR, #0x04       ; 確保異常返回使用的堆棧指針是PSP,即LR寄存器的位2要為1

3.3 任務切換函數 OSShed() (os_core.c)

該函數用於任務切換,由於還沒有實現優先級等功能,因此我們先使用兩個任務輪轉的方式來編寫。實質是,通過 PendSV 異常(宏定義 OS_TASK_SW)來改變 OSTCBCurPtr 的值,從而達到任務切換的效果。

void OSSched (void)
{
	if( OSTCBCurPtr == OSRdyList[0].HeadPtr )
	{
		OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
	}
	else
	{
		OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
	}
	
	OS_TASK_SW();
}

在 os_cpu.h 中已經定義:

#ifndef  OS_CPU_H
#define  OS_CPU_H

/*********************************************************************************************************/
#ifndef  NVIC_INT_CTRL
	#define  NVIC_INT_CTRL         		*((CPU_REG32 *)0xE000ED04)   /* 中斷控制及狀態寄存器 SCB_ICSR */
#endif

#ifndef  NVIC_PENDSVSET
	#define  NVIC_PENDSVSET       		0x10000000    /* 觸發PendSV異常的值 Bit28:PENDSVSET */
#endif

#define  OS_TASK_SW()               NVIC_INT_CTRL = NVIC_PENDSVSET
#define  OSIntCtxSw()               NVIC_INT_CTRL = NVIC_PENDSVSET

/*********************************************************************************************************/

void OSStartHighRdy(void);
void PendSV_Handler(void);

#endif   /* OS_CPU_H */

4 任務創建及切換實例 (app.c)

功能:實現兩個任務的切換。

在 app.c 中:

#include "ARMCM3.h"
#include "os.h"

#define  TASK1_STK_SIZE       20
#define  TASK2_STK_SIZE       20

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

static   OS_TCB    Task1TCB;
static   OS_TCB    Task2TCB;

uint32_t flag1;
uint32_t flag2;

void Task1 (void *p_arg);
void Task2 (void *p_arg);
void delay(uint32_t count);

int main (void)
{
	OS_ERR err;
	
	/* 初始化相關的全局變量 */
	OSInit(&err);
	
	/* 創建任務 */
	OSTaskCreate ((OS_TCB*)      &Task1TCB, 
	              (OS_TASK_PTR ) Task1, 
	              (void *)       0,
	              (CPU_STK*)     &Task1Stk[0],
	              (CPU_STK_SIZE) TASK1_STK_SIZE,
	              (OS_ERR *)     &err);

	OSTaskCreate ((OS_TCB*)      &Task2TCB, 
	              (OS_TASK_PTR ) Task2, 
	              (void *)       0,
	              (CPU_STK*)     &Task2Stk[0],
	              (CPU_STK_SIZE) TASK2_STK_SIZE,
	              (OS_ERR *)     &err);
				  
	/* 將任務加入到就緒列表 */
	OSRdyList[0].HeadPtr = &Task1TCB;
	OSRdyList[1].HeadPtr = &Task2TCB;
	
	/* 啟動OS,將不再返回 */				
	OSStart(&err);
}

void delay (uint32_t count)
{
	for(; count!=0; count--);
}


void Task1 (void *p_arg)
{
	for( ;; )
	{
		flag1 = 1;
		delay( 100 );		
		flag1 = 0;
		delay( 100 );
		
		/* 任務切換,這里是手動切換 */		
		OSSched();
	}
}

void Task2 (void *p_arg)
{
	for( ;; )
	{
		flag2 = 1;
		delay( 100 );		
		flag2 = 0;
		delay( 100 );
		
		/* 任務切換,這里是手動切換 */
		OSSched();
	}
}
  • 剛開始的運行流程:OS 系統初始化(OSInit) -> 初始化就緒列表(OS_RdyListInit) -> 創建任務(OSTaskCreate) -> 創建任務棧(OSTaskStkInit) -> 任務加入就緒列表 -> 啟動 OS(OSStart) -> 啟動任務切換(OSStartHighRdy) -> 觸發 PendSV 異常(PendSV_Handler) -> 完成上下文切換(OSTCBCurPtr更新、任務棧切換)。

  • 任務切換流程:任務主動發起切換(OSSched) -> 主動觸發 PendSV 異常 -> 完成上下文切換(OSTCBCurPtr更新、任務棧切換)。

通過漫長的 Debug 和找 bug,終於將程序仿真了出來。這次自己寫任務創建和切換的代碼,還有之前對 uCOS 的移植,可以說我們是真正踏上了 uCOS 的學習道路。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM