进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。

如果从信号处理程序返回(例如没有调用 exit 或longjmp),则继续执行在捕捉到信号时进程正在执行的正常指令序列(这类似于发生硬件中断时所做的)。

但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。如果进程正在执行malloc,在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,这时会发生什么?又例如,若进程正在执行getpwnam(见6.2节)这种将其结果存放在静态存储单元中的函数,其间插入执行信号处理程序,它又调用这样的函数,这时又会发生什么呢?在malloc例子中,可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个链表,而插入执行信号处理程序时,进程可能正在更改此链表。在getpwnam的例子中,返回给正常调用者的信息可能会被返回给信号处理程序的信息覆盖。

图10-4 信号处理程序可以调用的可重入函数

Single UNIX Specification说明了在信号处理程序中保证调用安全的函数。这些函数是可重入的并被称为是异步信号安全的(async-signal safe)。

除了可重入以外,在信号处理操作期间,它会阻塞任何会引起不一致的信号发送。图10-4列出了这些异步信号安全的函数。没有列入图10-4中的大多数函数是不可重入的,

因为

  • 已知它们使用静态数据结构;
  • 它们调用 malloc 或free;
  • 它们是标准I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。注意,虽然在本书的某些实例中,信号处理程序也调用了printf函数,但这并不保证产生所期望的结果,信号处理程序可能中断主程序中的printf函数调用。

应当了解,即使信号处理程序调用的是图10-4中的函数,但是由于每个线程只有一个errno变量(回忆1.7节对errno和线程的讨论),所以信号处理程序可能会修改其原先值。考虑一个信号处理程序,它恰好在main刚设置errno之后被调用。如果该信号处理程序调用read这类函数,则它可能更改errno的值,从而取代了刚由main设置的值。因此,作为一个通用的规则,当在信号处理程序中调用图10-4中的函数时,应当在调用前保存errno,在调用后恢复errno。(应当了解,经常被捕捉到的信号是SIGCHLD,其信号处理程序通常要调用一种wait函数,而各种wait函数都能改变errno。)

注意,图10-4没有包括longjmp(7.10节)和sig-longjmp(10.15节)。这是因为主例程以非可重入方式正在更新一个数据结构时可能产生信号。如果不是从信号处理程序返回而是调用siglongjmp,那么该数据结构可能是部分更新的。如果应用程序将要做更新全局数据结构这样的事情,而同时要捕捉某些信号,而这些信号的处理程序又会引起执行siglongjmp,则在更新这种数据结构时要阻塞此类信号。

实例

图10-5给出了一段程序,这段程序从信号处理程序my_alarm调用非可重入函数getpwnam,而my_alarm每秒钟被调用一次。10.10节中将说明alarm函数。在该程序中调用alarm函数使得每秒产生一次SIGALRM信号。

#include <pwd.h>
#include "apue.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);
        }
    }
}

图10-5 在信号处理程序中调用不可再入函数

运行该程序时,其结果具有随机性。通常,在信号处理程序经多次迭代返回时,该程序将由SIGSEGV信号终止。

检查core文件,从中可以看到main函数已调用getpwnam,但当getpwnam调用free时,信号处理程序中断了它的运行,并调用getpwnam,进而再次调用free。在信号处理程序调用free而主程序也在调用free时,malloc和free维护的数据结构就出现了损坏,偶然,此程序会运行若干秒,然后因产生 SIGSEGV 信号而终止。在捕捉到信号后,若main函数仍正确运行,其返回值却有时错误,有时正确。

从此实例中可以看出,如果在信号处理程序中调用一个非可重入函数,则其结果是不可预知的。

results matching ""

    No results matching ""