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

Igo*_*nko 1 signals tty pty

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

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

@@ -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);
Run Code Online (Sandbox Code Playgroud)

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

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

$ ./prog1
hello$

$ ./prog2
hello
Run Code Online (Sandbox Code Playgroud)

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


以下是这些程序:

程序1.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;
}
Run Code Online (Sandbox Code Playgroud)

程序2.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;
}
Run Code Online (Sandbox Code Playgroud)

小智 5

行为上的差异很可能是因为signal行为就像SA_RESTART在 上设置了标志一样sigaction。来自signal(2)手册页:

BSD 语义相当于使用以下标志调用 sigaction(2):

sa.sa_flags = SA_RESTART;
Run Code Online (Sandbox Code Playgroud)

Linux上的情况如下:

  • 内核的 signal() 系统调用提供 System V 语义。

  • 默认情况下,在 glibc 2 及更高版本中,signal() 包装函数不会调用内核系统调用。相反,它使用提供 BSD 语义的标志来调用 sigaction(2)...

使用该SA_RESTART标志时,某些系统调用会自动重新启动。当不使用它时,调用将返回错误,并将 errno 设置为EINTR

因此在“从环回读取”过程中会prog1发生以下情况:

  1. 您的进程被阻止read
  2. 它接收 SIGCHLD 并运行处理程序。
  3. read被阻止的系统调用返回-1
  4. 根据while条件退出循环,进程退出。

在 中prog2,该SA_RESTART行为意味着在 (2) 中运行信号处理程序后,read重新启动调用。

要使prog1行为类似于prog2,请设置SA_RESTART

sa.sa_flags = SA_RESTART;
Run Code Online (Sandbox Code Playgroud)

有关行为的更多详细信息,请参阅signal(7)手册页的“信号处理程序中断系统调用和库函数”部分。SA_RESTART