请参阅:堆栈指针寄存器SP详细信息和堆栈角色
作者:蓝色多橙色
URL:https://blog.csdn.net/QQ _ 36588941/article/details/89873633? UTM _ source=appapp _ version=4. 16.0 code=app _ 1562916241 ulink id=usr1mkqgl 919 blen
栈的实现方法ARM深入了解三个寄存器三级流水线三个寄存器栈的总体作用1 .保护现场2 .传递参数3 .临时变量保存在栈中
如何实现堆栈
随机存储器区组成一块区域作为堆栈区域,可以将数据逐个依次存储(压入)到该区域,这个过程称为“推栈”(push )。 通常,调整在指针65http://www.Sina.com/-堆栈pointer (上进行)上进行,SP始终指向包含最后推入堆栈的数据的数据单元(堆栈顶部)。 从堆栈读取数据时,按照堆栈指针指向的堆栈单元读取堆栈数据。 此过程称为“弹出窗口”(pop ),每次弹出数据时,SP都会反向调整,以实现后进先出原则。
堆栈是计算机中广泛使用的技术,常用于根据堆栈所具有数据的LIFO特性,保存中断断点、保存子程序调用的返回点、保存CPU字段数据等,在程序之间传递参数
ARM处理器通常使用堆栈指针 SP作为堆栈指针(SP )。 对于不同模式,ARM处理器总共有六个堆栈指针(SP ),其中用户模式和系统模式共享一个SP,每个异常模式都有自己的R13寄存器(SP )。 它们通常是指与每种模式对应的专用堆栈。 也就是说,使用ARM处理器,用户程序可以有六个不同的堆栈空间。 这些堆栈指针分别为R13、R13_svc、R13_abt、R13_und、R13_irq、R13_fiq,如表2-3堆栈指针的寄存器所示。
为了更准确地描述堆栈,根据"堆栈"操作时堆栈指针的增减方向,使堆栈向"增量堆栈" (sp向大的数值方向变化)和"减少堆栈" (sp向小的数值方向变化)变化此外,根据SP指针指向的存储单元是否包含堆栈数据,将堆栈分为“完整堆栈”(SP指针单元包含堆栈有效数据)和“空堆栈”(SP指针单元中的堆栈有效数据)
这两种组合有四种类型的堆栈方法:全增量、空增量、全增量和空增量。
ARM处理器的堆栈操作非常灵活,支持这四种堆栈。
ARM处理器的R13用作SP。 不使用堆栈时,R13也可以作为通用数据寄存器使用。
深入了解ARM三个寄存器深入了解ARM三个寄存器对编程和操作系统移植有很大的好处。
三级管道
PC表示程序计数器,流水线使用3个阶段,因此指令分3个阶段执行。
1 .取指(从内存加载指令);
2 .解码(标识要执行的指令);
3 .执行(处理指令并将结果写回寄存器)。
r15(PC )始终指向“手指在取”命令
ARM指令是三级流水线,在取、取、运行时同时运行,如果指向当前PC取的地址,则假设cpu取的指令地址为PC-4,ARM状态下1个指令占4字节
突然发生中断时,保存的是PC的地址
这样,如果在返回时返回PC,就会发现中途没有执行命令,所以使用SUB pc lr-irq #4。
三个寄存器1,堆栈指针r13(sp )。 每个异常模式都有自己的r13,通常是指专用于异常模式的堆栈。 也就是说,五种异常模式,非异常模式(用户模式和系统模式)都有各自的堆栈,并在不同的堆栈指针上创建索引。 这样,ARM进入异常模式时,程序可以将一般的通用寄存器推入堆栈,返回时弹出堆栈,保证程序在各种模式下的状态完整性。
2、连接寄存器r14(lr )在每种模式下r14都有自己的版本组,有两个特殊功能。
(1)保存子程序的返回地址。 使用BL或BLX时,跳转指令自动将返回地址放入r14中; 子程序通过将r14复制到PC中来实现返回,通常使用以下命令之一:
MOV PC,LR
BX LR
通常子程序是这样写的,保证子程序中也可以调用子程序。
stmfd sp!{lr}
.
ldmfd sp!{pc}
(2)当出现异常时,异常模式下的r14保持异常恢复地址,以使得r14能够以堆栈方式处理嵌套中断。
3、程序计数器r15(PC ):PC有读写限制。 如果未超出读限制,则读取的值是命令的地址加上8字节,ARM命令始终按字对齐,因此bit[1:0]始终为00。 用str或stm保存PC时,偏移可能是8或12等其他值。 从V3开始,写入bit[1:0]的值将被忽略。 从V4开始,写入r15的bit[1:0]必须为00。 如果不是00,结果是无法预测的。
ARM处理器使用流水线来加快处理器指令流,同时执行多个操作,并改进处理和内存系统之间的操作
加流畅,连续,能提供0.9MIPS/MHZ的指令执行速度。 栈的整体作用 1. 保护现场现场/上下文相当于案发现场,总有一些案发现场,要记录下来,否则被别人破坏,便无法恢复。而此处说的现场,是指CPU运行时,用到的一些寄存器,比如r0,r1等,对于这些寄存器的值,如果不保存而直接跳转到子函数中执行,其很可能被破坏,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场暂时保存(入栈push),等调用函数执行完毕后出栈(pop)再恢复现场。这样CPU就可以正确的继续执行了。
保存寄存器的值,一般用push指令,将对应的某些寄存器的值,一个个放到栈中,即所谓的压栈。然后待被调用的子函数执行完毕后再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。
其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,之前的pc值存在lr中),在子程序执行完毕后,再把栈中的lr值pop出来,赋值给pc,这样就实现了子函数的正确的返回。
2. 传递参数C语言函数调用时,会传给被调用函数一些参数,对于这些C语言级别参数,被编译器翻译成汇编语言时,要找个地方存放下来,并且让被调用函数能访问,否则没法传递。找个地方存放下来分2种情况。一是,本身传递的参数不多于4个,可以通过寄存器传送。因为在前面的保存现场动作中,已经保存好对应的寄存器的值,此时这些寄存器是空闲的,可以供我们使用存放参数。二是,参数多于4个,寄存器不够用,就得用栈。
3. 临时变量保存在栈中这些临时变量包括函数的非静态局部变量以及编译器自动生成的其他临时变量
举例分析C语言函数调用如何使用栈
上面的解释有些抽象,此处再用例子简单说明一下,就容易明白了:
用arm-inux-objdump –d u-boot dump_u-boot.txt得到dump_u-boot.txt文件。该文件是包含了u-boot可执行汇编代码,从中我们可以看到相应C程序对应的汇编代码。
下面贴出两个函数的汇编代码,一个是clock_init,另一个是与clock_init在同一C源文件中的函数CopyCode2Ram:
33d0091c: CopyCode2Ram:33d0091c: e92d4070 push {r4, r5, r6, lr}33d00920: e1a06000 mov r6, r033d00924: e1a05001 mov r5, r133d00928: e1a04002 mov r4, r233d0092c: ebffffef bl 33d008f0 b BootFrmNORFlash......33d00984: ebffff14 bl 33d005dc nand_read_ll......33d009a8: e3a00000 mov r0, #0 ; 0x033d009ac: e8bd8070 pop {r4, r5, r6, pc}33d009b0:clock_init:33d009b0: e3a02313 mov r2, #1275068416 ;0x4c00000033d009b4: e3a03005 mov r3, #5 ; 0x533d009b8: e5823014 str r3,......33d009f8: e1a0f00e mov pc, lr(1) 先分析clock_init对应的汇编代码,可以看到该函数第一行
:33d009b0: e3a02313 mov r2, #1275068416 ;0x4c000000
没有我们期望的push指令,即没有将一些寄存器的值放入栈。这是因为,clock_init用到的r2,r3等寄存器,和前面调用clock_init前用到的寄存器r0,没有冲突,故此处不用push保存,有个寄存器要注意,r14,即lr,前面调用clock_init时,用的bl指令,所以会自动把跳转时的pc值赋值给lr,所以也不需要push将PC值保存到栈。而clock_init对应的汇编代码最后一行: 33d009f8: e1a0f00e mov pc, lr 就是我们常见的mov pc,lr,把lr值,即之前保存的函数调用时的PC值,赋值给现在的PC,这样便实现了函数的正确返回,即返回到了函数调用时下一个指令的位置。CPU可以继续执行原先函数内剩下的代码。
(2) CopyCode2Ram对应汇编代码第一行:33d0091c: e92d4070 push {r4, r5, r6, lr}
就是我们所期望的,用push保存r4,r5,r6,lr,是因为此函数还包括其他函数调用
:33d0092c: ebffffef bl 33d008f0 b BootFrmNORFlash……
33d00984: ebffff14 bl 33d005dc nand_read_ll
……
也用到bl指令,会改变我们最开始进入clock_init时的lr值,所以也要push暂时保存起来。
而对应地,CopyCode2Ram最后一行:33d009ac: e8bd8070 pop {r4, r5, r6,pc}是把之前push的值给pop出来,还给对应的寄存器,其中最后一个是将开始push的lr的值pop出来赋给PC,实现了函数的返回。另外我们注意到,CopyCode2Ram的倒数第二行:33d009a8: e3a00000 movr0, #0 ; 0x0 是把0赋值给r0寄存器,就是我们说的返回值的传递,此处的返回值为0,也对应着C代码中的“ return 0”。
当然也可以用其他暂时空闲没有用到的寄存器来传递返回值。
对于使用哪个寄存器来传递返回值,是根据ARM的APCS寄存器的使用约定而设计的,最好按照其约定的来处理,不要随便改变它。这样程序将更加规范。