嵌入式OS的栈
有过嵌入式程序设计的程序员都知道,在创建一个新的任务时都要为其传入一个栈空间和栈的大小(有时),那么这个栈到底是用来干什么的呢?本文将带你找到答案。
本文介绍的实验环境是Windows上移植的uCOSII,在VS.Net 2003中。
一、栈首要的任务就是,存储当前任务的寄存器状态以便在任务切换时恢复到切换前的状态。
这个功能是嵌入式的OS中栈的特有功能,其地址存放在任务的控制块TCB中。
二、存放函数调用及局部变量。
一个嵌入式系统中往往存在很多个任务,特定任务的栈只存放该任务调用的函数及局部变量,下面我们给出实验结果:
//
创建任务需要的栈空间
int TaskStk[10][2048]; // Tasks stacks
//
创建任务
OSTaskCreate(Task1, 0, &TaskStk[2][2047], 10);
OSTaskCreate(Task2, 0, &TaskStk[3][2047], 9);
void
Task1(void * pParam)
{
while(1)
{
OS_ENTER_CRITICAL();
printf( "task1 is running/n");
OS_EXIT_CRITICAL();
OSTimeDly(5);
}
}
void
Task2(void * pParam)
{
int p = 10000;
int *pp = &p;
char
str[100] = {0};
while(1)
{
OS_ENTER_CRITICAL();
printf( "%s", "test 2/n");
OS_EXIT_CRITICAL();
OSTimeDly(10);
}
}
上面创建了2个任务,它们的栈是连续分布的,每个栈的大小是2048 * 4 个字节。
运行时栈的实际地址为:
&TaskStk[0][0]
0x0042e580
&TaskStk[1][0] 0x00430580
&TaskStk[2][0] 0x00432580
&TaskStk[3][0] 0x00434580
于是,任务1的栈空间为0x00432580 –> 0x0043457f;
任务2的栈空间为0x00434580 –> 0x0043657f;
任务2中的局部变量
int
p = 10000;的地址为 0x00436568,正好落在了任务的栈中。靠近栈底0x0043657f的位置。
任务2中的局部数据
char
str[100] = {0};的地址为0x004364f0,也正好落在了任务的栈中。
以上足以说明了栈的存放局部变量的功能。
三、更深层次的原因
当一个任务获得CPU后,系统的寄存器就被设置为当前任务先前的寄存器状态,其中一个最要的寄存器ESP(始终指向栈顶)就指向了该任务的栈顶(栈的空间由我们创建时确定)。接下来的函数调用使用栈,通过ESP所有的操作都是在我们传入的空间上进行,函数内部的局部变量也是如此。
四、栈溢出
既然,这个栈是用来存放局部变量的,那就不可避免的要讨论栈溢出。我们申请一个较大的数组:
char
str[2048 * 4] = {0}; 这个空间显然已经超出了任务2的栈大小。而任务1和任务2的栈是连续的,那么,这个语句就覆盖了任务1 的栈,导致了任务无法正确的切换返回,并最终导致错误。
但是,在真实的嵌入式环境中,这样的溢出会导致程序崩溃,但是,查找原因往往很难,因此,在嵌入式环境中使用局部变量时,要特别的注意。
五、总结
本文介绍了嵌入式OS中任务中栈的2个作用:任务切换和局部变量及调用。因为,稍有不慎很容易导致栈的溢出是整个系统崩溃,所以在使用布局的数据时,千万注意不要导致栈溢出,稍大一些数据空间可以使用动态申请或者声明为静态。