c - 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。
解决方案
您提供的代码无法编译,并且尝试修复它表明您遗漏了很多。我只是猜测。
为了让您继续前进,我将指出您可能误解的一些事实。连同几个文档链接,我希望这会有所帮助。
错误处理
第一:请养成处理错误的习惯,尤其是当你知道有些东西你不明白的时候。例如,父母(你的外壳)等到孩子终止,
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) 期间,已处理信号的处置被重置为默认值;忽略信号的处置保持不变。
因此,理想情况下,这意味着:
- 父(shell)看到
SIGINT
,如上所见,并打印“中断的系统调用”。 - 孩子的信号处理程序被重置为默认值。对于
SIGINT
,这意味着终止。
你不摆弄控制终端,所以孩子继承了父母的控制终端。这意味着它
SIGINT
被传递给父母和孩子。鉴于孩子的SIGINT
行为是终止,我敢打赌,没有任何进程正在运行。
除非您用于setpgid()
创建新的进程组。
进程组、会话和控制终端
有人曾经称我为 UNIX 灰胡子。虽然从视觉的角度来看这是正确的,但我必须拒绝这种恭维,因为我很少在 UNIX 最黑暗的角落之一——终端子系统中闲逛。不过,Shell 编写者也必须了解这一点。
setpgid()
在这种情况下,它是手册页的“注释”部分。我建议你读一下,尤其是上面写着的地方,
在任何时候,会话中的一个(并且只有一个)进程组可以是终端的前台进程组;(...)
您启动 shell 程序的 shell(可能是 bash)已经为您的程序的前台调用执行此操作,并将其标记为“前台进程组”。实际上,这意味着,“亲爱的终端,请,每当有人按 Ctrl-C 时,SIGINT
向该组中的所有进程发送一个。我(你的父母)只是坐下来等待(waitpid()
),直到一切都结束,然后将再次控制。” .
您为孩子创建了一个进程组,但不要告诉终端。你想要的是
- 从终端分离父级。
- 将子进程组设置为终端的前台进程组。
- 等待孩子(你已经这样做了)。
- 重新获得终端前景。
在所述手册页的“注释”部分的下方,它们提供了如何完成的链接。遵循这些,彻底阅读,尝试事情,并确保你处理错误。在大多数情况下,此类错误是误解的迹象。而且,在大多数情况下,此类错误可以通过重新阅读文档来修复。
推荐阅读
- java - 执行java类时如何在命令行参数中为属性指定路径
- java - 带有十六进制输入的 BigInteger NumberFormatException
- composer-php - 在更新时更新 json 文件中的作曲家依赖项
- html - 如何让iOS上的网页在退出全屏时继续播放视频
- xml - XML Twig:更新单个条目
- r-commander - R Commander 导入 Excel 文件
- karate - 使用空手道匹配部分响应
- swift - 快速输入 String() 和 String.self 有什么区别?
- c# - 一个项目中有许多 COM 对象
- regex - Golang替换任何和所有换行符