打印

keil 处理可重入函数的探讨

[复制链接]
4519|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
songhere|  楼主 | 2012-4-22 21:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在程序设计中,变量具体可以分为四种类型: 全局变量 ,静态全局变量,局部变量,静态局部变量。这几种变量类型对函数的可重入产生的重大的影响,因为不同的编译器采用不同的策略。
    针对51的存储区有限,keil c51因此有了覆盖和共享的处理方法。
共享:共享是针对全局变量或静态变量而言的,对全局变量定义后就对其分配了内存,其他变量不会覆盖这一地址,在任何函数或者程序中都可以共享该变量的内存。
覆盖:如果一个程序不再被调用,也不由其他的程序调用,在其他的程序运行之前程序也不在运行,那么这个程序的局部变量可以放在与其他的程序完全相同的RAM空间,这就是覆盖。
    所以说C51编译器并不是真正的C编译器。

    先说一下keilc51覆盖技术
1.局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
2.在编译链接时,即已经完成局部变量的定位;
3.如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。


     在单任务编程下,重入似乎不是个问题,因为函数会一直执行下去,就算被中断打断,中断服务程序保存寄存器,中断执行完毕后又恢复环境(注意到keil c51默认这样处理:中断中局部变量会采用静态分配内存的方式,不会与函数的局部变量覆盖),函数不会存在重不重入的问题。
在实时操作系统下,就必须考虑函数的重不重入了。
先考虑一下可重入函数与不可重入函数的具体区别。在实时操作系统下,二者的区别并不是执行过程中能否被打断执行。可重入函数在执行过程中可以被打断,内核启动另一个任务,该任务再次调用该函数。不可重入函数在执行过程中可以被打断,但在另一个任务中不得在调用这个函数,除非再次回到原来的任务将这个函数执行完成。
再来考虑可重入函数与不可重入函数的实现:因为全局变量,静态全局变量,静态局部变量会分配到固定的内存地址,这些量在可重入方面只需考虑保护,问题的关键在于动态局部变量的处理,如互相覆盖和入栈。


可重入函数:
首先是据绝对意义上的可重入函数:函数不使用全局变量,只使用存放在寄存器的局部变量。这种函数任意时刻都可以被打断,或再次调用,应为函数的局部变量在被中断时全部被入栈了,等到重新启用时又全部出栈了,例如函数
Unsigne char Min(unsigned char x1 , unsigned char x2)
{
If(x1 > x2)
{ x1 = x2;}
Return   x1;
}
另一种函数:不使用全局变量,局部变量部分存放在寄存器,部分存放在固定内存地址(哪怕静态化,独享该内存),他总是不可重入的,分析如下:静态的局部变量a,在第一进入函数后改变,中断第二次进入又改变,回到第一调用时可能就改变了,存在Bug,不可重入。
第二种可重入函数:函数使用全部变量 ,只使用存放在寄存器的局部变量(keil c51尽量将局部变量保存在寄存器)。 这种函数常常是系统函数,使用临界区代码。对起到标志作用的全局变量进行操作时要关中断,防止中断打断破坏,处理完毕后再开中断。这其实已经不是绝对意义上的可重入函数,但局部变量通过寄存器存放可以避免修改其他函数的局部变量。
第三种可重入函数:函数使用全局变量,局部变量部分存放在寄存器,部分存放在固定内存地址(因为局部变量过多)。这种函数跟第二种很相似,不是真正意义上的可重入。进入函数必须关中断,出去在开中断。值得注意的是:存放在固定内存地址的局部变量需要声明为静态以独享该内存,这样做的目的并不是怕其他函数其他破坏其局部变量(关中断不可能被破坏),而是怕破坏其他函数的局部变量,因为其他函数若有局部变量和其共享一个内存空间,执行其他函数→该函数→其他函数,可能导致其他函数局部变量被破坏。


