zoukankan      html  css  js  c++  java
  • 可重入函数与不可重入函数(转)

    http://www.cnblogs.com/luvi/archive/2008/05/09/1190493.html

    http://www.keil.com/support/man/docs/c51/c51_le_reentrantfuncs.htm


    主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

    也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

    编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

     说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

    示例:假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。

    unsigned int example( int para )

    {

        unsigned int temp;
            Exam = para; //
    (**)
            temp = Square_Exam( );
            return temp;
        }
        此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。

        unsigned int example( int para ) {
            unsigned int temp;
            [申请信号量操作] //(1)
            Exam = para;
            temp = Square_Exam( );
            [释放信号量操作]
            return temp;
        }
        (1)若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。

        保证函数的可重入性的方法:
        在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。
        VxWorks中采取的可重入的技术有:
        * 动态堆栈变量(各子函数有自己独立的堆栈空间)
        * 受保护的全局变量和静态变量
        * 任务变量


    --------------------------------------------------
        在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。满足下列条件的函数多数是不可重入的:
        1) 函数体内使用了静态的数据结构;
        2) 函数体内调用了malloc()或者free()函数;
        3) 函数体内调用了标准I/O函数。

        下面举例加以说明。
        A. 可重入函数
        void strcpy(char *lpszDest, char *lpszSrc)

     {
            while(*lpszDest++=*lpszSrc++);
            *dest=0;
        }

        B.
    不可重入函数1
        charcTemp;//全局变量
        void SwapChar1(char *lpcX, char *lpcY)

     {
            cTemp=*lpcX;
            *lpcX=*lpcY;
            lpcY=cTemp;//
    访问了全局变量
        }

        C. 不可重入函数2
        void SwapChar2(char *lpcX,char *lpcY)

     {
            static char cTemp;//
    静态局部变量
            cTemp=*lpcX;
            *lpcX=*lpcY;
            lpcY=cTemp;//使用了静态局部变量
        }

        问题1,如何编写可重入的函数?
        答:在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

        问题2,如何将一个不可重入的函数改写成可重入的函数?
        答:把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。
        1) 不要使用全局变量。因为别的代码很可能覆盖这些变量值。
        2) 在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”。
        3) 不能调用其它任何不可重入的函数。
        4) 谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

        堆栈操作涉及内存分配,稍不留神就会造成益出导致覆盖其他任务的数据,所以,请谨慎使用堆栈!最好别用!很多黑客程序就利用了这一点以便系统执行非法代码从而轻松获得系统控制权。还有一些规则,总之,时刻记住一句话:保证中断是安全的!

        实例问题:曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?
        unsigned int sum_int( unsigned int base )

    {
            unsigned int index;
            static unsigned int sum = 0; //
    注意,是static类型
            for (index = 1; index <= base; index++)
                sum += index;
            return sum;
        }

        分析:所谓的函数是可重入的(也可以说是可预测的),即只要输入数据相同就应产生相同的输出。这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果需要一个可重入的函数,一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
        将上面的函数修改为可重入的函数,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto类型的变量,函数即变为一个可重入的函数。
        当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。


    A reentrant function may be shared by several processes at the same time. When a reentrant function is executing, another process can interrupt the execution and then begin to execute that same reentrant function.

    Normally, functions in Cx51 cannot be called recursively or in a fashion which causes reentrancy. The reason for this limitation is that function arguments and local variables are stored in fixed memory locations. Recursive calls to the function use the same memory locations. And, in this case, arguments and locals would get corrupted.

    The reentrant function attribute allows you to declare functions that may be reentrant and, therefore, may be called recursively. For example:

    int calc (char i, int b) reentrant  {
      int  x;
      x = table [i];
      return (x * b);
    }
    

    Reentrant functions can be called recursively and can be called simultaneously by two or more processes. Reentrant functions are often required in real-time applications or in situations where interrupt code and non-interrupt code must share a function.

    As in the above example, you may selectively define (using the reentrant attribute) functions as being reentrant. For each reentrant function, a reentrant stack area is simulated in internal or external memory depending upon the memory model used, as follows:

    • Small model reentrant functions simulate the reentrant stack in idata memory.
    • Compact model reentrant functions simulate the reentrant stack in pdata memory.
    • Large model reentrant functions simulate the reentrant stack in xdata memory.

    Reentrant functions use the default memory model to determine which memory space to use for the reentrant stack. You may specify (with thesmall, compact, and large function attributes) which memory model to use for a function. Refer toSpecifying the Memory Model for a Function for more information about memory models and function declarations.

    The following rules apply to functions declared with the reentrant attribute.

    • bit type function arguments may not be used. Local bit scalars are also not available. The reentrant capability does not support bit-addressable variables.
    • Reentrant functions must not be called from alien functions.
    • Reentrant function cannot use the alien attribute specifier to enable PL/M-51 argument passing conventions.
    • A reentrant function may simultaneously have other attributes like using andinterrupt and may include an explicit memory model attribute (small,compact, large).
    • Return addresses are stored in the 8051 hardware stack. Any other required PUSH and POP operations also affect the 8051 hardware stack.
    • Reentrant functions using different memory models may be intermixed. However, each reentrant function must be properly prototyped and must include its memory model attribute in the prototype. This is necessary for calling routines to place the function arguments in the proper reentrant stack.
    • Each of the three possible reentrant models contains its own reentrant stack area and stack pointer. For example, ifsmall and large reentrant functions are declared in a module, bothsmall and large reentrant stacks are created along with two associated stack pointers (one for small and one for large).

    The reentrant stack simulation architecture is inefficient, but necessary due to a lack of suitable addressing methods available on the 8051. For this reason, use reentrant functions sparingly.

    The simulated stack used by reentrant functions has its own stack pointer which is independent of the 8051 stack and stack pointer. The stack and stack pointer are defined and initialized in theSTARTUP.A51 file.

    The following table details the stack pointer assembler variable name, data area, and size for each of the three memory models.

    Model Pointer Stack Information
    SMALL ?C_IBP
    (1 Byte)
    The stack is located in indirectly accessible internal memory (idata). The maximum reentrant stack size is 256 bytes. To access the small reentrant stack, R0 or R1 is loaded with the value of ?C_IBP and the reentrant stack is accessed indirectly using the MOV A, @R0/@R1 and MOV @R0/@R1, A instructions.
    COMPACT ?C_PBP
    (1 Byte)
    The stack is located in Page-addressable external memory (pdata). The maximum reentrant stack size is 256 bytes. To access the compact reentrant stack, R0 or R1 is loaded with the value of ?C_PBP and the reentrant stack is accessed indirectly using the MOVX A, @R0/@R1 and MOVX @R0/@R1, A instructions.
    LARGE ?C_XBP
    (2 Bytes)
    The stack is located in Externally accessible memory (xdata). The maximum reentrant stack size is 64K Bytes. To access the large reentrant stack, DPTR is loaded with the value of ?C_XBP and the reentrant stack is accessed indirectly using the MOVX A, @DPTR and MOVX @DPTR, A instructions.

    The simulated stack area for reentrant functions is organized from top to bottom—it stacks down. The 8051 hardware stack is just the opposite and is organized bottom to top—it stacks up. When using theSMALL memory model, both the simulated stack and the 8051 hardware stack share the same memory area but grow towards each other.

    The simulated stack and stack pointers are declared and initialized in the Cx51 Compiler startup code inSTARTUP.A51 which can be found in the LIB subdirectory. You must modify the startup code to specify which simulated stack(s) to initialize in order to use reentrant functions. You can also modify the starting address for the top of the simulated stack(s) in the startup code. Refer to STARTUP.A51 for more information on reentrant function stack areas.

    When calling a function with a reentrant stack, the compiler MUST know that the function has a reentrant stack. The compiler figures this out from the function prototype which should include the reentrant keyword (just like the function definition). The compiler must also know the memory model used by the function (to determine which reentrant stack to stuff arguments on). This is determined either from the memory model specified for the function, or the memory model specified to the compiler, or from the default memory model (small).

    To pass arguments, the compiler generates code that decrements the stack pointer and then "pushes" arguments onto the stack by storing the argument indirectly through R0/R1 or DPTR.

    When a reentrant function is called, it decreases the stack pointer (for local variables) and accesses arguments using the stack pointer plus an offset (which corresponds to the number of bytes of local variables).

    When a reentrant function returns, it adjusts the stack pointer to the value before arguments were pushed. So the caller does not need to perform any stack adjustment after calling a reentrant function.


  • 相关阅读:
    大数据
    优化
    gnu
    sed
    hadoop部署
    安装 zookeeper
    ansible
    ssh 配置无密码登录
    未完待续
    解正向代理、反向代理、透明代理
  • 原文地址:https://www.cnblogs.com/CodeWorkerLiMing/p/12007885.html
Copyright © 2011-2022 走看看