注:如下所講的堆棧即棧,由於堆棧說習慣了 ,堆是堆棧是棧;數組
下面簡單的介紹一下個人系統,其實還不能叫系統,由於太簡單了,只有心臟在跳動,還沒發育長大;函數
以MSP430單片機爲例,MSP430有16個寄存器,R0 (PC)、R1(SP)、R2(SR/CG1)、R3(CG2)、R4~R15(通用寄存器能夠給用戶存儲數據使用)。spa
你們都知道切換任務時要保存現場,那現場到底要保存什麼呢?今天我主要講講任務切換須要保存哪些寄存器,固然我闡述的是最簡單的系統,從0開始構建系統,任務切換最重要的一步就是發生定時器中斷時保存當前任務的堆棧指針,任務切換時保存現場PUSH / 恢復現場POP,寄存器的彈入彈出都和堆棧指針有關,堆棧指針指向哪就PUSH到哪,堆棧指針指向哪就從那POP,下面重要講解。指針
好比 :code
定義一個數組unsigned int buf[200]; orm
定義一個指針unsigned int* ptr= &buf[200-1];it
下面執行3條指令:基礎
mov ptr,sp;修改了堆棧指針,讓堆棧指針指向數組的最後一個字節變量
PUSH R4;把R4寄存器的值保存到數組的最後一個字節定時器
PUSH R5;把R5寄存器的值保存到數組的倒數第二個字節
。。。。。。。
PUSH R15;把R15寄存器的值保存到數組的倒數第11個字節
PUSH PC;把PC寄存器的值保存到數組的倒數第12個字節,PC你們都很熟悉了就是PC裏保存着下一個程序要執行的地址,對於任務來講就是下個任務要恢復的斷點。
執行完以上彙編指令後buf的存儲狀況以下:
|-------------------------------------------------------------------------------|
| pc | R15 | R14 | …………… | R4 |
|-------------------------------------------------------------------------------|
buf[199] buf[198]………………………………...buf[185]
爲何要從數組的最後一個字節開始push呢,由於堆棧從高地址往低地址增加,因此要從數組的最後一個字節開始存儲。
(一)好了有堆棧操做的基礎,下面開始講建立任務;
建立任務前的準備,首先定義了3個結構體,任務描述結構體,任務控制結構體,系統時間結構體;
//----------------------系統時間------------------------------------------
typedef struct
{
//unsigned char year : 7;
//unsigned char month : 4;
unsigned int day : 5;
unsigned int hour : 5;
unsigned int minute : 6;
unsigned int sec : 6;
unsigned int ms : 10;
}TaskSysTime,*PTaskSysTime;
//----------------------任務描述結構體------------------------------------------
typedef struct
{
OSELK_STK *pTaskStk;// 任務堆棧指針指向建立任務的堆棧數字最後一個字節,由於MSP430的棧從高地址往低地址方向增加
volatile TaskSysTime runTmr;//定時
volatile TaskSysTime runTmr_bak;//定時備份
unsigned char taskId;
unsigned char taskMode;//任務模式
unsigned char taskState;//任務狀態
E_Task_func * func;//任務人口
}TaskFuncType, *PTaskFuncType;
//----------------------任務控制結構體------------------------------------------
typedef struct
{
unsigned int task_cnt;//任務調度使用的,控制下一個任務調度誰
unsigned int exec_id;//
unsigned int CurTaskLens;//獲取當前任務列表裏的任務數
OSELK_STK ** ppTaskStk;//這個指針用來修改pTaskStk
}TaskTabCtrlType,*PTaskTabCtrlType;
(二)OK,3個結構體構建完了,開始建立任務,建立最重要的必需要傳入至少2參數,第一:任務入口地址即函數指針;第二:任務棧底地址。
/**
***************************************************************************************************
* create_task
*
* \param E_Task_func* fun \ unsigned int taskId \ unsigned int *pStk \ unsigned char mode \ TaskSysTime tmr
* \return void
*
***************************************************************************************************
*/
int create_task(E_Task_func* fun , unsigned int taskId , unsigned int *pStk , unsigned char mode , TaskSysTime tmr)
{
PTaskFuncType ptask_fun;
if ((NULL == fun ) || (NULL == pStk))
return (-1);
ptask_fun = &Task_funcTab[taskId];//依次往數組裏放
if (taskId < MAX_TASK)
{
if (ptask_fun->taskMode == 0)
{
ptask_fun->func = fun;
ptask_fun->taskMode = mode;
ptask_fun->runTmr = tmr;
ptask_fun->runTmr_bak =tmr;
ptask_fun->pTaskStk = LKOS_TaskStackInit(fun,pStk,taskId); //初始化堆棧
ptask_fun->taskId = taskId;
Task_Ctrl.CurTaskLens = taskId;
return (ptask_fun->taskId);
}
}
return (-1);
}
調用任務建立函數時,先定義一個任務棧數組以下:
unsigned int TASK1_STACK_SIZE[200]={0,};//任務棧
void LKOS_Task1(void);
/*----------------------------------------------------------------------------------------
* MAIN
*----------------------------------------------------------------------------------------
*/
int main( void )
{
TaskSysTime taskTmr;
sys_BspInit();
LKOS_TaskInit();
taskTmr.ms = 15;
create_task(LKOS_Task1,LKOS_Task1_ID,&TASK1_STACK_SIZE[200-1],TMR_MODE,taskTmr);
LKOS_FirstTaskInit();
while(1);
return 0;
}
(三)任務棧初始化
介紹一下個人任務棧怎麼用的,在建立任務時定義2個全局數組,TASK1_TACK_SIZE[200]; TASK2_TACK_SIZE[200];
建立任務時會初始化數組時以下:
TASK1_TACK_SIZE[200];
|-------------------------------------------------------------------|
| task1 | r4=0 | r5=0 | 0...... r15=0 |
|-------------------------------------------------------------------|
高地址 ----> 低地址(棧從高地址往低地址增加)
初始化完後TASK1_TACK_SIZE[199] =(unsigned int)task1;//任務入口地址
任務結構體的OSELK_STK *pTaskStk = &TASK1_TACK_SIZE[199-13];此時任務的棧指針指向TASK1_TACK_SIZE[199-13].
TASK2_TACK_SIZE[200];
|-------------------------------------------------------------------|
| task2 | r4=0 | r5=0 | 0...... r15=0 |
|-------------------------------------------------------------------|
高地址 ----> 低地址(棧從高地址往低地址增加)
初始化完後TASK2_TACK_SIZE[199] =(unsigned int)task2;//任務入口地址
任務結構體的OSELK_STK *pTaskStk = &TASK2_TACK_SIZE[199-13];此時任務的棧指針指向TASK2_TACK_SIZE[199-13].
(1)任務建立好以後從第一個任務開始運行,第一個任務就是從任務棧裏面取出task1並調用,第一個任務就開始跑起來了。
(2)當發生定時器中斷時:
第一:首先自動把PC和SR壓進當前的任務棧裏即任務1的棧數組TASK1_TACK_SIZE[200];
|-------------------------------------------------------------------|
| task1 | ...|....|pc|sr | ....... | task stack...... |
|---------------| --- |---------------------------------------------|
高地址 | | --------> 低地址
| |進入中斷服務程序時SP此時棧指針指向這裏;
|發中斷時任務棧可能指向這裏,爲何指向這裏,由於任務已經跑起來了用了一部分棧。
第二:進入中斷服務程序時,要手動保存全部工做寄存器R4~R15,就是用匯編PUSH指令把R4~R15的值到存到數組裏,以下:
(除了中斷會自動保存的PC和SR不須要保存其餘都要保存,固然有人會問局部變量哪去了,須要保存嗎,答應是不須要保存,由於局部變量就在棧裏,在壓入pc以前的就是局部變量或調用的子程序還沒返回時被壓棧的信息)
TASK1_TACK_SIZE[200]數組此時的存儲信息以下
|-----------------------------------------------------------------------|
| task1 | ...|....|pc|sr |r4|r5|r6|r7|r8|r9| ...r14|r15|.... | task stack...|
|---------------- -------------------------------- |---------------------|
高地址 --------> | 低地址
|
|
|SP (此時棧指針指向這裏)
此時須要保存當前的SP指針,當前SP指向r15寄存器的位置,把數組中r15的地址保存到任務塊裏的OSELK_STK *pTaskStk,初始化時OSELK_STK *pTaskStk = &TASK2_TACK_SIZE[199-13];此時要讓OSELK_STK *pTaskStk = r15的位置。下次恢復時只要把OSELK_STK *pTaskStk恢復到SP,即SP又從新指向了數組中r15的位置,這樣就能夠繼續往下執行了。
任務斷點恢復時數組變化過程以下,恢復時執行彙編指令POP:
TASK1_TACK_SIZE[200]數組,執行POP指令棧往回走
|<---------------sp---------------------|
|----------------|-------------------------------------------|--------------------|
| task1 |...|....| pc | sr | r4 | r5 | r6 |r7 | r8 | r9 | ...r14|r15|.... task stack...|
|----------------|----------------------------------------------------------------|
高地址 | -----à 低地址
|
|
|SP (此時棧指針指向這裏)
當彈出最後一個寄存器PC時,彙編執行POP PC時當即會執行被中斷的斷點程序繼續往下執行。
以此類推,每一個任務切換都是這個過程,這樣分時複用就開始跑起來了。
下面是中斷代碼:
#include <msp430f5247.h>
;********************************************************************************************************
;********************************************************************************************************
extern LKOS_TaskProcISR
extern pTaskStk
;********************************************************************************************************
;********************************************************************************************************
RSEG CODE ; Program code
;********************************************************************************************************
;Interrupt ISR
;********************************************************************************************************
TIMER1_ISR
dint;關閉中斷
push.w r4;保存工做寄存器
push.w r5
push.w r6
push.w r7
push.w r8
push.w r9
push.w r10
push.w r11
push.w r12
push.w r13
push.w r14
push.w r15
mov.w sp,pTaskStk;保存當前的任務棧指針
calla #LKOS_TaskProcISR;調用c語言 取一下任務棧指針
mov.w pTaskStk,sp;恢復任務棧指針
pop.w r15
pop.w r14
pop.w r13
pop.w r12
pop.w r11
pop.w r10
pop.w r9
pop.w r8
pop.w r7
pop.w r6
pop.w r5
pop.w r4
eint;開中斷
reti;這條指令中斷返回指令,會自動執行 POP SR ,POP PC,當POP PC時即任務被切換到下一個斷點了
;********************************************************************************************************
; Interrupt vectors
;********************************************************************************************************
COMMON INTVEC
ORG TIMER1_A0_VECTOR
WDT_VEC DW TIMER1_ISR
END