重新载入函数(Reentrant Functions)
当一个信号被捕获后,被一个进程处理,该进程的正常指令队列被信号处理函数临时中断。进程会继续处理,不过执行的是信号处理函数的指令。如果信号处理函数返回(不是调用exit或longjmp),会继续捕获信号之前指令继续执行。但是当信号被捕获后,在信号处理函数中,我们不能告诉进程处理到哪里了。当进程使用malloc向堆上分配额外内存时会发生什么?还有我们在信号处理函数中调用malloc会发生什么?或者进程正在调一个函数的过程中,如:getpwnam,它把结果存储在一个静态区域中,并且我们会在信号处理函数中调用相同的函数,这时会发生什么?在malloc的例子中,破坏会影响到进程,因为malloc一般维护着一个链表,该链表包含所有它分配的区域,并且它可能已经改变了链表中的数据。在getpwnam例子中,正常调用者的数据可能被信号处理函数覆盖。
Single UNIX Specification指定了一些函数,可以保证它们重新载入(reentrant)。
|
还有很多函数不在Figure 10.4中,因为(a)它们使用静态数据结构(b)它们调用malloc或free(c)它们是标准I/O库的一部分。多数标准I/O库的实现,以非重新载入(nonreentrant)的方式使用全局数据结构。注意,即便在我们的信号处理函数例子中调用printf,也不保证有预计的结果,因为信号处理函数能中断我们的主函数后调用printf。
要知道即使我们在信号处理函数中调用了Figure 10.4中的函数,每个线程也只有一个errno变量(APUE的1.7节谈论了errno和线程),并且我们可以修改这个值。考虑一个信号处理函数,它在main函数刚设置完errno后立即被调用,如果这个信号处理函数调用read,而且这个调用可以改变errno的值,那么这将会擦除之前在main函数中存储于errno中的值。因此,做为一般规则,当在信号处理函数中调用Figure 10.4中的函数时,我们应该保存和恢复errno中的值。(要知道通常捕获了SIGCHLD信号后,信号处理函数都会调用wait函数(几个版本中的一个),而所有的wait函数都能改变errno值。)
注意longjmp(APUE 7.10节)和siglongjmp(APUE 10.15节)不在Figure 10.4中,因为可能信号发生的同时,main例程已经以非重新载入(nonreentrant way)的方式更新了数据结构。如果我们调用siglongjmp代替从信号处理函数返回,这时这个数据结构可能已经更新了一半。如果用类似的方式更新全局数据结构,当捕获信号的同时,会导致sigsetjmp被执行,一个应用须要在更新数据结构的同时阻塞信号。
例子:
Figure 10.5. Call a nonreentrant function from a signal handler#include "apue.h"
#include <pwd.h>
static void
my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler\n");
if ((rootptr = getpwnam("root")) == NULL)
err_sys("getpwnam(root) error");
alarm(1);
}
int
main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for ( ; ; ) {
if ((ptr = getpwnam("sar")) == NULL)
err_sys("getpwnam error");
if (strcmp(ptr->pw_name, "sar") != 0)
printf("return value corrupted!, pw_name = %s\n",
ptr->pw_name);
}
}
|
上面的例子显示了一个程序,它从每秒调用一次的信号处理函数中调用了一个非重新载入函数(nonreentrant function)getpwnam。alarm函数会在APUE 10.10节介绍。在这里我们用它每秒产生一个SIGALRM信号。
当这个程序运行时,结果是随机的。一般情况下,当信号处理函数会在几次迭代后返回,程序可能会被SIGSEGV信号结束。可以通过检查core文件来查看main函数已经调用getpwnam,但是它的一些内部指针在信号处理函数调用相同函数时已经被破坏。偶尔这个程序也能在收到SIGSEGV信号前运行几秒钟。当信号被捕获后main函数正确的运行时,返回值有时是好的,而有时是被破坏了的。在Mac OS X上,malloc库会输出一条警告信息:释放过的内存不会再被malloc分配。