当通过ctrl+c发送SIGINT时,waitpid()有时会失败

ogsagwnx  于 2023-03-17  发布在  其他
关注(0)|答案(2)|浏览(137)

waitpid()在相关进程通过^C接收到SIGINT信号时会不时返回-1。但是,如果我通过pkill发送SIGINT信号,就无法重现这个问题。
errno的值设置为EINTR。
我有一个父进程,它创建子进程来执行从STDIN(使用execve())接收到的命令。
SIGINT和SIGQUIT的sigaction处理程序设置为SIG_DFL(仅用于子级)。SIGINT的sigaction处理程序设置为sighandler()(见下文),SIGQUIT设置为SIG_IGN(仅用于父级)。

void    sighandler(int signum)
{
    (void) signum;
    g_signal = true;
    rl_done = 1;
}

在从父进程启动所有fork之后,我对所有已经创建的pid调用waitpid(),使用wstatus我可以取回命令的输出值,并且还允许我知道进程是否已经被一个信号终止。

void    pid_lst_wait(t_pid_lst **pid_lst, int *wstatus)
{
    t_pid_lst   *ptr_pid_lst;

    *wstatus = 0;
    if (*pid_lst == NULL)
        return ;
    ptr_pid_lst = *pid_lst;
    while (ptr_pid_lst)
    {
        if (waitpid(ptr_pid_lst->pid, wstatus, 0) == -1)
            ft_dprintf(STDERR_FILENO, "waitpid: Failed to wait %d\n", ptr_pid_lst->pid);
        ptr_pid_lst = ptr_pid_lst->next;
    }
    pid_lst_clear(*pid_lst);
    *pid_lst = NULL;
}

由于waitpid失败,我无法设置返回状态以指示SIGINT已发送。
编辑:正如nos所说的here,我需要使用SA_RESTART来避免这种行为。

typedef struct sigaction    t_sigaction;

uint8_t signal_init(void)
{
    t_sigaction sa;

    sa = (t_sigaction){0};
    sigemptyset(sa.sa_mask);
    sa.sa_handler = SIG_IGN;
    if (sigaction(SIGQUIT, &sa, NULL) == -1)
        return (SSYSCALL_ERROR);
    sa.sa_handler = sighandler;
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGINT, &sa, NULL) == -1)
        return (SSYSCALL_ERROR);
    return (SSUCCESS);
}
voj3qocg

voj3qocg1#

在公共shell(例如Bash)中,^C将SIGINT发送到整个前台进程组:父母和孩子。
返回-1并将errno设置为EINTR是当函数被调用过程(即,在父进程中)中的未阻塞信号的传递中断时的预期默认行为。
您可以在提供给sigaction(2)struct sigaction结构的.sa_flags成员中指定SA_RESTART标志,以使waitpid在处理完信号传递后自动重新启动(另请参见:signal(7))。
一般来说,将信号传递给一个 * 子 * 进程(比如通过pkill)不会导致 * 父 * 进程中的waitpid返回-1(存在特定于操作系统的行为,比如在Linux上处理SIGCHILD)。

bakd9h0s

bakd9h0s2#

当您使用任何exec系统调用执行命令时,该命令创建的进程将接管调用exec系统调用的进程。因此,您在exec调用后尝试使用或访问的任何变量或代码都将无法工作。例如:

int main(int argc, char* argv[])
{
    int pid = fork();
    if (pid == 0)
    {
          execvp(argv[0],argv);
          printf("done\n");
    } else 
    {
         return 0;
    }
}

末尾的printf语句将不会被调用,因为由exec调用创建的进程完全接管了子进程。
因此,一旦执行exec系统调用,您在子进程中创建的信号处理程序就会丢失,而由exec调用创建的进程可能会创建自己的信号处理程序。
如果不了解进程和信号处理程序是如何/在何处创建的,我想我无法提供更多帮助,但如果您希望在父进程接收SIGINT时终止子进程,则必须在主进程中设置一个信号处理程序来处理SIGINT,然后在接收到SIGINT时,您可以在该信号处理程序中终止子进程。例如:

int child_pid;

void handler(int num)
{
    kill(child_pid, SIGKILL);
}

相关问题