综述+任务堆栈初始化过程分析.docx
《综述+任务堆栈初始化过程分析.docx》由会员分享,可在线阅读,更多相关《综述+任务堆栈初始化过程分析.docx(9页珍藏版)》请在冰豆网上搜索。
![综述+任务堆栈初始化过程分析.docx](https://file1.bdocx.com/fileroot1/2022-12/18/ab67d4c2-d1f9-4e21-90fd-2212f7221ef8/ab67d4c2-d1f9-4e21-90fd-2212f7221ef81.gif)
综述+任务堆栈初始化过程分析
窗体顶端
综述+任务堆栈初始化过程分析
2010-10-2703:
40
转载自likunyuan
最终编辑chiefsniper
首先在看本文时有以下几个知识点需要了解:
1、为什么我要把工作模式切换在管理模式呢?
答:
根据第二版的《ARM体系结构参考手册》,我们可以查看到在用户模式和系统模式下,使用LDM指令时,会导致对不可预知的错误。
但是在IAREWARM环境下,对系统初始化时,是默认将ARM切换到系统模式的,因此在uCOS-II启动时,我们默认的将系统切换到管理模式下工作。
这样就可以避免了在使用LDM指令时产生不可预知的错误了。
2、着重强调一下在OS_CPU_A.A中汇编指令中的BX指令,为什么要强调呢?
答:
根据第二版的《ARM体系结构参考手册》,在ARM体系的T变种版本4、ARM体系版本5及其以上版本。
BX指令把通用寄存器Rm中的值拷贝到PC,同时判断拷贝后的PC寄存器的第0位值是否是1,如果第0位的值是1,则将当前处理器切换到Thumd指令集下进行执行。
3、此处再讲解一下向PC寄存器写操作的注意事项,为什么要强调呢?
答:
根据第二版的《ARM体系结构参考手册》,我们可知,在向该寄存器进行写操作时会导致程序跳转到写入PC寄存器的地址处进行执行。
在ARM指令集必须要求是字对齐的,那么写入PC寄存器的值得最低2位就必须为0;
在Thumd指令集下必须是半字对齐的,那么写入PC寄存器的值得最低1位就必须是0;
ARM体系的版本较多,在ARM体系版本3及其以下版本,在ARM指令下写入PC寄存器的最低2位是被忽略了的(也就是说是由硬件实现PC=PC&0xFFFFFFFC操作)
在ARM体系版本4及其以上版本,在ARM指令下写入PC寄存器的最低2位值是必须为0的,这部分是必须软件完成的,即:
在将地址写入PC之前,必须将地址的值与0xFFFFFFFC进行与操作,再将操作的结果传递给PC寄存去。
在Thumd指令集下要求所有指令为半字对齐,因此写入PC寄存器的值必须先与0xFFFFFFFE进行与操作,然后再将与操作的结果传递给PC寄存器。
4、特别提到大家注意一下LDM指令。
为什么呢?
答:
该指令只能在ARM指令集中,在Thumd指令集中是没有该指令的。
在查看到《ARM+Architecture+Reference+Manual(2nd+Edition)》手册后,会发现对于LDM的操作有如下操作,用示意性代码表示如下:
If条件满足
{
Address=start_address
For(I=0;I<=14;i++)
{
If(register_list==1)
{
Ri=Memory[address,4];
Address+=4;
}
}
If(register_list[15]==1)
{
value=memory[address,4];
If(ARM体系5版本及其以上版本)
{
PC=value&0xFFFFFFFE
Tbit=Value[0];
}
Else
{
PC=value&0xFFFFFFFC
}
Address=address+4;
}
上面的示意性代码意思就是说,如果LDM的目的操作数包含PC寄存器的话,写入PC寄存器的值得最后一位路过时1的话,执行完毕LDM指令后,处理器就会切换到Thumd模式。
本次分析所使用到的资料或书籍如下:
1、《ARM+Architecture+Reference+Manual(2nd+Edition)》
2、《ARM体系结构与编程》--杜春蕾
3、《嵌入式实时操作系统μCOS-II(第二版)》--邵贝贝翻译
4、《ARM®IARAssemblerReferenceGuide》
5、《IAREmbeddedWorkbenchIDEUserGuide》
6、《IARC/C++DevelopmentGuideCompilingandlinking》
7、LPC2103的datasheet
8、《ARMandThumb-2InstructionSetQuickReferenceCard》
9、《Thumb16-bitInstructionSetQuickReferenceCard》
10、《ARM7TDMI-STechnicalReferenceManual》
转自Tony嵌入式论坛,地址:
文中所示代码下载:
(稍后提供)
下面先探讨一下LPC2103这个芯片的堆栈空间。
我们在建立该项目之前,就对编译器设置了该工程的endianmode为little。
Endian模式不清楚的就去查查《ARM体系结构与编程》,这里就不多说了。
在以ARM7TDMI-S为内核的ARM芯片,其堆栈增长方向是由高地址向低地址增长。
因此我们给每个任务分配的堆栈也必须按照这样的方式进行“push”和“pop”操作。
即可这样理解:
1、当我们在任务堆栈上执行类似的“push”操作时,首先将所需保存的数据压入到堆栈中,然后再对“任务的SP寄存器的值“-4。
(这里-4的偏移量是因为我的整个移植代码都是在ARM指令集下进行的所以偏移量的设置是4);
2、当我们在任务堆栈上执行类似的“pop“操作时,首先将堆栈中的数据弹出堆栈,然后再对”任务的SP寄存器的值“+4。
(这里+4的偏移量是因为我的整个移植代码都是放在ARM指令集下进行的,所以偏移量的设置是4);
任务堆栈的初始化过程分析
一、OSTaskStkInit函数
好了,现在就可以进行我们的代码移植分析了。
首先是对任务堆栈的初始化工作,代码如下:
#defineARM_MODE_ARM0x00000000
#defineARM_MODE_THUMB0x00000020
#defineARM_SVC_MODE_THUMB(0x00000013L+ARM_MODE_THUMB)
#defineARM_SVC_MODE_ARM(0x00000013L+ARM_MODE_ARM)
OS_STK*OSTaskStkInit(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT16Uopt)
{
OS_STK*stk;
INT32Utask_addr;
opt=opt;
stk=ptos;
task_addr=(INT32U)task&~1;
*(stk)=(INT32U)task_addr;//PC
*(--stk)=(INT32U)0x14141414L;//R14
*(--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
if((INT32U)task&0x01){
*(--stk)=(INT32U)ARM_SVC_MODE_THUMB;
}else{
*(--stk)=(INT32U)ARM_SVC_MODE_ARM;
}
return(stk);
}
现在一句一句来分析:
1、opt=opt;
stk=ptos;
这两句放到这里是没有任何实际意义,只是为了防止编译器报警;
2、task_addr=(INT32U)task&~1;这一句可能很多人都不理解,干嘛还要对任务的地址&~1呢?
答:
实际上任务的地址(就是调用函数的起始地址)是要在LDMFD写入到PC的,而对于LDM的操作,写入PC的最后1bit会影响到处理器是否切换到Thumd模式下的,我在这里将任务地址的最后1bit清0,是为了保证任务在ARM模式下进行工作的。
因为在部分ARM指令和Thumd指令混合编译的情况下,可能会导致整个uCOS系统崩溃的。
加上这一句就能很好的保证系统在进行任务切换的时候,所有指令均能在ARM指令系统下工作。
我说了是不是大家就很清楚了呢?
3、*(--stk)=(INT32U)0x14141414L;//R14
很多朋友可能会在这一句上有非常大的疑问了,R14寄存器不是LR寄存器么?
这个可以存储子程序的返回地址或者是存储分子语句的返回地址啊?
怎么这里会对R14赋值成(INT32U)0x14141414L。
这样做会不会让整个系统工作不正常啊?
答:
回答这个问题,就要用uCOS对于任务的定义了,在uCOS处理机制中,所有的任务都是一个包含死循环的函数,死循环中是根本就不会有返回地址的了,所以这里随便写入一个值都不会影响到任务的正常运行的。
4、对R12和R1的初始化,大家可能又要问了,干嘛这样赋值啊?
答:
实际上对任务的初始化时在任务运行之前进行的,一个任务都还没有运行,他有怎么可能有影响R1到R12的值了,对于这点大家可以想象一下一个前后台的系统在程序刚开始执行的时候是否会需要使用到这些寄存器呢?
所以初始化这些寄存器根本就不会对任务运行有任何影响。
5、*(--stk)=(INT32U)p_arg;这个是对R0寄存器的初始化。
为什么要这样做呢?
相信熟悉IAREWARM的朋友可能会知道(当然不知道的话,建议去做一个简单的测试程序),所有函数的第一个形式参数都是传递给R0的。
P_arg就是我们建立的任务的第一个形参,尽管很多朋友在这个形参的设计上没有使用到。
注意:
实际上MDK也是用R0作为函数的第一个形参压栈处理的。
6、对于最后的最后一个参数的初始化时对当前任务运行的异常模式和ARM或Thumd模式的一个压入堆栈的操作。
7、还有的朋友可能会问道,怎么没有看到对SP寄存器的保存呢?
答:
SP寄存器的保存实际是将SP寄存器的值保存到TCB的数据结构中了,所以这里就不再需要对SP进行重复保存了。
在这个函数完成以后,任务的任务堆栈就像下面的方式建立了:
低地址
CPSR
ARM_SVC_MODE_ARM
^
|
|
|
|
|
|
R0
(INT32U)p_arg
R1
0x01010101L
R2
0x02020202L
R3
0x03030303L
R4
0x04040404L
R5
0X05050505L
R6
0X06060606L
R7
0X07070707L
R8
0X08080808L
R9
0X09090909L
R10
0X10101010L
R11
0X11111111L
R12
0X12121212L
LR
0X14141414L
高地址
PC
(INT32U)task&~1
转自Tony嵌入式论坛,地址: