您的位置 首页 报告

KEIL C51可重入函数及模仿栈浅析

重入函数,又叫再入函数,是一种可以在函数体内直接或间接调用其自身的一种函数。再入函数可被递归调用,无论何时,包括中断函数在内的任何

重入函数,又名再入函数,是一种能够在函数体内直接或直接调用其本身的一种函数。

再入函数可被递归调用,不管何时,包括中止函数在内的任何函数都能够调入。再入函数在C51编译时运用的是模仿栈

函数阐明:函数名(方式参数表) reentrant

留意事项:

1、再入函数不能传递bit类型参数。

2、与PL/M51兼容的函数不能具有reentrant特点,这样也不能调用再入函数。

3、在编译时:再入函数树立的是模仿仓库区,small形式下模仿仓库区坐落idata区,compact形式下模仿仓库区坐落pdata区,large形式下模仿仓库区坐落xdata区。

4、在同一程序中能够界说和运用不同存储器形式的再入函数,恣意形式的再入函数不能调用不同存储器形式的再入函数,但能够调用一般函数。

5、实践参数能够传递给直接调用的再入函数。无再入特点的直接调用函数不能包括调用参数。

—————————————————————————————————-----

KEIL C51可重入函数及模仿栈浅析

  摘要:本文较具体的介绍了keil c51可再入函数和模仿仓库的一些概念和完结原理,经过一个简略的程序来剖析keil c51在大存储形式下可重入函数的调用进程,期望能为keil c51和在51系列单片机上移植嵌入式实时操作体系的初学者供给一些协助。

1、关于可重入函数(可再入函数)和模仿仓库(仿真仓库)

“可重入函数能够被一个以上的使命调用,而不用忧虑数据被损坏。可重入函数任何时候都能够被中止,一段时间今后又能够运转,而相应的数据不会丢掉。”(摘自嵌入式实时操作体系uC/OS-II)

在了解上述概念之前,有必要先说一下keil c51的“掩盖技能”。(选用该技能的原因请看附录中一网友的解说)

(1)局部变量存储在大局RAM空间(不考虑扩展外部存储器的状况);

(2)在编译链接时,即现已完结局部变量的定位;

(3)假如各函数之间没有直接或直接的调用联系,则其局部变量空间便可掩盖。

正是由于以上的原因,在Keil C51环境下,朴实的函数假如不加处理(如添加一个模仿栈),是无法重入的。举个比方:

在上面的代码中,TaskA与TaskB并不存在直接或直接的调用联系,因此它们的局部变量a与b便是能够被相互掩盖的,即它们或许都被定坐落某一个相同的RAM空间。这样,当TaskA运转一段时间,改动了a后,TaskB获得CPU操控权并运转时,便或许会改动b。由于a和b指向相同的RAM空间,导致TaskA从头获得CPU操控权时,a的值现已改动,然后导致程序运转不正确,反过来亦然。另一方面,func()与TaskB有直接的调用联系,因此其局部变量b与c不会被相互掩盖,但也不能确保func的局部变量c不会与TaskA或其他使命的局部变量构成可掩盖联系。

  依据上述剖析咱们很简略就能够判别出TaskA和TaskB这两个函数是不行重入的(当然,func也不行重入)。那么怎么让函数成为可重入函数呢?C51编译器选用了一个扩展关键字reentrant作为界说函数时的选项,需要将一个函数界说为可重入函数时,只需在函数后边加上关键字reentrant即可。

  与非可重入函数的参数传递和局部变量的存储分配办法不同,C51编译器为可重入函数生成一个模仿栈(相对于体系仓库或是硬件仓库来说),经过这个模仿栈来完结参数传递和寄存局部变量。模仿栈以大局变量?C_IBP、?C_PBP和?C_XBP作为栈指针(体系仓库栈顶指针为SP),这些变量界说在DATA地址空间,并且可在文件startup.a51中进行初始化。依据编译时选用的存储器形式,模仿栈区可坐落内部(IDATA)或外部(PDATA或XDATA)存储器中。如表1所示:

表1

  留意:51系列单片机的体系仓库(也叫硬件仓库或惯例栈)总是坐落内部数据存储器中(SP为 8位寄存器,只能指向内部),并且是“向上成长”型的(从低地址向高地址),而模仿栈是“向下成长”型的。

