现在的位置: 首页 > 综合 > 正文

可重入和不可重入——(二)实践部分

2018年03月16日 ⁄ 综合 ⁄ 共 3017字 ⁄ 字号 评论关闭

http://wenku.baidu.com/link?url=Z2D01xtYzwNpftsXHH3TtIl7OmXUpIC1y-LBQHgjP3GztbQ34di1AOOs0cZEcXyxuoyBqXs-HNX8sx4enqi0O0PWeVNmI9I85Wwf9Tqm743

由于每一次调用被reentrant声明的函数都要把函数的参数和内部变量压栈,所以很容易使堆栈区溢出,S52只有256Bytes的data段,一个简单的函数如果有一个参数三个内部变量,则需要压栈4字节以上,这还不包括函数调用堆栈. 
 
reentrant其实并不是适合低端的单片机,keil论坛上有人说对于那些有KB以上RAM的单片机reentrant才适合

BUTCHER提到的问题我一直很奇怪自己怎么没有出现,仔细检查代码才发现我所有的函数都在一开始添加了EA=0;也就是屏蔽了中断,这样就绝对没有函数重载的问题!

http://baike.baidu.com/view/1507600.htm?fr=aladdin

基本上下面的函数是不可重入的
(1)函数体内使用了静态数据结构
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。
把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。
其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。
第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。
第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。//这是临界区保护
第三,不能调用任何不可重入的函数。
第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。
还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!


C51 reentrant (51与其它(PC,ARM)的差异)

http://bbs.21ic.com/icview-40688-1-1.html

如果你想要在C51中使用OS,那么同所有其它要使用OS的C语言一样,函数的重入线程的安全问题,都是需要特别注意的问题.
    C51更有它特别的地方.
    
    在其它环境下(比如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 用来解决函数重入的问题.
    
    
    至于线程安全的问题,跟其它环境下的C程序所要注意的问题,小心对待全局变量以及其它的共享资源,应当就能避免大多数的安全问题.
    
    不要以为你不使用OS,就不会碰到这些问题.
    实际上,如果你的程序有中断处理例程,那么中断例程和你的主程序在某种意义上,就是两个不同的线程.
    它们同OS的线程一样,在从一个例程到另一个例程的切换过程中,都需要保存现场环境,在退出时,恢复环境.  


C51:***WARNING L15: MULTIPLE
CALL TO SEGMENT

http://www.cnblogs.com/CuriosityWzk/archive/2011/12/25/2301090.html

发生了重入!

想要避免这种情况的方法 

一.用reentrant使函数重入 
关于reentrant的说明: 
1,重入函数不能传递bit类型的参数和变量; 
2,重入函数建立的是模拟堆栈区,所以不使用一般函数位于存储模式默认空间的可覆盖式堆栈,而是在同一空间从顶端另行分配一个非覆盖式的重入堆栈。 
     small 默认空间是 data; 
     compact 默认空间是 pdata; 
     large 默认空间是 xdata; 
3,由于要保存参数和局部变量,所以会消耗很大的栈空间;尽量少用这种模式; 
4、在同一程序中可以定义和使用不同存储器模式的重入函数,任意模式的重入函数不能调用不同存储器模式的重入函数,但可以调用普通函数。 
5、实际参数可以传递给间接调用的重入函数。无重入属性的间接调用函数不能包含调用参数。 

二.如果空间多的话,可以定义两个同功能的函数,分别在中断和中断外调用 
别的方法没研究出来,嘿嘿~~~对了 我建议用第二种方法好点,第一种有些限制,不爽~~

三.主程序调用该函数时禁止中断。

可以在该函数被调用时用#pragma disable语句来实现禁止中断的目的。必须使用OVERLAY指令将该函数从覆盖分析中除去。

        也可以用EA=1,EA=0来开关中断 

最后,WARNING L15: MULTIPLE CALL TO SEGMENT 

     这个问题必须注意,可能引起程序冲突,假设你用于自动化领域,则可能导致信号产生尖峰。 产生这一警告的一个根源是:你在主循环 里调用了一个函数(如aaa),而在中断服务函数里,你用调用了这个函数(如aaa)。这样当主循环运行到该函数中时,一旦产生中断,则在中断里又再次调用该函数!这时,很可能出错! 避免这种情况的方法很多:如,在进中断的时候置需调用该函数的标志,而在主循环中调用该函数

抱歉!评论已关闭.