精华内容
下载资源
问答
  • 函数可重入和不可重入

    千次阅读 2017-04-15 18:26:26
    https://www.ibm.com/developerworks/cn/linux/l-reent.html这是一篇描述重入函数和不可重入函数的文章。先把他copy过来: 在早期的编程中,不可重入性对程序员并不构成威胁;函数不会有并发访问,也没有中断。在很...

    https://www.ibm.com/developerworks/cn/linux/l-reent.html这是一篇描述重入函数和不可重入函数的文章。先把他copy过来:
    在早期的编程中,不可重入性对程序员并不构成威胁;函数不会有并发访问,也没有中断。在很多较老的 C 语言实现中,函数被认为是在单线程进程的环境中运行。
    不过,现在,并发编程已普遍使用,您需要意识到这个缺陷。本文描述了在并行和并发程序设计中函数的不可重入性导致的一些潜在问题。信号的生成和处理尤其增加了额外的复杂性。由于信号在本质上是异步的,所以难以找出当信号处理函数 触发某个不可重入函数时导致的 bug。

    本文:
    a. 定义了可重入性,并包含一个可重入函数的 POSIX 清单。
    b. 给出了示例,以说明不可重入性所导致的问题。
    c. 指出了确保底层函数的可重入性的方法。
    d. 讨论了在编译器层次上对可重入性的处理。

    什么是可重入性?
    可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥 (或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断, 稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时 保护自己的数据。
    可重入函数:
    a. 不为连续的调用持有静态数据。
    b. 不返回指向静态数据的指针;所有数据都由函数的调用者提供。
    c. 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
    d. 绝不调用任何不可重入函数。
    不要混淆可重入与线程安全。在程序员看来,这是两个独立的概念:函数可以是可重入的,是线程安全的,或者 二者皆是,或者二者皆非。不可重入的函数不能由多个线程使用。另外,或许不可能让某个 不可重入的函数是线程安全的。
    IEEE Std 1003.1 列出了 118 个可重入的 UNIX? 函数,在此没有给出副本。参见 参考资料 中指向 unix.org 上此列表的链接。
    出于以下任意某个原因,其余函数是不可重入的:
    a. 它们调用了 malloc 或 free。
    b. 众所周知它们使用了静态数据结构体。
    c. 它们是标准 I/O 程序库的一部分。

    信号和不可重入函数
    信号(signal)是软件中断。它使得程序员可以处理异步事件。为了向进程发送一个信号, 内核在进程表条目的信号域中设置一个位,对应于收到的信号的类型。信号函数的 ANSI C 原型是:

    void (*signal (int sigNum, void (*sigHandler)(int))) (int);

    或者,另一种描述形式:

    typedef void sigHandler(int);
    SigHandler *signal(int, sigHandler *);

    当进程处理所捕获的信号时,正在执行的正常指令序列就会被信号处理器临时中断。然后进程继续执行, 但现在执行的是信号处理器中的指令。如果信号处理器返回,则进程继续执行信号被捕获时正在执行的 正常的指令序列。
    现在,在信号处理器中您并不知道信号被捕获时进程正在执行什么内容。如果当进程正在使用 malloc 在它的堆上分配额外的内存时,您通过信号处理器调用 malloc,那会怎样?或者,调用了正在处理全局数据结构的某个函数,而 在信号处理器中又调用了同一个函数。如果是调用 malloc,则进程会 被严重破坏,因为 malloc 通常会为所有它所分配的区域维持一个链表,而它又 可能正在修改那个链表。
    甚至可以在需要多个指令的 C 操作符开始和结束之间发送中断。在程序员看来,指令可能似乎是原子的 (也就是说,不能被分割为更小的操作),但它可能实际上需要不止一个处理器指令才能完成操作。 例如,看这段 C 代码:

    temp += 1;

    在 x86 处理器上,那个语句可能会被编译为:

    mov ax,[temp]
    inc ax
    mov [temp],ax

    这显然不是一个原子操作。
    这个例子展示了在修改某个变量的过程中运行信号处理器可能会发生什么事情:

    清单 1. 在修改某个变量的同时运行信号处理器
    #include <signal.h>
    #include <stdio.h>
    struct two_int { int a, b; } data;
    void signal_handler(int signum)
    {
        printf ("%d, %d\n", data.a, data.b);
        alarm (1);
    }
    int main (void)
    {
        static struct two_int zeros = { 0, 0 }, ones = { 1, 1 };
        signal (SIGALRM, signal_handler);
        data = zeros;
        alarm (1);
        while (1)
        {
            data = zeros;
            data = ones;
        }
    }

    这个程序向 data 填充 0,1,0,1,一直交替进行。同时,alarm 信号 处理器每一秒打印一次当前内容(在处理器中调用 printf 是安全的,当信号发生时 它确实没有在处理器外部被调用)。您预期这个程序会有怎样的输出?它应该打印 0,0 或者 1,1。但是实际的输出 如下所示:

    0, 0
    1, 1
    (Skipping some output...)
    0, 1
    1, 1
    1, 0
    1, 0
    ...

    在大部分机器上,在 data 中存储一个新值都需要若干个指令,每次存储一个字。 如果在这些指令期间发出信号,则处理器可能发现 data.a 为 0 而 data.b 为 1,或者反之。另一方面,如果我们运行代码的机器能够在一个 不可中断的指令中存储一个对象的值,那么处理器将永远打印 0,0 或 1,1。
    使用信号的另一个新增的困难是,只通过运行测试用例不能够确保代码没有信号 bug。这一困难的原因在于 信号生成本质上异步的。

    不可重入函数和静态变量
    假定信号处理器使用了不可重入的 gethostbyname。这个函数 将它的值返回到一个静态对象中:

    static struct hostent host; /* result stored here*/

    它每次都重新使用同一个对象。在下面的例子中,如果信号刚好是在 main 中调用 gethostbyname 期间到达,或者甚至在调用之后到达,而程序仍然在使用那个值,则 它将破坏程序请求的值。

    清单 2. gethostbyname 的危险用法
    main()
    {
        struct hostent *hostPtr;
        ...
        signal(SIGALRM, sig_handler);
        ...
        hostPtr = gethostbyname(hostNameOne);
        ...
    }
    void sig_handler()
    {
        struct hostent *hostPtr;
        ...
        /* call to gethostbyname may clobber the value stored during the call inside the main() */
        hostPtr = gethostbyname(hostNameTwo);
        ...
    }

    不过,如果程序不使用 gethostbyname 或者任何其他在同一对象中返回信息 的函数,或者如果它每次使用时都会阻塞信号,那么就是安全的。
    很多库函数在固定的对象中返回值,总是使用同一对象,它们全都会导致相同的问题。如果某个函数使用并修改了 您提供的某个对象,那它可能就是不可重入的;如果两个调用使用同一对象,那么它们会相互干扰。
    当使用流(stream)进行 I/O 时会出现类似的情况。假定信号处理器使用 fprintf 打印一条消息,而当信号发出时程序正在使用同一个流进行 fprintf 调用。 信号处理器的消息和程序的数据都会被破坏,因为两个调用操作了同一数据结构:流本身。
    如果使用第三方程序库,事情会变得更为复杂,因为您永远不知道哪部分程序库是可重入的,哪部分是不可重入的。 对标准程序库而言,有很多程序库函数在固定的对象中返回值,总是重复使用同一对象,这就使得那些函数 不可重入。
    近来很多提供商已经开始提供标准 C 程序库的可重入版本,这是一个好消息。对于任何给定程序库,您都应该通读它所提供 的文档,以了解其原型和标准库函数的用法是否有所变化。

    确保可重入性的经验
    理解这五条最好的经验将帮助您保持程序的可重入性。
    经验 1
    返回指向静态数据的指针可能会导致函数不可重入。例如,将字符串转换为大写的 strToUpper 函数可能被实现如下:

    清单 3. strToUpper 的不可重入版本
    char *strToUpper(char *str)
    {
            /*Returning pointer to static data makes it non-reentrant */
           static char buffer[STRING_SIZE_LIMIT];
           int index;
           for (index = 0; str[index]; index++)
                    buffer[index] = toupper(str[index]);
           buffer[index] = '\0';
           return buffer;
    }

    通过修改函数的原型,您可以实现这个函数的可重入版本。下面的清单为输出准备了存储空间:

    清单 4. strToUpper 的可重入版本
    char *strToUpper_r(char *in_str, char *out_str)
    {
            int index;
            for (index = 0; in_str[index] != '\0'; index++)
            out_str[index] = toupper(in_str[index]);
            out_str[index] = '\0';
            return out_str;
    }

    由进行调用的函数准备输出存储空间确保了函数的可重入性。注意,这里遵循了标准惯例,通过向函数名添加“_r”后缀来 命名可重入函数。
    经验 2
    记忆数据的状态会使函数不可重入。不同的线程可能会先后调用那个函数,并且修改那些数据时不会通知其他 正在使用此数据的线程。如果函数需要在一系列调用期间维持某些数据的状态,比如工作缓存或指针,那么 调用者应该提供此数据。
    在下面的例子中,函数返回某个字符串的连续小写字母。字符串只是在第一次调用时给出,如 strtok 子例程。当搜索到字符串末尾时,函数返回 \0。函数可能如下实现:

    清单 5. getLowercaseChar 的不可重入版本
    char getLowercaseChar(char *str)
    {
            static char *buffer;
            static int index;
            char c = '\0';
            /* stores the working string on first call only */
            if (string != NULL) {
                    buffer = str;
                    index = 0;
            }
            /* searches a lowercase character */
            while(c=buff[index]){
             if(islower(c))
             {
                 index++;
                 break;
             }
            index++;
           }
          return c;
    }

    这个函数是不可重入的,因为它存储变量的状态。为了让它可重入,静态数据,即 index, 需要由调用者来维护。此函数的可重入版本可能类似如下实现:

    清单 6. getLowercaseChar 的可重入版本
    char getLowercaseChar_r(char *str, int *pIndex)
    {
            char c = '\0';
            /* no initialization - the caller should have done it */
            /* searches a lowercase character */
           while(c=buff[*pIndex]){
              if(islower(c))
              {
                 (*pIndex)++; break;
              }
           (*pIndex)++;
           }
             return c;
    }

    经验 3
    在大部分系统中,malloc 和 free 都不是可重入的, 因为它们使用静态数据结构来记录哪些内存块是空闲的。实际上,任何分配或释放内存的库函数都是不可重入的。这也包括分配空间存储结果的函数。
    避免在处理器分配内存的最好方法是,为信号处理器预先分配要使用的内存。避免在处理器中释放内存的最好方法是, 标记或记录将要释放的对象,让程序不间断地检查是否有等待被释放的内存。不过这必须要小心进行,因为将一个对象 添加到一个链并不是原子操作,如果它被另一个做同样动作的信号处理器打断,那么就会“丢失”一个对象。不过, 如果您知道当信号可能到达时,程序不可能使用处理器那个时刻所使用的流,那么就是安全的。如果程序使用的是某些其他流,那么也不会有任何问题。
    经验 4
    为了编写没有 bug 的代码,要特别小心处理进程范围内的全局变量,如 errno 和 h_errno。 考虑下面的代码:

    清单 7. errno 的危险用法
    if (close(fd) < 0) 
    {
        fprintf(stderr, "Error in close, errno: %d", errno);
        exit(1);
    }

    假定信号在 close 系统调用设置 errno 变量 到其返回之前这一极小的时间片段内生成。这个生成的信号可能会改变 errno 的值,程序的行为会无法预计。
    如下,在信号处理器内保存和恢复 errno 的值,可以解决这一问题:

    清单 8. 保存和恢复 errno 的值
    void signalHandler(int signo)
    {
        int errno_saved;
        /* Save the error no. */
        errno_saved = errno;
        /* Let the signal handler complete its job */
        ...
        ...
        /* Restore the errno*/
        errno = errno_saved;
    }

    经验 5
    如果底层的函数处于关键部分,并且生成并处理信号,那么这可能会导致函数不可重入。通过使用信号设置和 信号掩码,代码的关键区域可以被保护起来不受一组特定信号的影响,如下:
    保存当前信号设置。
    用不必要的信号屏蔽信号设置。
    使代码的关键部分完成其工作。
    最后,重置信号设置。
    下面是此方法的概述:

    清单 9. 使用信号设置和信号掩码
    sigset_t newmask, oldmask, zeromask;
    ...
    /* Register the signal handler */
    signal(SIGALRM, sig_handler);
    /* Initialize the signal sets */
    sigemtyset(&newmask); sigemtyset(&zeromask);
    /* Add the signal to the set */
    sigaddset(&newmask, SIGALRM);
    /* Block SIGALRM and save current signal mask in set variable 'oldmask'
    */
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    /* The protected code goes here
    ...
    ...
    */
    /* Now allow all signals and pause */
    sigsuspend(&zeromask);
    /* Resume to the original signal mask */
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    /* Continue with other parts of the code */

    忽略 sigsuspend(&zeromask); 可能会引发问题。从消除信号阻塞到进程执行下一个 指令之间,必然会有时钟周期间隙,任何在此时间窗口发生的信号都会丢掉。函数调用 sigsuspend 通过重置信号掩码并使进程休眠一个单一的原子操作来解决这一问题。如果您能确保在此时间窗口中生成的信号不会有任何 负面影响,那么您可以忽略 sigsuspend 并直接重新设置信号。

    在编译器层次处理可重用性
    我将提出一个在编译器层次处理可重入函数的模型。可以为高级语言引入一个新的关键字: reentrant,函数可以被指定一个 reentrant 标识符,以此确保函数可重入,比如:

    reentrant int foo();

    此指示符告知编译器要专门处理那个特殊的函数。编译器可以将这个指示符存储在它的符号表中,并在中间代码生成阶段 使用这个指示符。为达到此目的,编译器的前端设计需要有一些改变。此可重入指示符遵循这些准则:
    不为连续的调用持有静态数据。
    通过制作全局数据的本地拷贝来保护全局数据。
    绝对不调用不可重入的函数。
    不返回对静态数据的引用,所有数据都由函数的调用者提供。
    准则 1 可以通过类型检查得到保证,如果在函数中有任何静态存储声明,则抛出错误消息。这可以在编译的语法分析 阶段完成。
    准则 2,全局数据的保护可以通过两种方式得到保证。基本的方法是,如果函数修改全局数据,则抛出一个错误 消息。一种更为复杂的技术是以全局数据不被破坏的方式生成中间代码。可以在编译器层实现类似于前面经验 4 的方法。 在进入函数时,编译器可以使用编译器生成的临时名称存储将要被操作的全局数据,然后在退出函数时恢复那些数据。 使用编译器生成的临时名称存储数据对编译器来说是常用的方法。
    确保准则 3 得到满足,要求编译器预先知道所有可重入函数,包括应用程序所使用的程序库。这些关于函数的 附加信息可以存储在符号表中。
    最后,准则 4 已经得到了准则 2 的保证。如果函数没有静态数据,那么也就不存在返回静态数据的引用的问题。
    提出的这个模型将简化程序员遵循可重入函数准则的工作,而且使用此模型可以预防代码出现无意的可重入性 bug。


    下面就我理解,简单描述可重入和不可重入的函数。

    在多线程环境中,可重入函数与不可重入函数对线程安全至关重要。一个可重入的函数简单来说就是可以被中断的函数,也就是说可以在该函数的任意时刻中断它,通过系统调度去执行另一段代码或者本函数片段被重新执行,返回时执行原先的函数的数据不会被破坏或者改变。不可重入函数则相反,一旦被中断执行,返回执行时候数据可能会被破坏或者改变。

    在Linux API(库函数 / 系统调用)中,重入与不可重入的函数随处可见,如:

    时间类编程:
    char *asctime(const struct tm *tm);
    char *asctime_r(const struct tm *tm, char *buf);
    
    char *ctime(const time_t *timep);
    char *ctime_r(const time_t *timep, char *buf);
    
    struct tm *gmtime(const time_t *timep);
    struct tm *gmtime_r(const time_t *timep, struct tm *result);
    
    struct tm *localtime(const time_t *timep);
    struct tm *localtime_r(const time_t *timep, struct tm *result);
    
    目录类编程:
    struct dirent *readdir(DIR *dirp);
    int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
    
    字符串类编程:
    char *strtok(char *str, const char *delim);
    char *strtok_r(char *str, const char *delim, char **saveptr);

    只要函数名含”_r”,那么它就是可重入函数,与之对应的没有”_r”则是不可重入函数。
    下面以一个简单的例子,表现重入与不可重入的函数关系,及其使用。

    代码功能: 可重入遍历数组 / 不可重入遍历数组。

    #include <stdio.h>
    #include <stdlib.h>
    #define MAX 6
    
    int i;  //数组的索引值
    
    //可重入,它并不依赖全局索引值,而是操作用aip调用者分配来的空间,确保函数是可以重入的
    int traverse_arr_r(int *arr, int **save)
    {
        if (**save + 1 < MAX && **save >= 0)
            return arr[(**save)++];
    
        return -1;
    }
    
    //不可重入,依赖全局索引值
    int traverse_arr(int *arr)
    {
        if (i + 1 < MAX)
            return arr[i++];
    
        return -1;
    }
    
    int main(void)
    {
        int a[5] = {1, 2, 3, 4, 5};
        int num;
        //int i = 0;
        int *i = NULL;
        i = (int* )malloc(sizeof(int));
    
        //调用不可重入的函数,还需要注意遍历完一次后要让全局索引归零
        //while ((num = traverse_arr(a)) != -1)
        //{
        //  printf("num = %d\n", num);
        //}
    
        //调用可重入的函数
        while ((num = traverse_arr_r(a, &i)) != -1)
        {
            printf("num = %d\n", num);
        }
    
        free(i);
        return 0;
    }
    展开全文
  • C语言之可重入函数 和不可重入函数

    千次阅读 2017-09-06 21:51:32
    如果这个函数不幸被设计成为不可重入函数的话, 那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不预料的后果。 那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的...

     

    可重入函数


          在 实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,

    那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不可预料的后果。

    那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会 出错。

    不可重入函数在实时系统设计中被视为不安全函数。




      满足下列条件的函数多数是不可重入的:


    (1)函数体内使用了静态的数据结构;


    (2)函数体内调用了malloc()或者free()函数;


    (3)函数体内调用了标准I/O函数。




    如何写出可重入的函数?在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,

    写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

    或者调用该函数前关中断,调用后再开中断。


     


    可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断

    ,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,

    即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。




    说法2:




    一个可重入的函数简单来说,就是:可以被中断的函数。就是说,你可以在这个函数执行的任何时候中断他的运行,

    在任务调度下去执行另外一段代 码而不会出现什么错误。而不可重入的函数由于使用了一些系统资源,比如全局变量区,

    中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是 不能运行在多任务环境下的。


    基本上下面的函数是不可重入的
    (1)函数体内使用了静态的数据结构;
    (2)函数体内调用了malloc()或者free()函数;
    (3)函数体内调用了标准I/O函数。


    把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。
    其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。


    第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。


    第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。//这是临界区保护


    第三,不能调用任何不可重入的函数。


    第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。


    还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!


     


     

    相信很多人都看过下面这个面试题


    中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

     

    __interrupt double compute_area (double radius) 
    {
        double area = PI * radius * radius;
        printf("\nArea = %f", area);
        return area;
    }
    



    这个函数有太多的错误了,以至让人不知从何说起了:
    1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
    2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
    3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,

    有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
    4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。

    不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
     
     
    -->
    如果说中断服务程序有返回值,那么它的值返回给谁呢?
    在系统的运行过程中,一定是某种中断源出发了相应的中断,系统上挂接的中断服务程序进行现场的处理,例如告警等操作,然后清中断。
    也就是说中断服务程序链接在某一类中断源上,而这些中断源的产生是随机的,所以,中断服务程序并没有一个固定的调用者,

    也没有固定的返回地址,所以返回值也没有用


    我的问题是,这里所说的printf()经常有重入的问题,具体是指什么?有人能给解释一下么?
    这个概念在嵌入式操作系统中比较重要,由于存在任务的调度,它实时系统,可剥夺型内核中是危险的,如同一个安静的水雷。

    可能会被触发,也可能安然无恙。由于它运行结果的不可预期性,会使系统带来隐患。



    下面引用一段别人的解释:


    这主要在多任务环境中使用,一个可重入的函数简单来说,就是:可以被中断的函数。就是说,你可以在这个函数执行的任何时候中断他的运行,

    在OS的调度下去执行另外一段代码而不会出现什么错误。而不可重入的函数由于使用了一些系统资源,

    比如全局变量区,中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是不能运行在多任务环境下的。


    把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。


    其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。


    第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。


    第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,

    这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。


    第三,不能调用任何不可重入的函数。


    第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。


    还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!


    通俗的来讲吧:由于中断是可能随时发生的,断点位置也是无法预期的。所以必须保证每个函数都具有不被中断发生,

    压栈,转向ISR,弹栈后继续执行影响的稳定性。也就是说具有不会被中断影响的能力。既然有这个要求,

    你提供和编写的每个函数就不能拿公共的资源或者是变量来使用,因为该函数使用的同时,ISR(中断服务程序)

    也可那会去修改或者是获取这个资源,从而有可能使中断返回之后,这部分公用的资源已经面目全非。




    满足下列条件的函数多数是不可重入的:


    (1)函数体内使用了静态的数据结构;


    (2)函数体内调用了malloc()或者free()函数;


    (3)函数体内调用了标准I/O函数。




         下面举例加以说明。


    可重入函数

     

    void strcpy(char* lpszDest, char* lpszSrc)
    
    
    {
    
    
         while(*lpszDest++ = *lpszSrc++);
    
    
         *dest=0;
    
    
    }




    非可重入函数1

     

    char cTemp;            // 全局变量
    
    
    void SwapChar1(char* lpcX, char* lpcY)
    
    
    {
    
    
         cTemp = *lpcX; 
    
    
         *lpcX = *lpcY; 
    
    
         lpcY = cTemp;     // 访问了全局变量,在分享内存的多个线程中可能造成问题
    
    
    }




    非可重入函数2

     

    void SwapChar2(char* lpcX, char* lpcY)
    
    
    {
    
    
         static char cTemp;  // 静态局部变量
    
    
         cTemp = *lpcX; 
    
    
         *lpcX = *lpcY; 
    
    
         lpcY = cTemp;   // 使用了静态局部变量,在分享内存的多个线程中可能造成问题
    
    
    }




         如何写出可重入的函数?在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。

    如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

     

     

    备注:


    可重入和线程安全(Thread-Safe)是两个不同的概念:可重入函数一定是线程安全的;线程安全的函数可能是重入的,也可能是不重入的;线程不安全的函数一定是不可重入的。

    reentrant函数与是不是多线程无关,如果是reentrant函数,那么要求即使是同一个进程(或线程)同时多次进入该函数时,该函数仍能够正确的运作.
    该要求还蕴含着,如果是在多线程环境中,不同的两个线程同时进入该函数时,该函数也能够正确的运作.

    thread safe函数是与多线程有关的,它只是要求不同的两个线程同时对该函数的调用在逻辑上是正确的.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    本文转自:

    http://www.cnblogs.com/AlwaysOnLines/p/3912680.html

    https://blog.csdn.net/zyboy2000/article/details/51120771

     

     

    展开全文
  • C语言之可重入函数 && 不可重入函数

    千次阅读 2015-09-18 21:11:58
    如果这个函数不幸被设计成为不可重入函数的话,那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不预料的后果。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程...

    转自:http://bubuko.com/infodetail-312842.html

    可重入函数

         在 实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会 出错。不可重入函数在实时系统设计中被视为不安全函数。


         满足下列条件的函数多数是不可重入的:

     


    (1)函数体内使用了静态的数据结构;

    (2)函数体内调用了malloc()或者free()函数;

    (3)函数体内调用了标准I/O函数。


    如何写出可重入的函数?在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断。

     

    可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。

     

    说法2:


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

    基本上下面的函数是不可重入的
    (1)函数体内使用了静态的数据结构;
    (2)函数体内调用了malloc()或者free()函数;
    (3)函数体内调用了标准I/O函数。

    把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。
    其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。

    第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。

    第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。//这是临界区保护

    第三,不能调用任何不可重入的函数。

    第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

    还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!

     

     

     

    相信很多人都看过下面这个面试题

    中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

    __interrupt double compute_area (double radius) 
    {
        double area = PI * radius * radius;
        printf("\nArea = %f", area);
        return area;
    }

    这个函数有太多的错误了,以至让人不知从何说起了:
    1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
    2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
    3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
    4) 与第三点一脉相承, printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
     
     
    -->
    如果说中断服务程序有返回值,那么它的值返回给谁呢?
    在系统的运行过程中,一定是某种中断源出发了相应的中断,系统上挂接的中断服务程序进行现场的处理,例如告警等操作,然后清中断。
    也就是说中断服务程序链接在某一类中断源上,而这些中断源的产生是随机的,所以,中断服务程序并没有一个固定的调用者,也没有固定的返回地址,所以返回值也没有用

    我的问题是,这里所说的printf()经常有重入的问题,具体是指什么?有人能给解释一下么?
    这个概念在嵌入式操作系统中比较重要,由于存在任务的调度,它实时系统,可剥夺型内核中是危险的,如同一个安静的水雷。可能会被触发,也可能安然无恙。由于它运行结果的不可预期性,会使系统带来隐患。

    下面引用一段别人的解释:

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

    把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。


    其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。

    第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。

    第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。

    第三,不能调用任何不可重入的函数。

    第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

    还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!

    通俗的来讲吧:由于中断是可能随时发生的,断点位置也是无法预期的。所以必须保证每个函数都具有不被中断发生,压栈,转向ISR,弹栈后继续执行影响的稳定性。也就是说具有不会被中断影响的能力。既然有这个要求,你提供和编写的每个函数就不能拿公共的资源或者是变量来使用,因为该函数使用的同时,ISR(中断服务程序)也可那会去修改或者是获取这个资源,从而有可能使中断返回之后,这部分公用的资源已经面目全非。


    满足下列条件的函数多数是不可重入的:

    (1)函数体内使用了静态的数据结构;

    (2)函数体内调用了malloc()或者free()函数;

    (3)函数体内调用了标准I/O函数。


         下面举例加以说明。

    可重入函数

    void strcpy(char* lpszDest, char* lpszSrc)

    {

         while(*lpszDest++ = *lpszSrc++);

         *dest=0;

    }

    非可重入函数1

    char cTemp;            // 全局变量

    void SwapChar1(char* lpcX, char* lpcY)

    {

         cTemp = *lpcX; 

         *lpcX = *lpcY; 

         lpcY = cTemp;     // 访问了全局变量,在分享内存的多个线程中可能造成问题

    }

    非可重入函数2

    void SwapChar2(char* lpcX, char* lpcY)

    {

         static char cTemp;  // 静态局部变量

         cTemp = *lpcX; 

         *lpcX = *lpcY; 

         lpcY = cTemp;   // 使用了静态局部变量,在分享内存的多个线程中可能造成问题

    }

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

    万次阅读 多人点赞 2018-08-16 20:55:25
    这样的函数是不安全的函数,也叫不可重入函数。 相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否...

    在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数

    相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。

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

    也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括 static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括 static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。
    编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
    说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

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

    int Exam = 0;  
    unsigned int example( int para )   
    {   
        unsigned int temp;  
        Exam = para; // (**)  
        temp = Square_Exam( );  
        return temp;  
    }  

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

    int Exam = 0;  
    unsigned int example( int para )   
    {  
        unsigned int temp;  
        [申请信号量操作] //(1)  加锁  
        Exam = para;  
        temp = Square_Exam( );  
        [释放信号量操作] //     解锁   
        return temp;  
    }  

    申请不到“信号量”,说明另外的进程正处于给 Exam 赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。

    保证函数的可重入性的方法:

    1)在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);

    2)对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。

    满足下列条件的函数多数是不可重入(不安全)的:

    1)函数体内使用了静态的数据结构;

    2)函数体内调用了malloc() 或者 free() 函数;

    3)函数体内调用了标准 I/O 函数。

    如何将一个不可重入的函数改写成可重入函数呢?把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的:

    1)不要使用全局变量。因为别的代码很可能改变这些变量值。

    2)在和硬件发生交互的时候,切记执行类似 disinterrupt() 之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/ 退出核心”。

    3)不能调用其它任何不可重入的函数。

    4)谨慎使用堆栈。

    Linux常见的可重入函数

    展开全文
  • 可重入函数和不可重入函数

    千次阅读 2018-11-10 16:24:04
    一、可重入函数 为了增强程序的稳定性,在信号处理函数中应使用可重入函数。 所谓可重入函数是指一个可以被多个任何调用的过程,任务在调用时不必担心数据是否出错。因为进程在收到信号后,就将跳转到信号处理函数...
  • 可重入函数总结

    千次阅读 2016-04-11 12:59:55
    1.malloc,free, printf均是不可重入函数(意味不能在中断函数或信号处理函数同时调用),但是线程安全函数(意味它可以被多个线程调用) 2.函数有static变量,则该函数是不可重入函数 满足下面条件之一的多数是...
  • 可重入函数与非线程安全函数一览

    千次阅读 2015-03-23 21:44:36
    深入理解可重入与线程安全 在多线程编程和信号处理过程中,经常会遇到可重入(reentrance)与线程安全(thread-safe)。 很多人纠结于reentrance和thread-safe两个概念理解...首先来看下APUE中,列出的可重入函数
  • 可重入函数与不可重入函数

    万次阅读 2016-10-03 08:53:31
    可重入函数: ( a )已知它们使用静态数据结构 ( b )它们调用m a l l o c或f r e e ( c )它们是标准I / O函数。标准I / O库的很多实现都以不可重入方式使用全局数据结构。因为每个线程只有一个e r r n o变量,...
  • (三十三)信号——可重入函数

    千次阅读 2017-01-10 12:04:41
    一个可重入函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入函数由于使用了一些系统资源,比如...
  • Qt槽函数重入问题

    万次阅读 2019-11-29 18:14:36
    在Qt的信号槽机制中,如果一个槽函数的执行时间很长,在槽函数还没有执行结束的时候,有新的信号产生,默认情况下,该次信号不会被丢弃,而是会等槽函数执行结束后再次调用槽函数 但是在某些情况下,如果想将槽函数...
  • 关于static和可重入函数

    千次阅读 2013-06-03 21:59:53
     将上面的函数修改为可重入函数很简单,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto 类型的变量,函数即变为一个可重入函数。  当然,有些时候,在函数中是必须要使用static变量的,...
  • 可重入函数分类 (1)显式可重入函数 如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用...
  • 在进行链表的插入时,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数重入:即重复调用,...
  • 函数可重入性理解

    千次阅读 2013-01-05 10:23:19
    考虑到一点就是程序设计的可重入性。经过简单的搜索,在维基百科和其他朋友的帖子里找到了相关概念。为了帮助自己今后理解,特地将此概念加上自己的理解整理在此。 一、维基百科解释 1 若一个程序或子程序...
  • C++多态虚函数表详解(多重继承、多继承情况)

    万次阅读 多人点赞 2018-08-20 16:51:00
    本文关键词:C++ 多态 虚函数表 虚函数指针 动态绑定 概述:C++相对其他面向对象语言来说,之所以灵活、高效。很大程度的占比在于其多态技术和模板技术。C++虚函数表是支撑C++多态的重要技术,它是C++动态绑定...
  • 一、 POSIX 中对可重入和线程安全这两个概念的定义:  Reentrant Function:A function whose effect, when called by two or more threads,is guaranteed to be as if  the threads each ...
  • 1、查看matlab某函数调用了哪些函数 https://blog.csdn.net/smallcaptain2009/article/details/81195569 https://blog.csdn.net/lusongno1/article/details/54667066 2、查看matlab某函数哪些函数调用...
  • 什么是可重入

    千次阅读 2019-04-29 21:22:18
      我写博文不一定是按照一个...所以在写有关可重入函数或者代码时,所有函数内都应该只处理本函数内的数据,而不要去操作共享数据,也不要通过地址去操作其他函数内部的数据。
  • 函数可重入性及编写规范

    万次阅读 热门讨论 2006-05-15 10:41:00
    一、可重入函数1)什么是可重入性?可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号...
  • Java取整函数 四舍五入函数

    万次阅读 2016-01-21 09:58:31
    简介 Math类中提供了5个与取整相关的函数,如下所示: static double ceil(double a):天花板函数,返回大于等于a的最小整数(但是以浮点数形式存储)。...static double rint(double a):四舍五入函数,返
  • 函数概述 函数的分类 函数是C语言程序基本的组成单位,每个程序有且只能有一个主函数(main()),其他的函数都是子函数。按照不同的额分类标准,函数可以分成不同的类: 函数从是否有返回值,可以将函数分为又...
  • C++ 类成员函数函数指针

    万次阅读 多人点赞 2018-08-23 18:40:17
    当我们在 C++ 中直接像 C 那样使用类的成员函数指针时,通常会报错,提示你不能使用非静态的函数指针: reference to non-static member function must be called 两个解决方法: 把非静态的成员方法改成静态的...
  • 就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。 我先给你一个自定义数据类型的例子。 typedef int* PINT; //为int* 类型定义了一个PINT的别名 int main
  • 连续、偏导、微和连续且偏导之间的关系
  • Kotlin系列之扩展函数

    万次阅读 2018-04-17 00:22:18
    简述: 今天带来的是Kotlin浅谈系列的第五弹,这讲主要是讲利用Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可以减少很多的样板代码,大大提高开发的效率;...
  • hive内嵌UDAF函数: 聚合函数使用详解

    万次阅读 2018-03-06 23:36:43
    1.什么是聚合函数 聚合函数是hive内置函数,聚合函数对一组值执行计算,并返回单个值。在Hive的聚合中,如果某个聚合列的值中有null,则包含该null的行将在聚合时被忽略除,了 COUNT 函数以外。为了避免这种情况,...
  • C++函数多重定义问题

    千次阅读 2018-06-10 15:45:49
    C++头文件包括了函数的声明.以及在头文件中实现了函数的定义.那么这个头文件.在别的地方只能被#include一次。如果在其他的地方 至少两次 #include这个头文件.那么就会出现函数多重定义的问题.C++在编译头文件的时候....
  • 可重入函数

    千次阅读 2006-02-28 16:27:00
    可重入函数在多线程条件下,函数应当是线程安全的,进一步,更强的条件是可重入的。一个可重入函数保证了在多线程条件下,函数的状态不会出现错误。eg. in c static int tmp;void swap1(int* x, int* y) { tmp=*x; ...
  • MATLAB 传递函数的相关函数

    千次阅读 多人点赞 2019-06-17 23:15:57
    1. 连续时间传递函数 1.1 多项式形式传递函数 num = 1; % 分子多项式的系数 den = [2, 3]; % 分母多项式的系数 G = tf(num, den) % 求传递函数 在matlab中,运行上述代码,可以得到传递函数 1.2 因式乘积形式...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,158,154
精华内容 4,063,261
关键字:

哪些函数属于可重入函数