FreeRTOS內核源碼解讀之-------系統啓動(三)

前面文章兩篇文章介紹了FreeRTOS的啓動過程,但是有些問題還沒有解決,在本篇文章中將會逐一解決。
首先,在《FreeRTOS內核源碼解讀之-------系統啓動(一)》中提到Cortex-M4內核中兩個不同的棧指針寄存器MSP和PSP。對於不具有嵌入式OS的應用,可以在操作中只使用MSP棧指針寄存器;對於含有嵌入式OS(就像FreeRTOS)應用,異常處理(包括內核狀態下)使用的是MSP,對於應用任務使用的是PSP。每一個應用任務都有自己的棧空間,那麼上面這種機制是怎麼實現的呢?
還有FreeRTOS是一個多任務運行的操作系統,那麼當一個任務正在運行時,會被優先級更高的任務或者中斷打斷,FreeRTOS是怎麼保證被打斷的任務在重新運行時不會出現問題?換句話說,如何實現保護現場、任務切換等?
本文從這兩個問題觸發,具體闡述FreeRTOS內核啓動過程,在下一篇介紹FreeRTOS任務調度。

  • 問題引入
  • Cortex-M4對於多任務運行的硬件架構支持特性
    一、問題引入
    通過上一篇文章《FreeRTOS內核源碼解讀之-------系統啓動()》分析,我們知道FreeRTOS最後調用prvStartFirstTask產生一個SVC中斷,產生SVC中斷之後,在函數vPortSVCHandler進行有FreeRTOS內核態到用戶態的切換。兩個函數的代碼如下:
__asm void prvStartFirstTask( void )
{
	PRESERVE8
	    /* Cortext-M3硬件中,0xE000ED08地址處爲VTOR(向量表偏移量)寄存器,存儲向量表起始地址*/
    ldr r0, =0xE000ED08    
    ldr r0, [r0]
    /* 取出向量表中的第一項,向量表第一項存儲主堆棧指針MSP的初始值*/
    ldr r0, [r0]   
    /* 將堆棧地址存入主堆棧指針 */
    msr msp, r0
    /* 使能全局中斷*/
    cpsie i
    cpsie f
    dsb
    isb
    /* 調用SVC啓動第一個任務 */
    svc 0
    nop
    nop
}
__asm void vPortSVCHandler( void )
{
	PRESERVE8
	ldr	r3, =pxCurrentTCB
	ldr r1, [r3]
	ldr r0, [r1]
	ldmia r0!, {r4-r11, r14}
	msr psp, r0
	isb
	mov r0, #0
	msr	basepri, r0
	bx r14
}

首先FreeRTOS從內核態切換到用戶態時,是怎樣切換MSP和PSP棧指針的呢?還有FreeRTOS是怎樣找到應用任務的棧以及用戶編寫的應用程序的呢?
在函數prvStartFirstTask主要做的內容如下:
1)將堆棧地址存入主棧指針寄存器,在中斷向量表中第一項是堆棧地址;
2)開中斷;
3)產生一個SVC中斷。
在函數prvStartFirstTask中設置了MSP主堆棧指針。
在函數vPortSVCHandler可以看到對寄存器進行了賦值,對PSP進行了賦值、然後進行程序跳轉。那麼猜測關於上面的疑問可以從這幾句來尋求答案。
二、應用任務棧幀
在函數vPortSVCHandler中會看到這麼一句 ldr r3, =pxCurrentTCB ,pxCurrentTCB變量存放的是當前需要運行任務的控制塊。代碼:

ldr	r3, =pxCurrentTCB
	ldr r1, [r3]
	ldr r0, [r1]

具體作用是什麼的?
首先看任務控制塊中的內容(去掉各種條件編譯):

typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;
	ListItem_t			xStateListItem;
	ListItem_t			xEventListItem;	
	UBaseType_t			uxPriority;	
	StackType_t			*pxStack;
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;