不可重入函数(前面已经说明这个函数在执行过程中若被打断,不会再次调用,即不可重入)
第一种函数:不使用全局变量,局部变量部分存放在寄存器,部分存放在固定内存地址(若都存放在寄存器就是可重入函数了)。 这种函数不可重入,但存放在固定内存地址的局部变量应该防止互相覆盖,具体原因下面分析。
第二种函数: 不使用全局变量,局部变量都存放在固定内存地址,存放在固定内存地址的局部变量应该防止互相覆盖,具体原因下面分析。不过这种函数很少见,因为keil c51先考虑把局部变量存放在寄存器。
第三种函数: 使用全局变量,局部变量部分存放在寄存器。进入函数不需要关中断保护全局变量,因为这种函数大多是用户函数,全局变量大多不是起标志作用的,一边是传递数据的,即一般是一个函数修改,一个函数读取。
第四种函数:使用全局变量,局部变量部分存放在寄存器,部分存放在固定内存地址,存放在固定内存地址的局部变量应该防止互相覆盖。跟第三种类似。
还有一种函数: 使用全局变量,局部变量都存放在固定内存地址。跟第二种一样少见,都不考虑。
分析不可重入函数存放在固定地址的局部变量不能相互覆盖的问题:
Keil 进行以下处理:如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
任务的基本结构与模式:
   vold TaskA(void*ptr){
              UINT8 vaL_a
               //其他一些变量定义
             do{
                    //实际的用户任务处理代码
                 }while(1)
    }

   void TaskB(void*ptr){
              UINT8 vaLb
             //其他一些变量定义
              do{
                   Funcl()
                 //其他实际的用户任务处理代码
                  }while(1)
    }

  void Funcl(){
          UlNT8 v al_fa
         //其他变量的定义
         //函数的处理代码
    }

    在上面的代码中,TaskATaskB并不存在直接或间接的调用关系,因而其局部变量val_aval_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的RAM空间。这样,当TaskA运行一段时间,改变了val_a后,TaskB取得CPU控制权并运行时,便可能会改变val_b。由于其指向相同的RAM空间,导致TaskA重新取得CPU控制权时,vala的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,Funcl()TaskB有直接的调用关系,因而其局部变量val_faval_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与TaskA或其他任务的局部变量形成可覆盖关系。


     所以得到以下结论:存在直接或间接调用关系的函数局部变量不会覆盖,不存在调用关系的函数局部变量又不能覆盖。
    怎么办? 第一种解决方案:我们只能将非重入函数中不是存放在寄存器中的局部变量全部静态化存放在固定内存地址,这样内存开销就会很大,但要保证程序运行的正确性,只能这么做。

   第二种:局部变量覆盖,通过进入程序就关中断进行保护,这样就不会被打断了,但这样做系统的实时性就会降低很多。 现实情况是根据具体的情况进行决定,具体问题具体分析。

相关帖子

沙发
airwill| | 2012-4-23 13:13 | 只看该作者
分析了一大通, 颇在理, 但是最后的结果并不正确.
第一种解决方案:我们只能将非重入函数中不是存放在寄存器中的局部变量全部静态化存放在固定内存地址,
    这点根本不能解决问题!
Keil C51 编译器采用了一个扩展关键字reentrant作为定义函数时的选项,需要将一个函数定义为可重入函数时,只要在函数后面加上关键字 reentrant 即可。

使用特权

评论回复
板凳
电子三极管| | 2012-4-23 14:05 | 只看该作者
本人有工控、交通类成熟产品全部技术资料,有意向者联系 非诚请勿扰
https://bbs.21ic.com/icview-328904-1-1.html

使用特权

评论回复
地板
madcool| | 2012-4-29 16:43 | 只看该作者
竞争和同步确实是一个很棘手的问题,局部变量在任务切换的时候会入栈不存在因任务切换引发的重入问题,全局变量在不同任务或中断引用时需要加互斥手段,各个操作系统都会提供一系列操作原语,楼主是不是想复杂了?另外在线程函数中定义应该属于局部变量吧?

使用特权

评论回复
5
songhere|  楼主 | 2012-6-4 22:54 | 只看该作者
2楼的应该再看看keil编译器手册。   reentrant 关键字虽然是将函数可重入,但是这里的重入堆栈又不一样,编译器是重新组织了一个堆栈,与先前那个堆栈不是一个空间,那种函数在操作系统函数中依然是不可重入。。。。

使用特权

评论回复
6
airwill| | 2012-6-5 09:14 | 只看该作者
2楼的应该再看看keil编译器手册。   reentrant 关键字虽然是将函数可重入,但是这里的重入堆栈又不一样,编译器是重新组织了一个堆栈,与先前那个堆栈不是一个空间,那种函数在操作系统函数中依然是不可重入。。。。 ...
songhere 发表于 2012-6-4 22:54

的确没有细想一下. 其实这个模拟的堆栈空间就是为还是的局部变量都保存在不同的内存地址中. 如果多个任务同时调用这个函数, 任务切换又发生在这个被调用的函数里. 那么这个模拟堆栈的指针就需要同时跟着任务一起切换才能正常工作.

使用特权

评论回复
7
songhere|  楼主 | 2012-6-6 22:53 | 只看该作者
4楼童鞋,keil c51不是标准的c编译器,局部变量优先分配在寄存器中的,'这样是可以重入的。但51寄存器最多分配3个局部变量,在有局部变量,编译 器就分配到固定内存地址,为了节省内存,不同的函数的局部变量是可能分配在一块的,这并且任务抢占时,是不会入栈的,这是与标准c编译 器不同的地方,也就造成较复杂函数不可重入的原因。。。

使用特权

评论回复
8
hunancjz| | 2015-6-24 10:10 | 只看该作者
本帖最后由 hunancjz 于 2015-6-24 10:12 编辑
songhere 发表于 2012-6-6 22:53
4楼童鞋,keil c51不是标准的c编译器,局部变量优先分配在寄存器中的,'这样是可以重入的。但51寄存器最多 ...

不同的函数的局部变量 不会 分配在一块的。如果不同函数的分配在一块,函数调用就会出错了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

52

帖子

1

粉丝