sigaction() 与 signal() 有何不同?

How sigaction() differs from signal()?

prog2 不会在 CTRL+D 上退出。为什么 prog1CTRL+D 上退出呢?更奇怪的是,信号处理程序不执行任何操作,尽管它以某种方式影响最终结果...

以下两个程序的区别仅在于prog1.c中使用了sigaction(),而在prog2.c中使用了signal()

@@ -39,10 +39,7 @@
     /* read from loopback tty */
     if (cpid > 0) {
         /* this is the strange part */
-        struct sigaction sa;
-        sa.sa_handler = child_handler;
-        sa.sa_flags = 0;
-        sigaction(SIGCHLD, &sa, NULL);
+        signal(SIGCHLD, child_handler);

         struct termios tty;
         tcgetattr(fd, &tty);

这些程序中的每一个都简单地打开一个环回tty并将自己分成两个进程,一个从tty读取响应,另一个将数据写入tty设备。然后从loopback tty虚拟设备接收到的数据输出到控制终端。

使用 -lutil 选项编译 prog1prog2。启动每个程序并键入 hello<CTRL+D>。这导致以下输出:

$ ./prog1
hello$

$ ./prog2
hello

顺便说一句,应该在 sigaction() 中设置哪些标志以复制 signal() 的行为?


以下是节目:

prog1.c

#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>

int fd;

void child_handler(int s)
{
    (void) s;
}

int main(void)
{
    char c;

    /* open loopback tty device */
    pid_t lpid;
    lpid = forkpty(&fd, NULL, NULL, NULL);
    if (lpid == -1) {
        exit(1);
    }
    if (lpid == 0) {
        char *args[] = { "cat", NULL };
        execv("/bin/cat", args);
    }

    /* create parallel process */
    pid_t cpid;
    cpid = fork();
    if (cpid == -1) {
        close(fd);
        exit(1);
    }

    /* read from loopback tty */
    if (cpid > 0) {
        /* this is the strange part */
        struct sigaction sa;
        sa.sa_handler = child_handler;
        sa.sa_flags = 0;
        sigaction(SIGCHLD, &sa, NULL);

        struct termios tty;
        tcgetattr(fd, &tty);
        cfmakeraw(&tty);
        tcsetattr(fd, TCSANOW, &tty);

        while (read(fd, &c, 1) != -1)
            write(STDOUT_FILENO, &c, 1);
    }

    /* write to loopback tty */
    if (cpid == 0) {
        struct termios stdtio_restore;
        struct termios stdtio;
        tcgetattr(STDIN_FILENO, &stdtio_restore);
        tcgetattr(STDIN_FILENO, &stdtio);
        cfmakeraw(&stdtio);
        tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);

        while (1) {
            read(STDIN_FILENO, &c, 1);
            if (c == 0x04) break;
            write(fd, &c, 1);
        }

        tcsetattr(0, TCSANOW, &stdtio_restore);
        close(fd);
        exit(0);
    }

    return 0;
}

prog2.c

#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>

int fd;

void child_handler(int s)
{
    (void) s;
}

int main(void)
{
    char c;

    /* open loopback tty device */
    pid_t lpid;
    lpid = forkpty(&fd, NULL, NULL, NULL);
    if (lpid == -1) {
        exit(1);
    }
    if (lpid == 0) {
        char *args[] = { "cat", NULL };
        execv("/bin/cat", args);
    }

    /* create parallel process */
    pid_t cpid;
    cpid = fork();
    if (cpid == -1) {
        close(fd);
        exit(1);
    }

    /* read from loopback tty */
    if (cpid > 0) {
        /* this is the strange part */
        signal(SIGCHLD, child_handler);

        struct termios tty;
        tcgetattr(fd, &tty);
        cfmakeraw(&tty);
        tcsetattr(fd, TCSANOW, &tty);

        while (read(fd, &c, 1) != -1)
            write(STDOUT_FILENO, &c, 1);
    }

    /* write to loopback tty */
    if (cpid == 0) {
        struct termios stdtio_restore;
        struct termios stdtio;
        tcgetattr(STDIN_FILENO, &stdtio_restore);
        tcgetattr(STDIN_FILENO, &stdtio);
        cfmakeraw(&stdtio);
        tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);

        while (1) {
            read(STDIN_FILENO, &c, 1);
            if (c == 0x04) break;
            write(fd, &c, 1);
        }

        tcsetattr(0, TCSANOW, &stdtio_restore);
        close(fd);
        exit(0);
    }

    return 0;
}

The difference in behavior is likely because signal behaves as if the SA_RESTART flag was set on sigaction. From the signal(2) manual page:

The BSD semantics are equivalent to calling sigaction(2) with the following flags:

sa.sa_flags = SA_RESTART;

The situation on Linux is as follows:

  • The kernel's signal() system call provides System V semantics.

  • By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics...

When the SA_RESTART flag is used some system calls are automatically restarted. When it is not used the call will return an error with errno set to EINTR.

Thus in the "read from loopback" process in prog1 the following happens:

  1. Your process is blocked in read
  2. It receives SIGCHLD and 运行s the handler.
  3. The read system call it was blocked in returns -1
  4. The loop exits based on the while condition and the process exits.

In prog2, the SA_RESTART behavior means that after the signal handler is 运行 in (2), the read call is restarted.

To make prog1 behave like prog2, set SA_RESTART:

sa.sa_flags = SA_RESTART;

See the "Interruption of system calls and library functions by signal handlers" section of the signal(7) manual page for more detail on SA_RESTART's behavior.