KEIL可从入函数讲解.docx
《KEIL可从入函数讲解.docx》由会员分享,可在线阅读,更多相关《KEIL可从入函数讲解.docx(39页珍藏版)》请在冰豆网上搜索。
KEIL可从入函数讲解
《KEILC51可重入函数及模拟栈浅析》
一文的理解
此片文章主要是关于前辈文章的理解,没有超越前辈的地方,前辈文章链接如下:
一下是我对于这篇文章的理解,如果有错希望大家能够指出并改正,鄙人QQ:
384710930
在进入代码阅读之前,本人想说说我的见解:
本人觉得先读C?
ADDXBP和C?
XBPOFF函数对于理解该文有比较好效果,但是鉴于前辈的顺序不是如此,所以我就不加更改!
关键在于下面两个函数!
文字功底不行,请大家见谅,一下附录原文和前辈文章里没有写全的代码!
附录(原文):
KEILC51可重入函数及模拟栈浅析(转)
原文:
摘要:
本文较详细的介绍了keilc51可再入函数和模拟堆栈的一些概念和实现原理,通过一个简单的程序来剖析keilc51在大存储模式下可重入函数的调用过程,希望能为keilc51和在51系列单片机上移植嵌入式实时操作系统的初学者提供一些帮助。
1、关于可重入函数(可再入函数)和模拟堆栈(仿真堆栈)
“可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。
可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。
”(摘自嵌入式实时操作系统uC/OS-II)
在理解上述概念之前,必须先说一下keilc51的“覆盖技术”。
(采用该技术的原因请看附录中一网友的解释)
(1)局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
(2)在编译链接时,即已经完成局部变量的定位;
(3)如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
正是由于以上的原因,在KeilC51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重入的。
举个例子:
在上面的代码中,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位寄存器,只能指向内部),而且是“向上生长”型的(从低地址向高地址),而模拟栈是“向下生长”型的。
1、可重入函数参数传递过程剖析
在进入剖析之前,先简单讲讲c51函数调用时参数是如何传递的。
简单来说,参数主要是通过寄存器R1~R7来传递的,如果在调用时,参数无寄存器可用或是采用了编译控制指令“NOREGPARMS”,则参数的传递将发生在固定的存储器区域,该存储器区域称为参数传递段,其地址空间取决于编译时所选择的存储器模式。
利用51单片机的工作寄存器最多传递3个参数,如表2所示。
表二
举两个例子:
func1(inta):
“a”是第一个参数,在R6,R7中传递;
func2(intb,intc,int*d):
“b”在R6,R7中传递,“c”在R4,R5中传递,“*d”则在R1,R2,R3中传递。
至于函数的返回值通过哪些寄存器或是什么方法传递这里就不说了,大家可以看看c51的相关文档或是书籍。
好了,接下来我们开始剖析一个简单的程序,代码如下:
程序很简单,废话少说,下面跟我一起看看c51翻译成的汇编语言是什么样子的(大存储模式下largeXDATA)。
说明:
模拟栈指针最初在startup.a51中初始化为0xFFFF+1;由以上汇编代码可以看出参数是从右往左扫描的。
接下来看看fun的汇编代码:
(很长,大家耐心看吧,有些可以跳过的)
说明:
栈结构如下
接下来说明两个重点子函数C_ADDXBP和C_XBPOFF
终于到尾声了,最后重点说明啦~~~
模拟堆栈是向下生长的,C_XBP最初等于0xffff+1,那么请看下面这句
其实是这样:
加0xffff相当与减1,加0xfffe相当与减2,加0xfffd相当于减4。
。
。
。
。
。
为啥,就不用说了吧:
)
结束语:
经过了几天的研究,终于写了个总结报告,算是自己的一点小小成就吧,错误之处在所难免,希望能够同大家一起讨论问题,共同进步。
参考文献:
1、徐爱钧,彭秀华《单片机高级语言C51windows环境编程与应用》电子工业出版社2001
2、彭光红,构造一个51单片机的实时操作系统。
附录:
在其它环境下(比如PC,比如ARM),函数重入的问题一般不是要特别注意的问题.只要你没有使用static变量,或者指向static变量的指针,一般情况下,函数自然而然地就是可重入的.
但C51不一样,如果你不特别设计你的函数,它就是不可重入的.
引起这个差别的原因在于:
一般的C编译器(或者更确切点地说:
基于一般的处理器上的C编译器),其函数的局部变量是存放于堆栈中的,而C51是存放于一个可覆盖的(数据)段中的.
至于C51这样做的原因,不是象有些人说的那样,为了节约内存.事实上,这样做根本节约不了内存.理由如下:
1)如果一个函数func1调用另一个函数func2,那么func1,func2的局部变量根本就不能是同一块内存.C51还是要为他们分配不同的RAM.这跟使用堆栈相比,节约不了内存.
2)如果func1,func2不是在一个调用链上,那么C51可以通过覆盖分析,让它们的局部变量共享相同的内存地址.但这样也不会比使用堆栈节约内存.因为既然它们是在不同的调用链上,那么当其中一个函数运行时,那么另外一个函数必然不在其生命期内,它所占用的堆栈也已释放,归还给系统.
真实的原因(C51使用覆盖段作为局部变量的存放地的原因)是:
51的指令系统没有一个有效的相对寻址(变址寻址)的指令,这使得使用堆栈作为变量的代价太过昂贵.
使用堆栈存放变量的一般做法是:
进入函数时,保留一段堆栈空间,作为变量的存放空间,用一个可作为基址寻址的寄存器指向这个空间,通过加上一个偏移量,就可以访问不同的变量了.
例如:
MOVEAX,[EBP+14];X86指令
LDRR0,[R12,#14];ARM指令
都可以很好的解决这个问题.
但51缺少这样的指令.
*其实,51中还是有2个可变址寻址的指令的,但不适合访问堆栈的局部变量这样的场合.
MOVCA,@A+DPTR
MOVCA,@A+PC
所以,C51有个特别的关键字:
reentrant用来解决函数重入的问题.
附录(所有代码):
Main.c
#include
intfun(chara,charb,charc,chard)reentrant;
voidmain()
{
inti;
i=fun(1,2,3,4);
}
intfun(chara,charb,charc,chard)reentrant
{
intj1,j2;
j1=a+b+c+d;
j2=j1+10;
returnj2;
}
STARTUP.A51(启动文件):
$NOMOD51
;------------------------------------------------------------------------------
;ThisfileispartoftheC51Compilerpackage
;Copyright(c)1988-2005KeilElektronikGmbHandKeilSoftware,Inc.
;Version8.01
;
;***<<>>***
;------------------------------------------------------------------------------
;STARTUP.A51:
Thiscodeisexecutedafterprocessorreset.
;
;TotranslatethisfileuseA51withthefollowinginvocation:
;
;A51STARTUP.A51
;
;TolinkthemodifiedSTARTUP.OBJfiletoyourapplicationusethefollowing
;Lx51invocation:
;
;Lx51yourobjectfilelist,STARTUP.OBJcontrols
;
;------------------------------------------------------------------------------
;
;User-definedPower-OnInitializationofMemory
;
;WiththefollowingEQUstatementstheinitializationofmemory
;atprocessorresetcanbedefined:
;
;IDATALEN:
IDATAmemorysize<0x0-0x100>
;Note:
Theabsolutestart-addressofIDATAmemoryisalways0
;TheIDATAspaceoverlapsphysicallytheDATAandBITareas.
IDATALENEQU80H
;
;XDATASTART:
XDATAmemorystartaddress<0x0-0xFFFF>
;TheabsolutestartaddressofXDATAmemory
XDATASTARTEQU0
;
;XDATALEN:
XDATAmemorysize<0x0-0xFFFF>
;ThelengthofXDATAmemoryinbytes.
XDATALENEQU0
;
;PDATASTART:
PDATAmemorystartaddress<0x0-0xFFFF>
;TheabsolutestartaddressofPDATAmemory
PDATASTARTEQU0H
;
;PDATALEN:
PDATAmemorysize<0x0-0xFF>
;ThelengthofPDATAmemoryinbytes.
PDATALENEQU0H
;
;
;------------------------------------------------------------------------------
;
;ReentrantStackInitialization
;
;ThefollowingEQUstatementsdefinethestackpointerforreentrant
;functionsandinitializedit:
;
;StackSpaceforreentrantfunctionsintheSMALLmodel.
;IBPSTACK:
EnableSMALLmodelreentrantstack
;StackspaceforreentrantfunctionsintheSMALLmodel.
IBPSTACKEQU0;setto1ifsmallreentrantisused.
;IBPSTACKTOP:
EndaddressofSMALLmodelstack<0x0-0xFF>
;Setthetopofthestacktothehighestlocation.
IBPSTACKTOPEQU0xFF+1;default0FFH+1
;
;
;StackSpaceforreentrantfunctionsintheLARGEmodel.
;XBPSTACK:
EnableLARGEmodelreentrantstack
;StackspaceforreentrantfunctionsintheLARGEmodel.
XBPSTACKEQU0;setto1iflargereentrantisused.
;XBPSTACKTOP:
EndaddressofLARGEmodelstack<0x0-0xFFFF>
;Setthetopofthestacktothehighestlocation.
XBPSTACKTOPEQU0xFFFF+1;default0FFFFH+1
;
;
;StackSpaceforreentrantfunctionsintheCOMPACTmodel.
;PBPSTACK:
EnableCOMPACTmodelreentrantstack
;StackspaceforreentrantfunctionsintheCOMPACTmodel.
PBPSTACKEQU0;setto1ifcompactreentrantisused.
;
;PBPSTACKTOP:
EndaddressofCOMPACTmodelstack<0x0-0xFFFF>
;Setthetopofthestacktothehighestlocation.
PBPSTACKTOPEQU0xFF+1;default0FFH+1
;
;
;------------------------------------------------------------------------------
;
;MemoryPageforUsingtheCompactModelwith64KBytexdataRAM
;CompactModelPageDefinition
;
;DefinetheXDATApageusedforPDATAvariables.
;PPAGEmustconformwiththePPAGEsetinthelinkerinvocation.
;
;Enablepdatamemorypageinitalization
PPAGEENABLEEQU0;setto1ifpdataobjectareused.
;
;PPAGEnumber<0x0-0xFF>
;uppermost256-byteaddressofthepageusedforPDATAvariables.
PPAGEEQU0
;
;SFRaddresswhichsuppliesuppermostaddressbyte<0x0-0xFF>
;most8051variantsuseP2asuppermostaddressbyte
PPAGE_SFRDATA0A0H
;
;
;------------------------------------------------------------------------------
;StandardSFRSymbols
ACCDATA0E0H
BDATA0F0H
SPDATA81H
DPLDATA82H
DPHDATA83H
NAME?
C_STARTUP
?
C_C51STARTUPSEGMENTCODE
?
STACKSEGMENTIDATA
RSEG?
STACK
DS1
EXTRNCODE(?
C_START)
PUBLIC?
C_STARTUP
CSEGAT0
?
C_STARTUP:
LJMPSTARTUP1
RSEG?
C_C51STARTUP
STARTUP1:
IFIDATALEN<>0
MOVR0,#IDATALEN-1
CLRA
IDATALOOP:
MOV@R0,A
DJNZR0,IDATALOOP
ENDIF
IFXDATALEN<>0
MOVDPTR,#XDATASTART
MOVR7,#LOW(XDATALEN)
IF(LOW(XDATALEN))<>0
MOVR6,#(HIGH(XDATALEN))+1
ELSE
MOVR6,#HIGH(XDATALEN)
ENDIF
CLRA
XDATALOOP:
MOVX@DPTR,A
INCDPTR
DJNZR7,XDATALOOP
DJNZR6,XDATALOOP
ENDIF
IFPPAGEENABLE<>0
MOVPPAGE_SFR,#PPAGE
ENDIF
IFPDATALEN<>0
MOVR0,#LOW(PDATASTART)
MOVR7,#LOW(PDATALEN)
CLRA
PDATALOOP:
MOVX@R0,A
INCR0
DJNZR7,PDATALOOP
ENDIF
IFIBPSTACK<>0
EXTRNDATA(?
C_IBP)
MOV?
C_IBP,#LOWIBPSTACKTOP
ENDIF
IFXBPSTACK<>0
EXTRNDATA(?
C_XBP)
MOV?
C_XBP,#HIGHXBPSTACKTOP
MOV?
C_XBP+1,#LOWXBPSTACKTOP
ENDIF
IFPBPSTACK<>0
EXTRNDATA(?
C_PBP)
MOV?
C_PBP,#LOWPBPSTACKTOP
ENDIF
MOVSP,#?
STACK-1
;ThiscodeisrequiredifyouuseL51_BANK.A51withBankingMode4
;CodeBanking
;SelectBank0forL51_BANK.A51Mode4
#if0
;Initializebankmechanismtocodebank0whenusingL51_BANK.A51withBankingMode4.
EXTRNCODE(?
B_SWITCH0)
CALL?
B_SWITCH0;initbankmechanismtocodebank0
#endif
;
LJMP?
C_START
END
编译结果:
126:
?
C_STARTUP:
LJMPSTARTUP1
127:
128:
RSEG?
C_C51STARTUP
129:
130:
STARTUP1:
131:
132:
IFIDATALEN<>0
C:
0x000002009ALJMPSTARTUP1(C:
009A)
9:
{
10:
intj1,j2;
11:
j1=a+b+c+d;
C:
0x000390FFFFMOVDPTR,#0xFFFF
C:
0x0006120126LCALLC?
ADDXBP(C:
0126)
C:
0x0009EBMOVA,R3
C:
0x000AF0MOVX@DPTR,A
C:
0x000B90FFFFMOVDPTR,#0xFFFF
C:
0x000E120126LCALLC?
ADDXBP(C:
0126)
C:
0x0011EDMOVA,R5
C:
0x0012F0MOVX@DPTR,A
C:
0x001390FFFFMOVDPTR,#0xFFFF
C:
0x0016120126LCALLC?
ADDXBP(C:
0126)
C:
0x0019EFMOVA,R7
C:
0x001AF0MOVX@DPTR,A
C:
0x001B90FFFCMOVDPTR,#0xFFFC
C:
0x001E120126LCALLC?
ADDXBP(C:
0126)
12:
j2=j1+10;
C:
0x0021900005MOVDPTR,#0x0005
C:
0x002412014ALCALLC?
XBPOFF(C:
014A)
C:
0x0027E0MOVXA,@DPTR
C:
0x0028FFMOVR7,A
C:
0x002933RLCA
C:
0x002A95E0SUBBA,ACC(0xE0)