UCOS-II任务堆栈初始化函数分析与移植

UCOS-II的在建立任务函数中要对新建任务的堆栈进行初始化。堆栈初始化函数原型是:

OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt);

void (*task)(void *pd): 定义一个函数指针变量task,这个函数指针指向的函数有一个void *类型的参数,没有返回值。

p_arg:是任务开始执行时,传递给任务的参数的指针。

Ptos:是分配给任务的堆栈栈顶的指针。

Opt:在OSTaskCreate()函数中调用 OSTaskStkInit()函数时,Opt为0。因为OSTaskStkInit()函数不支持在任务的建立过程中设置选项。OSTaskCreateExt()函数支持这个选项。

OSTaskStkInit()是一个指针函数,也就是返回值是一个指针,返回初始化后的堆栈的栈顶。

任务建立前要先建立任务堆栈。

在µC/OS-II中,用OS_STK_GROWTH来设置堆栈的增长方向,OS_STK_GROWTH为0表示堆栈从低地址向高地址增长;OS_STK_GROWTH为1表示堆栈从高地址向低地址增长,其宏定义为:

#define OS_STK_GROWTH 1; //堆栈从高地址向低地址增长

#define OS_STK_GROWTH 0; //堆栈从低地址向高地址增长

我现在用的是LPC2214,ARM7内核,定义

typedef unsigned int OS_STK;

OS_STK为32位数据类型

#define TASK_STK_SIZE 512

OS_STK TaskStartStk[TASK_STK_SIZE];

定义一个数组作为任务堆栈,堆栈长度512*4 = 2K字节,乘4是因为OS_STK是32位数据类型。

当调用函数OSTaskCreate()创建一个任务时,把数组的指针传递给函数OSTaskCreate()中的堆栈栈顶指针ptos,就可以把该数组和任务关联起来成为该任务的任务堆栈。

先了解下栈顶和栈底。

栈顶是堆栈中存储第一个数据的地方。栈底是堆栈中存储最后1个数据的地方。

clip_image001

栈顶和栈底刚开始我总是弄混,想当然认为栈顶应该就是在堆栈中所有数据的最上面。理解成最上面有歧义,因为还有个方向问题。可以把最先送入堆栈的数看做是最上面,也可以把最后进入堆栈的数看做是最上面。我觉得准确的理解就是栈顶是最先送入堆栈的数放的地方。

TaskStartStk[0]是数组中的最低地址,TaskStartStk[TASK_STK_SIZE – 1]是数组中的最高地址。

如果是递增方式堆栈,那么栈顶就是最低地址& TaskStartStk[0],栈底是最高地址& TaskStartStk[TASK_STK_SIZE – 1]。

如果是递减方式堆栈,那么栈顶就是最高地址& TaskStartStk[TASK_STK_SIZE- 1],栈底是最低地址& TaskStartStk[0]。

Ucos在LPC2214上移植,堆栈为递减方式,

#define ARM_SYS_MODE (0x0000001FL)

OSTaskStkInit()移植代码如下:

OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt)

{

OS_STK *stk;

opt = opt; /* ‘opt’ is not used, prevent warning*/

stk = ptos; /* Load stack pointer */

*(stk) = (OS_STK)task; /*PC EntryPoint */

*(–stk ) = (INT32U)0x14141414L; /*R14(LR) */

*(–stk) = (INT32U)0x12121212L; /* R12 */

*(–stk) = (INT32U)0x11111111L; /* R11 */

*(–stk) = (INT32U)0x10101010L; /* R10 */

*(–stk) = (INT32U)0x09090909L; /* R9 */

*(–stk) = (INT32U)0x08080808L; /* R8 */

*(–stk) = (INT32U)0x07070707L; /* R7

*(–stk) = (INT32U)0x06060606L; /* R6

*(–stk) = (INT32U)0x05050505L; /* R5

*(–stk) = (INT32U)0x04040404L; /* R4

*(–stk) = (INT32U)0x03030303L; /* R3

*(–stk) = (INT32U)0x02020202L; /* R2

*(–stk) = (INT32U)0x01010101L; /* R1

*(–stk) = (INT32U)p_arg; /* R0 : argument

*(–stk) = (INT32U)ARM_SYS_MODE; /* CPSR (Enable both IRQ and FIQ interrupts) */

return (stk);

}

OSTaskStkInit()返回值就是初始化完成后的堆栈指针,放在任务控制块的开始,OSTCBStkPtr中。堆栈初始化完成后堆栈指针是& TaskStartStk[TASK_STK_SIZE- 1-15];这个值应该放在R13(SP)中,这里为什么没处理R13呢?

在任务第一次开始执行时,操作系统首先得到任务的任务控制块,然后从任务控制块得到任务的堆栈指针,再把这个堆栈指针送到R13(SP)。然后再OSCtxSw()函数中把初始化的各个寄存器的值送到对应的CPU寄存器,所以在OSTaskStkInit()函数里不用处理R13。在任务执行过一次之后,任务被中断切换到其它任务之前,就会从R13(SP)得到堆栈指针当前的位置,在OSCtxSw()函数中把CPU的R0-R12,R14等值压到R13(SP)指定的堆栈中。

OSTaskStkInit()中数据进栈的顺序要和OSCtxSw()中数据出栈的顺序对应。

PC值是最后出栈,所以要最先进栈。把任务函数的地址压入堆栈。出栈后,任务函数地址送入PC后,就开始执行任务函数。

R14是返回地址,但是任务函数是一个无限循环,只有在任务调度时才会退出,而在任务调度时切换到另一个任务时,会把另一个任务堆栈中存储的R14的值送到R14,保证程序在另一个任务被中断时的断点继续运行。所以在初始化堆栈时,R14的值是没有用的,可以随意赋值。

R1-R12可以随意赋值。

R0用来存储任务传递的参数。选择R0,是因为任务的参数在编译时是通过R0来传递的。

最后设置一个任务运行时的状态寄存器CPSR值,压入堆栈。

返回堆栈初始化完成后的指针给任务控制块的OSTCBStkPtr。在任务第一次开始执行时,将从任务控制块的OSTCBStkPtr得到任务堆栈的当前值,并把堆栈中的值送到CPU对应的寄存器,最后任务函数的地址送到PC开始执行任务。此时SP的值为堆栈的栈顶& TaskStartStk[TASK_STK_SIZE- 1]。所以OSTaskStkInit中没处理R13(SP).

© 版权声明
THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发