龙空技术网

抽丝剥茧——从零开始构建嵌入式OS(0)之初始化堆栈

钥匙方程 308

前言:

现时咱们对“状态机三部分”大致比较珍视,姐妹们都想要学习一些“状态机三部分”的相关资讯。那么小编在网络上搜集了一些有关“状态机三部分””的相关内容,希望各位老铁们能喜欢,各位老铁们一起来了解一下吧!

#文章首发挑战赛#

做嵌入式开发时间长了,最终都会涉及到RTOS的内容。小编也是从2004年开始接触uCOSII,哪个时候感觉RTOS真是很厉害啊,很多代码是看不懂,整个OS的设计有很多讨巧的地方,但是确实人家的设计是用心了,尤其是在调度算法上,用二维bitmap的方法实现,正因为如此,人家宣称自己的操作系统,任务调度时间是确定的,因为用了调度算法就是一个查表,每次查找最高优先级的任务,时间完全是一样的,怎么能不是确定的呢?

不过,再好也是人家的,我们自己从头构建一个是不是可以呢?这个想法其实5年前就有了,当时太忙了没有时间。现在呢,不幸的是小编失业了,大龄码农的悲哀吧。但是终于有时间,继续做自己想做的事情。我不知道我能坚持多久啊,希望这次能够完成吧。

言归正传,RTOS操作系统的本质就是要建立多任务,每个任务或者线程可以按照非常线性的逻辑顺序执行,不必像状态机那样,要考虑状态机的切换,认为RTOS的任务调度就是状态机的切换,这部分工作统一交给RTOS,对于用户来讲就是不可见的,这样用户只需要关注业务逻辑的思考就可以了,这是RTOS的初衷,事实上也确实如此。如果使用习惯RTOS,再回到裸机开发,会非常不适应,尤其是遇到逻辑比较复杂的时候。

栈是RTOS的基础,RTOS所谓的任务切换,所做的就是任务栈的切换,任务调度就是找出就绪状态下最高优先级的任务栈地址。

我们以ARM的Cortex-M0作为目标芯片(因为我手上恰好有现成的开发环境),因此我们要首先搞清楚Cortex-M0的栈结构,该如何保存和恢复。

首先,栈操作的最小单位是4字节(32位),Cortex-M0的栈空间被设计位字对齐,地址必须是4的整数倍。由于这个原因,栈指针的最低两位BITS[1:0]在硬件上被置为0,因此读出也为0.

其次、向栈中存入数据叫“压栈”(使用PUSH指令),回复数据叫“出栈”(使用POP指令)。根据架构不同,有些处理器压栈后地址增加,有些地址减小。Cortex-M0操作基于“满递减”的栈模型,意味着栈指针始终指向栈空间最后一个数据,在执行存储数据PUSH前,栈指针先减小。

R0-R12为通用寄存器,R13为系统堆栈指针sp,堆栈指针是用于访问堆栈,也即系统的RAM区。Cortex-M0中采用了两个堆栈指针:主堆栈指针(MSP)和进程堆栈指针(PSP),R13在任何时刻只能是其中一个,默认情况为MSP,可以通过控制寄存器(CONTORL)来改变。

第三、任务堆栈结构,总共16个寄存器,占用64字节,后面我们固定用下面的堆栈结构来保存和恢复寄存器的值。

按照我们设计的栈结构,我们在创建任务时,就按照上面的栈结构进行初始化。

我们假定要做两个任务的演示,任务分别为Task1和Task2因此我们定义两个任务栈stack1和stack2,长度是128数组。

unsigned int stack1[128], stack2[128]

接下来我们就初始化任务栈,为了好在调试过程中观察寄存器的值,我们对寄存器初始化值和寄存器编号进行了对应,比如,寄存器R1的初始值,就用0x01010101标识。

按照我们设计的栈结构,我们在创建任务时,就按照上面的栈结构进行初始化。

我们假定要做两个任务的演示,任务分别为Task1和Task2因此我们定义两个任务栈stack1和stack2,长度是128数组。

unsigned int stack1[128], stack2[128]

接下来我们就初始化任务栈,为了好在调试过程中观察寄存器的值,我们对寄存器初始化值和寄存器编号进行了对应,比如,寄存器R1的初始值,就用0x01010101标识。为了初始化任务堆栈,我们实现一个统一的函数。

unsigned int *StackInit (void (*task)(void *param), void *param, unsigned int *pTop)

{

unsigned int *pStack;

pStack = pTop; /* 指向栈顶*/

*(pStack) = (unsigned int)0x01000000L; /* 保存R15(xPSR) 特殊寄存器值固定*/

*(--pStack) = (unsigned int)task; /*保存任务地址 R14(PC) */

*(--pStack) = (unsigned int)0x13131313L; /*LR */

*(--pStack) = (unsigned int)0x12121212L; /* R12 */

*(--pStack) = (unsigned int)0x03030303L; /* R3 */

*(--pStack) = (unsigned int)0x02020202L; /* R2 */

*(--pStack) = (unsigned int)0x01010101L; /* R1 */

*(--pStack) = (unsigned int)param; /* R0 : 参数指针 */

*(--pStack) = (unsigned int)0x11111111L; /* R11 */

*(--pStack) = (unsigned int)0x10101010L; /*R10 */

*(--pStack) = (unsigned int)0x09090909L; /* R9 */

*(--pStack) = (unsigned int)0x08080808L; /* R8 */

*(--pStack) = (unsigned int)0x07070707L; /*R7 */

*(--pStack) = (unsigned int)0x06060606L; /* R6 */

*(--pStack) = (unsigned int)0x05050505L; /* R5 */

*(--pStack) = (unsigned int)0x04040404L; /* R4 */

return (pStack);

}

任务堆栈的初始化,我们暂且写到这里,下一篇我们将继续讨论如利用初始化的任务堆栈,启动多任务进行工作。

小编坚持分享自己工总结的干货,看完如果对您有帮助,麻烦请您点赞转发。

标签: #状态机三部分 #堆栈操作实验报告总结