首页 > 解决方案 > exec 进程忽略信号 C

问题描述

我正在用 C 编写一个 shell,我正在尝试添加信号处理。在 shell 中,调用 fork() 并且子进程执行 shell 命令。子进程被放入它自己的进程组中。这样,如果在子进程处于前台时按下 Ctrl-C,它将关闭共享相同进程组 ID 的所有进程。shell 按预期执行命令。

问题是信号。例如,当我执行“sleep 5”,然后按 Ctrl-C 进行 SIGINT 时,“shell>”提示符按预期出现,但进程仍在后台运行。如果我在按下 Ctrl-C 后快速运行“ps”,睡眠呼叫仍然存在。然后在 5 秒后,我再次运行“ps”,它就消失了。当我按下 Ctrl-Z (SIGTSTP) 时也会发生同样的事情。使用 SIGTSTP,进程会按预期进入后台,但不会暂停执行。它一直运行直到完成。

为什么这些进程会像这样被发送到后台并继续运行?这是我的代码的要点...

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

int status;

void sig_handler_parent()
{
    printf("\n");
}

void sig_handler_sigchild(int signum)
{
    waitpid(-1, &status, WNOHANG);
}

int main()
{
    signal(SIGCHLD, sig_handler_sigchild);
    signal(SIGINT, sig_handler_parent);
    signal(SIGQUIT, sig_handler_parent);
    signal(SIGTERM, sig_handler_parent);
    signal(SIGCONT, sig_handler_parent);
    signal(SIGTSTP, sig_handler_parent);

    while (1)
    {
        printf("shell> ");

        // GET COMMAND INPUT HERE

        pid = fork();

        if (pid == 0)
        {
            setpgid(getpid(), getpid());

            execvp(cmd[0], cmd);
            printf("%s: unknown command\n", cmd[0]);

            exit(1);
        }
        else
            waitpid(0, &status, WUNTRACED); 
    }

    return 0;
}

ps 我已经尝试在 exec 命令之前将所有信号处理程序设置为 SIG_DFL。

标签: cprocesssignalsexec

解决方案


您提供的代码无法编译,并且尝试修复它表明您遗漏了很多。我只是猜测。

为了让您继续前进,我将指出您可能误解的一些事实。连同几个文档链接,我希望这会有所帮助。

错误处理

第一:请养成处理错误的习惯,尤其是当你知道有些东西你不明白的时候。例如,父母(你的外壳)等到孩子终止,

waitpid(0, &status, WUNTRACED); 

你说,

例如,当我执行“sleep 5”,然后按 Ctrl-C 进行 SIGINT 时,“shell>”提示符按预期出现,但进程仍在后台运行。

实际发生的情况是,一旦您按下 Ctrl-C,父级(而不是子级;原因见下文)接收SIGINT(内核的终端子系统处理键盘输入,看到有人同时按住“Ctrl”和“C”,并得出结论, 必须发送具有该控制终端SIGINT的所有进程)。

将父分支更改为,

int error = waitpid(0, &status, WUNTRACED); 
if (error != 0)
    perror("waitpid");

有了这个,你会看到perror()打印如下:

waitpid: interrupted system call

你想SIGINT去找孩子,所以肯定有问题。

信号处理程序fork(),和exec()

接下来,你的信号处理程序会发生fork()什么 exec()

信号概述手册页指出,

通过 fork(2) 创建的子代继承了其父代的信号处置的副本。在 execve(2) 期间,已处理信号的处置被重置为默认值;忽略信号的处置保持不变。

因此,理想情况下,这意味着:

  1. 父(shell)看到SIGINT,如上所见,并打印“中断的系统调用”。
  2. 孩子的信号处理程序被重置为默认值。对于SIGINT,这意味着终止。

你不摆弄控制终端,所以孩子继承了父母的控制终端。这意味着它 SIGINT被传递给父母孩子。鉴于孩子的SIGINT行为是终止,我敢打赌,没有任何进程正在运行。

除非您用于setpgid()创建新的进程组。

进程组、会话和控制终端

有人曾经称我为 UNIX 灰胡子。虽然从视觉的角度来看这是正确的,但我必须拒绝这种恭维,因为我很少在 UNIX 最黑暗的角落之一——终端子系统中闲逛。不过,Shell 编写者也必须了解这一点。

setpgid() 在这种情况下,它是手册页的“注释”部分。我建议你读一下,尤其是上面写着的地方,

在任何时候,会话中的一个(并且只有一个)进程组可以是终端的前台进程组;(...)

您启动 shell 程序的 shell(可能是 bash)已经为您的程序的前台调用执行此操作,并将其标记为“前台进程组”。实际上,这意味着,“亲爱的终端,请,每当有人按 Ctrl-C 时,SIGINT向该组中的所有进程发送一个。我(你的父母)只是坐下来等待(waitpid()),直到一切都结束,然后将再次控制。” .

您为孩子创建了一个进程组,但不要告诉终端。你想要的是

  1. 从终端分离父级。
  2. 将子进程组设置为终端的前台进程组。
  3. 等待孩子(你已经这样做了)。
  4. 重新获得终端前景。

在所述手册页的“注释”部分的下方,它们提供了如何完成的链接。遵循这些,彻底阅读,尝试事情,并确保你处理错误。在大多数情况下,此类错误是误解的迹象。而且,在大多数情况下,此类错误可以通过重新阅读文档来修复。


推荐阅读