49 读书笔记:第10章 信号 (2)

10.4 不可靠的信号

        早期的UNIX版本中,信号是不可靠的。不可靠在这里指的是,信号可能会丢失:一个信号发生了,但进程却可能一直不知道这一点。同时,进程对信号的控制能力也很差,它能捕捉信号或忽略它。有时用户希望通知内核阻塞一个信号:不要忽略该信号,在其发生时记住它,然后在进程做好准备时再通知它。这种阻塞信号的能力当时是不具备的。

        早期版本中的一个问题进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值。

        这些早期系统的另一个问题是:进程不希望某种信号发生时,它不能关闭该信号。进程所做的一切就是忽略该信号。

10.5 中断的系统调用

        早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno被设置为EINTR。这要处理的理由是:因为一个信号确实发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个应当唤醒阻塞的系统调用的好机会。

        为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,它们包括:

        (1) 在读某些类型的文件(管道、终端设备以及网络设备)时,如果数据并不存在则可能会使调用者永远阻塞。

        (2) 在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞。

        (3) 打开某些类型文件,在某种条件发生之前也可能会使调用者阻塞。

        (4) pause(按照定义,它使调用进程休眠直至捕捉到一个信号)和wait函数。

        (5) 某些ioctl操作。

        (6) 某些进程间通信函数。

        在这些低速系统调用中,一个值得注意的例外是与磁盘I/O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动器将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I/O操作总会很快返回,并使调用者不再处于阻塞状态。

        对于中断的read、write系统调用。如若read系统调用已接收并传送数据至应用程序缓冲区,但尚未接收到应用程序全部数据时被中断,操作系统可以认为该系统调用失败,并将errno设置为EINTR;另一种处理方式是允许该系统调用成功返回,返回已接收到的部分数据量。write与此类似。

        与被中断的系统调用相关的问题是必须显示地处理出错返回。

        为了帮助应用程序使其不必处理被中断的系统调用,某些被中断系统调用自动重启动。自动重启动的系统调用包括ioctl、read、readv、write、writev、wait和waitpid。前5个函数只对低速设备进行操作时才会被信号中断,而wait和waitpid在捕捉到信号时总是被中断。因为这种自动重启动的处理方式也会带来问题,所以某些应用程序并不希望这些函数被中断后重启动。为此,允许进程基于每个信号禁用此功能。

10.6 可重入函数

        进程捕捉到信号并对其进程处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程在何处执行。

        不可重入函数,其原因为:(a) 已知它们使用静态数据结构,(b) 它们调用malloc或free,(c) 它们是标准I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。

        信号处理程序可能会修改其原先值。作为一个通用的规则,当在信号处理程序中调用某些可重入函数时,应当在其前保存errno,在其后恢复errno。

        在程序清单10-2中,信号处理程序my_alarm调用了不可重入函数getpwnam,而my_alarm每秒钟调用一次。

        《UNIX环境高级编程》P247:程序清单10-2 在信号处理程序中调用不可重入函数(有改动)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>

static void my_alarm(int signo)
{
    struct passwd   *rootptr;

    printf("in signal handler\n");

    if ((rootptr = getpwnam("root")) == NULL)
        fprintf(stderr, "getpwnam(root) error\n");
    alarm(1);
}

int main(void)
{
    struct passwd *ptr;
    signal(SIGALRM, my_alarm);
    alarm(1);

    for ( ; ; ) { 
        if ((ptr = getpwnam("user")) == NULL)
            fprintf(stderr, "getpwnam error\n");
//      pause();
        if (strcmp(ptr->pw_name, "user") != 0)
            printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
    }   
    exit(0);
}

        该程序在我的电脑上(Ubuntu 14.04)没要按照预期运行,信号处理程序调用一次之后,就不再调用。(其中原因?)。如果去掉pasue()那行注释,该程序会每秒调用一次信号处理程序。

10.7 SIGCLD语义

        SIGCLD和SIGCHLD这两个信号很容易混淆。SIGCLD是System V的一个信号名,其语义与名为SIGCHLD的BSD信号不同。POSIX.1 则采用BSD的SIGCHLD信号。

        由于历史原因,System V处理SIGCLD信号的方式不同于其他信号。省略掉……

        如果SIGCHLD(没打错,有H)被忽略,4.4BSD总是产生僵死进程,如果要避免僵死进程,则必须等待子进程。但Mac OS X 10.3在SIGCHLD被忽略时,并不创建僵死进程。在SVR4中,如果调用signal或sigset将SIGCHLD的配置设置为忽略,则绝不会产生僵死进程。Linux2.4.22和Solaris 9在此方面追随SVR4.

        使用sigaction可设置SA_NOCLDWAIT标志以避免僵死进程。

        《UNIX环境高级编程》P249:程序清单10-3 不能正常工作的System V SIGCLD处理程序

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

static void sig_cld(int signo);

int main()
{
    pid_t   pid;

    if (signal(SIGCLD, sig_cld) == SIG_ERR)
        perror("signal error");
    if ((pid = fork()) < 0) {
        perror("fork error");
    } else if (pid == 0) {
        sleep(2);
        _exit(0);
    }   
    pause();
    exit(0);
}

static void sig_cld(int signo)
{
    pid_t   pid;
    int     status;

    printf("SIGCLD received");
    if (signal(SIGCLD, sig_cld) == SIG_ERR)
        perror("signal error");
    if ((pid = wait(&status)) < 0)
        perror("wait error\n");
    printf("pid = %d\n", pid);
}

        在本人系统(Ubuntu 14.04 )上,工作正常。其原因是,当一进程安排捕捉SIGCHLD,并且已经有进程准备好由其父进程等待时,该系统调用并不调用SIGCHLD(SIGCLD)信号的处理程序。

        在System V中产生问题是:在信号处理程序开始处调用signal,内核会立即检查是否有需要等待的子进程(因为我们正在处理一个SIGCLD,所以确实有这种子进程),所以它产生另一个对信号处理程序的调用。信号处理程序调用signal,整个过程再次重复。为了解决这一问题,应当在调用wait取到子进程的终止状态后再调用signal。此时仅当其他子进程终止时,内核才会再次产生此种信号。

        如果为SIGCHLD建立了一个信号处理程序,又存在一个已终止但父进程尚未等待它的进程,则是否会产生信号?POSIX.1没有对此作说明。但POSIX.1在信号发生时并没有将信号配置复位为默认值,于是在SIGCHLD处理程序中也就不必再为该信号建立一个信号处理程序。

        在Linux中SIGCLD等价与SIGCHLD。

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)