Reentrant and non-reentrant


Mainly used in multitasking environments, a reentrant function is simply a function that can be interrupted, that is, it can be interrupted at any time when the function is executed, and then transferred to the OS scheduling to execute another piece of code, and There will be no error when returning control; non-reentrant functions use some system resources, such as global variable area, interrupt vector table, etc., so if it is interrupted, there may be problems, such functions cannot run in a multitasking environment.

It can also be understood in this way that reentrancy means repeated entry. First, it means that the function can be interrupted, and secondly, it means that it does not depend on any environment (including static) except for using variables on its own stack. Such a function is purecode ( pure code) is reentrant, which allows multiple copies of the function to run, and since they use separate stacks, they do not interfere with each other. If you do need to access global variables (including static), be sure to implement mutual exclusion. Reentrant functions are very important in parallel execution environments, but generally have some performance penalty for accessing global variables.

When writing reentrant functions, if global variables are used, they should be protected by means of closing interrupts and semaphores (ie, P, V operations).

 Description: If the global variables used are not protected, this function is not reentrant, that is, when multiple processes call this function, it is very likely that the relevant global variables will become unknowable.

Example: Assuming that Exam is a global variable of type int, the function Squre_Exam returns the squared value of Exam. Then the following functions are not reentrant.

unsigned int example( int para )

{

    unsigned int temp;
        Exam = para; //  (**)
        temp = Square_Exam( );
        return temp;
    }
    If this function is called by multiple processes, the result may be unknown, because when the (**) statement has just been executed After that, another process that uses this function may just be activated, then when the newly activated process executes this function, it will assign a different para value to Exam, so when the control returns to "temp = Square_Exam( )", the calculated temp may not be the expected result. This function should be improved as follows.

    unsigned int example( int para ) {
        unsigned int temp;
        [Apply for semaphore operation] //(1)     Exam
        = para;     temp
        = Square_Exam( );
        [release semaphore operation]
        return temp; "Semaphore", indicating that another process is in the process of assigning value to Exam and calculating its square (that is, using this signal), and the process must wait for it to release the signal before continuing to execute. If the signal is applied for, it can continue to execute, but other processes must wait for the process to release the semaphore before using the signal.     Ways to guarantee the reentrancy of a function:




    When writing functions, try to use local variables (such as registers, variables in the stack), and protect the global variables to be used (such as taking off interrupts, semaphores, etc.), so that the formed function must be a reentrant The function.
    The reentrant technologies adopted in VxWorks are:
    * Dynamic stack variables (each sub-function has its own independent stack space)
    * Protected global variables and static variables
    * Task variables


------------ --------------------------------------
    In the design of real-time systems, there are often multiple A case where a task calls the same function. If this function is unfortunately designed to be non-reentrant, different tasks calling this function may modify the data of other tasks calling this function, resulting in unpredictable consequences. So what is a reentrant function? The so-called reentrant function refers to a process that can be called by multiple tasks, and the tasks do not have to worry about whether the data will be wrong when calling. Non-reentrant functions are considered unsafe functions in real-time system design. Most of the functions that meet the following conditions are not reentrant:
    1) The function body uses a static data structure;
    2) The function body calls the malloc() or free() function;
    3) The function body calls the standard I/O function.

    Examples are given below.
    A. Reentrant function
    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类型,则返回为错指针。

 

这种情况出现在多任务系统当中,在任务执行期间捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断。如果从信号处理程序返回,则继续执行进程断点处的正常指令序列,从重新恢复到断点重新执行的过程中,函数所依赖的环境没有发生改变,就说这个函数是可重入的,反之就是不可重入的。
众所周知,在进程中断期间,系统会保存和恢复进程的上下文,然而恢复的上下文仅限于返回地址,cpu寄存器等之类的少量上下文,而函数内部使用的诸如全局或静态变量,buffer等并不在保护之列, 所以如果这些值在函数被中断期间发生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。打个比方,比如malloc,将如一个进程此时正在执行 malloc分配堆空间,此时程序捕捉到信号发生中断,执行信号处理程序中恰好也有一个malloc,这样就会对进程的环境造成破坏,因为malloc通 常为它所分配的存储区维护一个链接表,插入执行信号处理函数时,进程可能正在对这张表进行操作,而信号处理函数的调用刚好覆盖了进程的操作,造成错误。

满足下面条件之一的多数是不可重入函数:
(1)使用了静态数据结构;
(2)调用了malloc或free;
(3)调用了标准I/O函数;标准io库很多实现都以不可重入的方式使用全局数据结构。
(4)进行了浮点运算.许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326031316&siteId=291194637