【问题标题】:exec process ignoring signals Cexec 进程忽略信号 C
【发布时间】:2020-04-26 04:49:30
【问题描述】:

我正在用 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;
}

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

【问题讨论】:

  • 如果子进程在自己的进程组中,它不会收到发往父进程组的信号,对吗?

标签: c process signals exec


【解决方案1】:

您确定您的子进程实际上正在接收来自您的 tty 的信号吗?我相信您需要拨打tcsetpgrp 来实际告诉控制终端向您的子进程的进程组发送信号。

例如,在您致电fork 之后和exec 之前,请在您的孩子内部尝试此操作。

tcsetpgrp(STDIN_FILENO, getpid())

Here is the man page 为tcsetpgrp(3)

【讨论】:

  • 我添加了 tcsetpgrp(0, getpid()) 到孩子,程序冻结在这一行。我也尝试添加到父级,这只是我运行一个命令后 SIGTSTP 的外壳。我尝试在 while 循环的顶部添加另一个。这使它忽略了我输入的第一个命令,执行第二个命令尝试然后 shell 退出。
【解决方案2】:

您提供的代码无法编译,尝试修复它显示 你省略了很多。我只是猜测。

为了让您更上一层楼,我将指出一些事实 你可能误会了。连同一些文档 链接,希望对您有所帮助。

错误处理

首先:请养成处理错误的习惯,尤其是当您 知道有些事情你不明白。例如, 父母(你的外壳)等到孩子终止,

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()?

signal overview man page 状态,

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

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

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

你不要摆弄控制终端,所以孩子 继承父级的控制终端。这意味着 SIGINT 被传递给父 和 子。鉴于 孩子的SIGINT 行为是终止,我敢打赌没有进程 离开了。

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

进程组、会话和控制终端

曾经有人称我为 UNIX 灰胡子。虽然这是真的形式 从视觉的角度来看,我必须拒绝这种赞美,因为我很少 徘徊在 UNIX 最黑暗的角落之一——终端 子系统。不过,Shell 编写者也必须了解这一点。

在此上下文中,它是 setpgid() man page 的“注释”部分。我建议 你读到了,尤其是上面写着的,

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

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

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

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

在所述手册页的“NOTES”部分的下方,他们提供了链接 如何做到这一点。遵循那些,彻底阅读,尝试事物, 并确保您处理错误。在大多数情况下,此类错误是迹象 的误解。而且,在大多数情况下,此类错误可以通过以下方式修复 重新阅读文档。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多