2、可重入函数参数传递进程剖析

  在进入剖析之前,先简略讲讲c51函数调用时参数是怎么传递的。简略来说,参数主要是经过寄存器R1~R7来传递的,假如在调用时,参数无寄存器可用或是选用了编译操控指令“NOREGPARMS”,则参数的传递将发生在固定的存储器区域,该存储器区域称为参数传递段,其地址空间取决于编译时所挑选的存储器形式。运用51单片机的作业寄存器最多传递3个参数,如表2所示。

表二

  举两个比方:

  func1(int a):“a”是第一个参数,在R6,R7中传递;

  func2(int b,int c, int *d):“b”在R6,R7中传递,“c”在R4,R5中传递,“d”则在R1,R2,R3中传递。

  至于函数的返回值经过哪些寄存器或是什么办法传递这儿就不说了,咱们能够看看c51的相关文档或是书本。

  好了,接下来咱们开端剖析一个简略的程序,代码如下:

  程序很简略,废话少说,下面跟我一同看看c51翻译成的汇编语言是什么姿态的(大存储形式下large XDATA)。

  阐明:模仿栈指针开始在startup.a51中初始化为0xFFFF+1;由以上汇编代码能够看出参数是从右往左扫描的。

  接下来看看fun的汇编代码:(很长,咱们耐性看吧,有些能够越过的)

  阐明:栈结构如下

  接下来阐明两个要点子函数C_ADDXBP和C_XBPOFF

  总算到结尾了,最终要点阐明啦~~~

  是向下成长的,C_XBP开始等于0xffff+1,那么请看下面这句

  其实是这样:加0xffff适当与减1,加0xfffe适当与减2,加0xfffd适当于减3。。。。。。为啥,就不用说了吧:)

结束语:

  经过了几天的研讨,总算写了个总结报告,算是自己的一点小小成果吧,过错之处在所难免,期望能够同咱们一同评论问题,共同进步。

附录:

  在其它环境下(比方PC,比方ARM),函数重入的问题一般不是要特别留意的问题。只需你没有运用static变量,或许指向static变量的指针,一般状况下,函数自然而然地便是可重入的。

  但C51不一样,假如你不特别规划你的函数,它便是不行重入的.

  引起这个不同的原因在于:一般的C编译器(或许更确切点地说:根据一般的处理器上的C编译器),其函数的局部变量是寄存于仓库中的,而C51是寄存于一个可掩盖的(数据)段中的。

  至于C51这样做的原因,不是象有些人说的那样,为了节省内存.事实上,这样做底子节省不了内存.理由如下:

  1)假如一个函数func1调用另一个函数func2,那么func1,func2的局部变量底子就不能是同一块内存.C51仍是要为他们分配不同的RAM.这跟运用仓库比较,节省不了内存.

  2)假如func1,func2不是在一个调用链上,那么C51能够经过掩盖剖析,让它们的局部变量同享相同的内存地址.但这样也不会比运用仓库节省内存.由于已然它们是在不同的调用链上,那么当其间一个函数运转时,那么别的一个函数必定不在其生命期内,它所占用的仓库也已开释,归还给体系.

  实在的原因(C51运用掩盖段作为局部变量的寄存地的原因)是:

  51的指令体系没有一个有用的相对寻址(变址寻址)的指令,这使得运用仓库作为变量的价值过分贵重.

  运用仓库寄存变量的一般做法是:

  进入函数时,保存一段仓库空间,作为变量的寄存空间,用一个可作为基址寻址的寄存器指向这个空间,经过加上一个偏移量,就能够拜访不同的变量了.

  例如:

  MOV EAX, [EBP + 14];X86指令

  LDR R0, [R12, #14];ARM指令

  都能够很好的处理这个问题。但51短少这样的指令.

  其实,51中仍是有2个可变址寻址的指令的,但不合适拜访仓库的局部变量这样的场合.

  MOVC A, @A+DPTR

  MOVC A, @A+PC

  所以,C51有个特别的关键字: reentrant用来处理函数重入的问题。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ceping/baogao/258197.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部