去掉條件編譯感覺好清爽,這裏只是關注第一個成員變量,爲了填充一下版面,請允許我這麼做。關於任務控制塊各成員變量的講解,請關注我的另一篇文章 《FreeRTOS內核源碼解讀之-------任務創建》 。由此可知 ldr r3, =pxCurrentTCB 其實就是 ldr r3, =pxTopOfStack。那麼,最後就是使得r0寄存器保存任務棧指針。
那麼,任務棧裏面存放的是什麼呢?由於任務第一次運行,那麼需要去任務創建函數中尋找一點蛛絲馬跡。
任務創建函數調用過程如下:

xTaskCreate->prvInitialiseNewTask->pxPortInitialiseStack

從這裏看出任務創建函數xTaskCreate,最終會調用pxPortInitialiseStack。函數pxPortInitialiseStack代碼如下(下面就分析一下):

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */

	/* Offset added to account for the way the MCU uses the stack on entry/exit
	of interrupts, and to ensure alignment. */
	pxTopOfStack--;

	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	/* Save code space by skipping register initialisation. */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

	/* A save method is being used that requires each task to maintain its
	own exec return value. */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_EXEC_RETURN;

	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

代碼不是很多,就一句一句的分析一下吧。
1)*pxTopOfStack = portINITIAL_XPSR:棧頂保存了xPSR,且它的值爲portINITIAL_XPSR,0x01000000。其實就是一個初始狀態,其中的1表示Thumb狀態。因爲Cortex-M只有Thumb狀態。
2)*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK : portSTART_ADDRESS_MASK=0xfffffffe,pxCode保存的是任務函數的函數指針,就是我們用戶編寫的任務函數,這句就是保存任務跳轉時的跳轉地址,這裏要求必須半字或者字對齊,因此需要&portSTART_ADDRESS_MASK。
3)*pxTopOfStack = ( StackType_t ) prvTaskExitError :該數值是賦值給LR寄存器,保存任務出錯處理函數指針,一般不會執行這個函數,因爲我們的任務都是一個不能返回的死循環,因此不會執行。
4)pxTopOfStack -= 5:留出空間用於保存寄存器 R12, R3, R2 and R1。
5)*pxTopOfStack = ( StackType_t ) pvParameters:保存任務函數的參數。
6)*pxTopOfStack = portINITIAL_EXEC_RETURN:處理器進入異常處理或者中斷模式的時候,連接寄存器的數值會被更新爲portINITIAL_EXEC_RETURN,當我們使用BX或者LDR這類指令時,會將該數值寫入程序寄存器,是用來觸發異常返回機制。
7)pxTopOfStack -= 8:保存r4~r11。
通過上面分析,我們大體畫出任務棧中保存的數據如下:
在這裏插入圖片描述那麼我們在回過來看一下函數vPortSVCHandler具體做了哪些事情:
1)將任務棧保存的R11-R4內容賦給寄存器R11-R4;
2)將portINITIAL_EXEC_RETURN賦給寄存器R14,主要是用於觸發中斷返回,也就是下面 bx r14 指令;
3)pvParameters 賦值給PSP寄存器。
那麼現在就需要解決FreeRTOS運行用戶任務時,MSP到PSP切換的問題。玄妙之處就是portINITIAL_EXEC_RETURN,上面說了將portINITIAL_EXEC_RETURN=0xfffffffd寫入到R14寄存器(LR)之後,我們調用bx r14指令之後就會產生中斷返回,通過查閱資料得到如下圖:
在這裏插入圖片描述正是寫入的是0xfffffffd,所以中斷返回的時候是的處理器進入處理模式,並且使用進程棧PSP寄存器。
總結: 本文主要解決了SVC中斷處理函數中具體執行內容,和任務創建過程中任務棧中存放的數據。以及如何從MSP棧指針切換到PSP棧指針。下一篇文章將對FreeRTOS中任務切換進行講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章