可重入和非可重入函数
深入理解可重入函数为何物,首先需要区分单线程程序和多线程程序。典型UNIX程序都具有一条执行线程,贯穿程序始终,CPU围绕单条执行逻辑来处理指令。而对于多线程程序而言,同一进程却存在多条独立、并发的执行逻辑流。
多线程程序与使用了信号处理函数的程序中需要注意函数是否可重入,因为信号处理函数可能会在任一时刻异步中断程序的执行,从而在同一个进程中实际形成两条(主程序和信号处理函数)独立(虽然不是并发)的执行线程。
上图中1是信号到达
2是内核代表进程调用信号处理函数
3是执行信号处理函数
4是程序从中断点恢复执行
如果同一个进程的多条线程可以同时安全地调用某一函数,那么该函数时可重入的,此处,“安全”意味着,无论其他线程调用该函数的执行状态如何,函数均可产生预期结果。
更新全局变量或静态数据结构的函数可能是不可重入的,只用到局部变量的函数肯定是可重入的。如果对函数的两个调用(例如两个线程)同时试图更新某一全局变量或数据类型,那么二者很可能会相互干扰并产生不正确的结果。例如假设某线程正在为一链表数据结构添加一个新的链表项,而另一线程也正试图更新同一链表。由于为链表添加新项涉及对多个指针的更新,一旦另一线程中断这些步骤并修改了相同的指针,结果就会产生混乱。C语言中的malloc()函数就维护一个针对已释放内存块的链表,用于从堆中重新分配内存。因此malloc函数族以及使用malloc的库函数是不可重入的。
还有一个函数使用了经静态分配的内存来返回信息,因此也是不可重入的,因为下次调用会覆盖上次调用的返回信息,例如crypt() getpwnam() gethostbyname()等函数。
一个例子:
#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
#define _XOPEN_SOURCE 600
#endif
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "tlpi_hdr.h"
static char *str2; /* Set from argv[2] */
static int handled = 0; /* Counts number of calls to handler */
static void
handler(int sig)
{
crypt(str2, "xx");
handled++;
}
int
main(int argc, char *argv[])
{
char *cr1;
int callNum, mismatch;
struct sigaction sa;
if (argc != 3)
usageErr("%s str1 str2\n", argv[0]);
str2 = argv[2]; /* Make argv[2] available to handler */
cr1 = strdup(crypt(argv[1], "xx")); /* Copy statically allocated string
to another buffer */
if (cr1 == NULL)
errExit("strdup");
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handler;
if (sigaction(SIGINT, &sa, NULL) == -1)
errExit("sigaction");
/* Repeatedly call crypt() using argv[1]. If interrupted by a
signal handler, then the static storage returned by crypt()
will be overwritten by the results of encrypting argv[2], and
strcmp() will detect a mismatch with the value in 'cr1'. */
for (callNum = 1, mismatch = 0; ; callNum++) {
if (strcmp(crypt(argv[1], "xx"), cr1) != 0) {
mismatch++;
printf("Mismatch on call %d (mismatch=%d handled=%d)\n",
callNum, mismatch, handled);
}
}
}
调用crypt()加密第一个命令行参数中的字符串,并使用strdup将结果复制到独立缓冲区中。
为SIGINT信号(CTRL + C)创建处理器函数。处理器函数调用crypt()加密第2个命令行参数所提供的参数。使用crypt()加密第一个命令行参数中的字符串,检查是否一致。
不产生信号的情况下总是一致的,然而一旦收到信号,而主程序又在for循环内的crypt()调用之后,字符串的匹配检查之前遭到信号处理函数的终端,这时就会发生字符串不匹配的情况。