自己动手从0到1学写RTOS学习指南

  1. 主页
  2. 自己动手从0到1学写RTOS学习指南
  3. 常见问题FAQ汇总
  4. 任务创建时堆栈为什么传入1024

任务创建时堆栈为什么传入1024

内容纲要

感谢同学 @小钟 的提问

Q:刚看到任务切换原理部分,任务堆栈这里是把数组高地址传入的,应该是1023的地址吧,1024的话不是到隔壁内存去了么。源码如下:

{% highlight c%}
tTaskStack task1Env[1024];
tTaskStack task2Env[1024];
tTaskInit(&tTask1, task1, (void )0x11111111, &task1Env[1024]);
tTaskInit(&tTask2, task2, (void
)0x22222222, &task2Env[1024]);
{% endhighlight %}

A: 回顾在《芯片内核简介》中学到的,Cortex‐M3 使用的是“向下生长的满栈”模型,也即堆栈指针总是指向最后一个压入堆栈的单元。

而stack参数,指的是任务初始的堆栈指针。

{% highlight c%}
void tTaskInit (tTask task, void (entry)(void ), void param, uint32_t * stack)
{% endhighlight %}
那么显然,对于tTaskStack task2Env[1024],stack就不是指向1023,而是1024,即task2Env的末端。

  • 当执行压栈指令,如PUSH;或者发生异常时,硬件自动压栈,堆栈指针会先减4,即调整到1023单元处,然后再向1023单元处压入值。所以,压栈操作不会写到task2Env之外的内存空间。
  • 在执行tTaskInit对堆栈进行初始化时(在后两节视频课程介绍)

这个过程由我们手动对堆栈进行了一些初始化操作,可以看到操作代码为*(–stack)=???,也是先将堆栈指针减4,然后再存入具体的值。这个过程和使用PUSH执行压栈方式是一样的。也即,在这个过程中也不会使用1024单元,而是使用1023、1022….各个单元。

{% highlight c%}
void tTaskInit (tTask task, void (entry)(void ), void param, uint32_t stack)
{
(–stack) = (unsigned long)(1<<24); // XPSR, 设置了Thumb模式,恢复到Thumb状态而非ARM状态运行
(–stack) = (unsigned long)entry; // 程序的入口地址
(–stack) = (unsigned long)0x14; // R14(LR), 任务不会通过return xxx结束自己,所以未用
(–stack) = (unsigned long)0x12; // R12, 未用
(–stack) = (unsigned long)0x3; // R3, 未用
(–stack) = (unsigned long)0x2; // R2, 未用
(–stack) = (unsigned long)0x1; // R1, 未用
(–stack) = (unsigned long)param; // R0 = param, 传给任务的入口函数
(–stack) = (unsigned long)0x11; // R11, 未用
(–stack) = (unsigned long)0x10; // R10, 未用
(–stack) = (unsigned long)0x9; // R9, 未用
(–stack) = (unsigned long)0x8; // R8, 未用
(–stack) = (unsigned long)0x7; // R7, 未用
(–stack) = (unsigned long)0x6; // R6, 未用
(–stack) = (unsigned long)0x5; // R5, 未用
*(–stack) = (unsigned long)0x4; // R4, 未用

task->stack = stack;                                // 保存最终的值

}
{% endhighlight %}

实际上,要求传递任务堆栈的初始指针并不好,影响可移植性。试想一下,如果我们的代码要求在两种不同类型的CPU间迁移。

针对Cortex-M类型的CPU,任务初始化代码如下。
{% highlight c%}
tTaskInit(&tTask1, task1Entry, (void *)0x11111111, &task1Env[1024]);
{% endhighlight %}
如果另一种类型的CPU堆栈指针总是指向下一个空单元。也就是压栈时先向当前堆栈指针指向的单元写入值,然后再将地址递减。那么任务的初始化代码就变成。
{% highlight c%}

tTaskInit(&tTask1, task1Entry, (void *)0x11111111, &task1Env[1023]);

{% endhighlight %}
这就导致了:因为采用的CPU不同, tTaskInit()的调用必须修改。

负责应用开发但不懂这个问题的同学,可能会很烦:我干吗要知道传递&task1Env[1023],还是&task1Env[1024]?

所以,在本课程的后续课程, tTaskInit()会修改为:

{% highlight c%}
void tTaskInit (tTask task, void (entry)(void ), void param, uint32_t prio, uint32_t * stack, uint32_t size)
{% endhighlight %}
堆栈相关的参数含义,变成 stack – 堆栈空间的起始地址,size – 堆栈空间的大小。

无论是移植到哪种类型的CPU,任务初始化代码就统一变成了:

{% highlight c%}
tTaskInit(&tTask1, task1Entry, (void *)0x11111111, 0, task1Env, sizeof(task1Env));
{% endhighlight %}

这篇文章对您有用吗?

我们要如何帮助您?

发表评论

电子邮件地址不会被公开。 必填项已用*标注