注:以下所讲的堆栈即栈,因为堆栈说习惯了 ,堆是堆栈是栈;
下面简单的介绍一下我的系统,其实还不能叫系统,因为太简单了,只有心脏在跳动,还没发育长大;
以MSP430单片机为例,MSP430有16个寄存器,R0 (PC)、R1(SP)、R2(SR/CG1)、R3(CG2)、R4~R15(通用寄存器可以给用户存储数据使用)。
大家都知道切换任务时要保存现场,那现场到底要保存什么呢?今天我主要讲讲任务切换需要保存哪些寄存器,当然我阐述的是最简单的系统,从0开始构建系统,任务切换最重要的一步就是发生定时器中断时保存当前任务的堆栈指针,任务切换时保存现场PUSH / 恢复现场POP,寄存器的弹入弹出都和堆栈指针有关,堆栈指针指向哪就PUSH到哪,堆栈指针指向哪就从那POP,下面重要讲解。
比如 :
定义一个数组unsigned int buf[200];
定义一个指针unsigned int* ptr= &buf[200-1];
下面执行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