如何使用 C 模拟 bash 中的信号(Ctrl+\ 和 Ctrl+C)?

How to simulate signals (Ctrl+\ and Ctrl+C) like in bash using C?

我想模拟信号 (Ctrl+\ and Ctrl+C) 在 bash 但使用 C.

我想使用信号函数终止子进程 void (*signal(int sig, void (*func)(int)))(int);

到目前为止我达到了:

#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pid_t     cpid = 0;

void signal_handler(int sig) {
    // char parent_str[] = "this is ctrl + c in the parent prosess\n";
    char child_str[] = "this is ctrl + c in the child prosess\n";
    // signal(sig, signal_handler);
    if (sig == SIGINT) {
        write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
        kill(cpid, SIGINT);
    }
    else if (sig == SIGQUIT)
    {
        write(STDOUT_FILENO, "this is ctrl + \ in the child prosess\n", sizeof("this is ctrl + \ in the child prosess\n") - 1);
        kill(cpid, SIGQUIT);
    }
}

int main(int argc, char **argv) {
    pid_t pid, pgid;

    (void)argv;
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);
    pid = fork();
    cpid = pid;
    if (pid == 0) {
        cpid = 1;
        while(1);
        exit(EXIT_SUCCESS);
    }
    wait (NULL);
    printf("pid = %d\ncpisd = %d", pid, cpid);
    while (1);
}

但是当我编译这段代码时,进程不会终止,为什么?

The process won't kill, why?

我认为令人困惑的部分是子进程也将使用与其父进程相同的 signal_handler 函数——即使 fork()signal() 之后被调用。如果您希望子进程在收到来自父进程的信号时死亡,则处理程序应测试这是否是子进程(子进程的 cpid 设置为 1)并告诉它显式退出。

这是一个例子:

void signal_handler(int sig) {
    // char parent_str[] = "this is ctrl + c in the parent prosess\n";
    char child_str[] = "this is ctrl + c in the child prosess\n";
    // signal(sig, signal_handler);
    if (sig == SIGINT) {
        write(STDOUT_FILENO, child_str, sizeof(child_str) - 1); 
        if(cpid != 1) {
            kill(cpid, SIGINT);
        } else {
            exit(1);
        }   
    }   
    else if (sig == SIGQUIT)
    {   
        write(STDOUT_FILENO, "this is ctrl + \ in the child prosess\n", sizeof("this is ctrl + \ in the child prosess\n") - 1); 
        if(cpid != 1) {
            kill(cpid, SIGQUIT);
        } else {
            exit(1);
        }   
    }   
}

当您 fork a process almost everything that has occurred thus far is duplicated in the child process - that is to say, the child process has a nearly identical environment to its parent at the very moment fork successfully returns. While there are some exceptions to this (as detailed in the man page for fork) 信号处理程序被保留时。

当您键入 ^C 时,终端中发生的事情是 SIGINT 被发送到 both 您的 parent 和 child 进程。这是以相同的方式处理的,因为信号处理函数在 parent 和 child 中是相同的,但由于 cpid.

的值而具有不同的结果

在parent中,cpid0,因此kill有这样的效果:

If pid equals 0, then sig is sent to every process in the process group of the calling process.

所以 parent 将信号“转发”给 child,因为它属于 parent 的进程组。

child进程因此接收到两个信号;一个直接来自终端,一个来自 parent 进程。使用相同的处理程序,但这次 cpid1,它调用 kill 并满足以下条件:

If pid is positive, then signal sig is sent to the process with the ID specified by pid.

所以这个调用

kill(1, SIGINT)

其中 PID 1 几乎肯定是 init,负责整个系统的根进程。这可能是件好事,但没有任何效果,否则您的系统可能会关闭。

最后,当每个进程沿着这条链推送信号时,什么都没有发生,但这就是我们看到三行文本的原因:一行来自 parent,两行来自 child .


我们希望在 parent 和 child 中以不同方式处理我们的信号,因此最好使用两个不同的处理程序。

由于从终端发送信号导致信号重叠,如果我们只想接受来自 [=75= 的信号,我们将必须确定是谁在 child ] 直接处理。 signal handlers can not provide this level of detail, so instead we need sigaction 个处理程序。

这是一个小示例,它在 parent 和 child 之间拆分信号处理。在 child 中,我们使用 SA_SIGINFO 标志设置 struct sigaction,这表明我们要使用 sa_sigaction 处理程序。我们使用 getppid 并将其与 info->si_pid 进行比较,以确定信号是否直接来自 parent。

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

static pid_t child_id;

void sigparent(int sig) {
    const char msg[] = "The parent will kill the child.\n";

    switch (sig) {
    case SIGINT:
    case SIGQUIT:
        write(STDOUT_FILENO, msg, (sizeof msg) - 1);
        kill(child_id, SIGQUIT);
        break;
    default:
        break;
    }
}

void sigchild(int sig, siginfo_t *info, void *ctx) {
    const char msg[] = "The child will now die.\n";

    if (getppid() == info->si_pid)
        switch (sig) {
        case SIGINT:
        case SIGQUIT:
            write(STDOUT_FILENO, msg, (sizeof msg) - 1);
            _exit(EXIT_SUCCESS);
            break;
        default:
            break;
        }
}

void parent(void) {
    signal(SIGINT, sigparent);
    signal(SIGQUIT, sigparent);

    puts("Parent will wait...");
    waitpid(child_id, NULL, WUNTRACED);
    puts("Parent is done waiting.");
}

void child(void) {
    struct sigaction action = { 0 };

    sigemptyset(&action.sa_mask);
    action.sa_flags = (SA_SIGINFO);
    action.sa_sigaction = sigchild;

    sigaction(SIGINT, &action, NULL);
    sigaction(SIGQUIT, &action, NULL);

    while (1) {
        puts("Child is sleeping...");
        sleep(2);
    }
}

int main(void) {
    if ((child_id = fork()))
        parent();
    else
        child();
}