- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
这是How to prevent SIGINT in child process from propagating to and killing parent process?的延续
在上面的问题中,我了解到SIGINT
并不是从子级冒泡到父级,而是发布给了整个前台进程组,这意味着我需要编写一个信号处理程序来防止当我击打CTRL + C
时父级退出。
我试图实现这一点,但这就是问题所在。具体来说,我调用的kill
syscall终止了子进程,如果我传递SIGKILL
,则一切正常,但是如果我传递SIGTERM
,它也会终止父进程,稍后在shell提示符中显示Terminated: 15
。
即使SIGKILL可以工作,但我仍想使用SIGTERM是因为从我所读到的内容来看,这似乎总体上是一个更好的主意,从而使该过程可以终止自己的清理机会。
以下代码是我提出的精简示例
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
pid_t CHILD = 0;
void handle_sigint(int s) {
(void)s;
if (CHILD != 0) {
kill(CHILD, SIGTERM); // <-- SIGKILL works, but SIGTERM kills parent
CHILD = 0;
}
}
int main() {
// Set up signal handling
char str[2];
struct sigaction sa = {
.sa_flags = SA_RESTART,
.sa_handler = handle_sigint
};
sigaction(SIGINT, &sa, NULL);
for (;;) {
printf("1) Open SQLite\n"
"2) Quit\n"
"-> "
);
scanf("%1s", str);
if (str[0] == '1') {
CHILD = fork();
if (CHILD == 0) {
execlp("sqlite3", "sqlite3", NULL);
printf("exec failed\n");
} else {
wait(NULL);
printf("Hi\n");
}
} else if (str[0] == '2') {
break;
} else {
printf("Invalid!\n");
}
}
}
handle_sigint
函数并不感到兴奋。有没有更标准的杀死交互式子进程的方式?
最佳答案
您的代码中存在太多错误(由于未清除struct sigaction
上的信号掩码),任何人都无法解释您所看到的效果。
相反,请考虑以下工作示例代码,例如example.c
:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Child process PID, and atomic functions to get and set it.
* Do not access the internal_child_pid, except using the set_ and get_ functions.
*/
static pid_t internal_child_pid = 0;
static inline void set_child_pid(pid_t p) { __atomic_store_n(&internal_child_pid, p, __ATOMIC_SEQ_CST); }
static inline pid_t get_child_pid(void) { return __atomic_load_n(&internal_child_pid, __ATOMIC_SEQ_CST); }
static void forward_handler(int signum, siginfo_t *info, void *context)
{
const pid_t target = get_child_pid();
if (target != 0 && info->si_pid != target)
kill(target, signum);
}
static int forward_signal(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_sigaction = forward_handler;
act.sa_flags = SA_SIGINFO | SA_RESTART;
if (sigaction(signum, &act, NULL))
return errno;
return 0;
}
int main(int argc, char *argv[])
{
int status;
pid_t p, r;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s COMMAND [ ARGS ... ]\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Install signal forwarders. */
if (forward_signal(SIGINT) ||
forward_signal(SIGHUP) ||
forward_signal(SIGTERM) ||
forward_signal(SIGQUIT) ||
forward_signal(SIGUSR1) ||
forward_signal(SIGUSR2)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
p = fork();
if (p == (pid_t)-1) {
fprintf(stderr, "Cannot fork(): %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (!p) {
/* Child process. */
execvp(argv[1], argv + 1);
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
/* Parent process. Ensure signals are reflected. */
set_child_pid(p);
/* Wait until the child we created exits. */
while (1) {
status = 0;
r = waitpid(p, &status, 0);
/* Error? */
if (r == -1) {
/* EINTR is not an error. Occurs more often if
SA_RESTART is not specified in sigaction flags. */
if (errno == EINTR)
continue;
fprintf(stderr, "Error waiting for child to exit: %s.\n", strerror(errno));
status = EXIT_FAILURE;
break;
}
/* Child p exited? */
if (r == p) {
if (WIFEXITED(status)) {
if (WEXITSTATUS(status))
fprintf(stderr, "Command failed [%d]\n", WEXITSTATUS(status));
else
fprintf(stderr, "Command succeeded [0]\n");
} else
if (WIFSIGNALED(status))
fprintf(stderr, "Command exited due to signal %d (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
else
fprintf(stderr, "Command process died from unknown causes!\n");
break;
}
}
/* This is a poor hack, but works in many (but not all) systems.
Instead of returning a valid code (EXIT_SUCCESS, EXIT_FAILURE)
we return the entire status word from the child process. */
return status;
}
gcc -Wall -O2 example.c -o example
./example sqlite3
sqlite3
-但是,即使您直接运行
sqlite3
,它也不会中断-;相反,您只在屏幕上看到
^C
。这是因为
sqlite3
以这样的方式设置了终端:Ctrl + C不会引起信号,只是被解释为普通输入。
sqlite3
命令从
.quit
退出,或在行首按Ctrl + D。
Command ... []
行,然后再返回到命令行。因此,父进程不会被信号杀死/伤害/阻碍。
ps f
来查看终端进程的树,然后通过该方法找出父进程和子进程的PID,并向其中一个发送信号以观察发生了什么。
SIGSTOP
信号,因此反射(reflect)作业控制信号是很重要的(就像使用Ctrl + Z时一样)。为了进行适当的作业控制,父进程将需要建立一个新的 session 和一个进程组,并暂时与终端分离。这也是可能的,但是超出了本文的范围,因为它涉及 session ,进程组和终端的相当详细的行为,以便正确管理。
sqlite3
命令。 (您可以为该程序添加任何可执行文件和任何参数字符串。)
internal_child_pid
变量以及
set_child_pid()
和
get_child_pid()
函数用于原子地管理子进程。
__atomic_store_n()
和
__atomic_load_n()
是编译器提供的内置组件。有关GCC的信息,请查询
see here。它们避免了仅部分分配子pid时发生信号的问题。在某些常见的体系结构上不会发生这种情况,但这只是作为一个仔细的示例,因此原子访问用于确保仅看到完整的(旧的或新的)值。如果我们在过渡期间暂时屏蔽了相关信号,则可以避免完全使用这些信号。再次,我认为原子访问更简单,并且在实践中可能会很有趣。
forward_handler()
函数以原子方式获取子进程PID,然后验证它是否为非零(我们知道我们有一个子进程),并且我们没有转发该子进程发送的信号(只是为了确保我们不会引起信号) Storm ,两人用信号相互轰炸)。
siginfo_t
手册页中列出了
man 2 sigaction
结构中的各个字段。
forward_signal()
函数为指定的信号
signum
安装上述处理程序。请注意,我们首先使用
memset()
将整个结构清除为零。如果结构中的某些填充转换为数据字段,则以这种方式清除它可以确保将来的兼容性。
.sa_mask
中的
struct sigaction
字段是无序的信号集。屏蔽中设置的信号被阻止在执行信号处理程序的线程中传递。 (对于上面的示例程序,我们可以放心地说,这些信号在信号处理程序运行时被阻塞;只是在多线程程序中,信号仅在用于运行处理程序的特定线程中被阻塞。)
sigemptyset(&act.sa_mask)
清除信号掩码很重要。仅将结构设置为零是不够的,即使实际上在许多机器上都可以(可能)工作。 (我不知道;我什至没有检查过。我更喜欢鲁棒和可靠,而不是懒惰和脆弱!)
SA_SIGINFO
,因为处理程序使用三参数形式(并使用
si_pid
的
siginfo_t
字段)。
SA_RESTART
标志仅存在是因为OP希望使用它。它只是意味着,如果可能的话,如果使用当前在系统调用中阻塞的线程(如
errno == EINTR
)传递信号,则C库和内核将尝试避免返回
wait()
错误。您可以删除
SA_RESTART
标志,并在父进程的循环中的适当位置添加调试
fprintf(stderr, "Hey!\n");
,以查看随后发生的情况。
sigaction()
函数将返回0,否则将返回
-1
设置为
errno
的函数。如果成功分配了
forward_signal()
,则
forward_handler
函数返回0,否则返回非零errno号。有些人不喜欢这种返回值(他们更愿意为错误返回-1,而不是
errno
值本身),但是出于某种不合理的原因,我喜欢这种惯用语。一定要更改它。
main()
。
-h
或
--help
参数,它将打印使用情况摘要。同样,以这种方式执行只是我喜欢的事情-
getopt()
和
getopt_long()
更常用于解析命令行选项。对于这种琐碎的程序,我只是对参数检查进行了硬编码。
fork()
部分应该很熟悉。如果返回
-1
,则派生失败(可能是由于限制或类似原因),然后打印出
errno
消息是一个很好的主意。返回值在子进程中为
0
,在父进程中为子进程ID。
execlp()
函数带有两个参数:二进制文件的名称(在PATH环境变量中指定的目录将用于搜索这样的二进制文件),以及指向该二进制文件的参数的指针数组。第一个参数将是新二进制文件中的
argv[0]
,即命令名称本身。
execlp(argv[1], argv + 1);
调用与上述描述进行比较,则实际上很容易解析。
argv[1]
命名要执行的二进制文件。
argv + 1
基本上等同于
(char **)(&argv[1])
,即它是一个以
argv[1]
而不是
argv[0]
开头的指针数组。再次,我只是喜欢
execlp(argv[n], argv + n)
惯用语,因为它允许一个人执行在命令行上指定的另一条命令,而不必担心解析命令行或通过 shell 执行它(有时是完全不希望的)。
man 7 signal
手册页介绍了如何在
fork()
和
exec()
处发出信号处理程序。简而言之,信号处理程序是通过
fork()
继承的,但是在
exec()
处重置为默认值。幸运的是,这正是我们想要的。
sigprocmask()
。阻塞信号意味着使其“等待”;直到信号被解除阻塞,它才会被传送。在子进程中,信号可能会保持阻塞状态,因为无论如何都会通过
exec()
将信号处理重置为默认设置。在父进程中,我们可以-或在派生之前无关紧要-安装信号处理程序,最后释放信号。这样,我们将不需要原子填充,甚至不需要检查子pid是否为零,因为在传递任何信号之前,子pid将被设置为其实际值!
while
循环基本上只是
waitpid()
调用的循环,直到我们开始的确切子进程退出或发生有趣的事情(子进程以某种方式消失)为止。如果要安装没有
EINTR
标志的信号处理程序,则此循环包含非常仔细的错误检查以及正确的
SA_RESTART
处理。
EXIT_SUCCESS
或
EXIT_FAILURE
。我之所以将其保留下来,是因为当您想返回与子进程返回的相同或相似的退出状态代码时,有时会在实践中使用它。因此,仅用于说明。如果您发现自己的程序应返回与其 fork 并执行的子进程相同的退出状态,那么这仍然比设置机器使进程以杀死该子进程的信号杀死自身更好。处理。如果您需要使用此注释,只需在其中添加醒目的注释,并在安装说明中添加注释,以便那些在可能不希望使用的体系结构上编译程序的人可以对其进行修复。
关于c - 使用SIGTERM在子进程上调用kill会终止父进程,但是使用SIGKILL调用它会使父进程保持事件状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40477988/
我的应用程序包含两部分:网络部分和 GUI。它的工作方式有点像浏览器 - 用户从服务器请求一些信息,服务器发回一些代表某些 View 的数据,然后 GUI 显示它。 现在我已经将网络部分实现为一项服务
给定表达式字符串exp,编写程序检查exp中“{”、“}”、“(”、“)”、“[”、“]的对和顺序是否正确。 package main import ( "fmt" stack "gi
我想要一个简单的脚本在后台保持运行。目前看起来像这样: import keyboard while True: keyboard.wait('q') keyboard.send('ct
我维护着许多 RedHat Enterprise Linux(7 台和 8 台)服务器(>100 台),其中包含不同的应用程序。为了保持理智,我当然会使用 Ansible 等工具,更重要的是,公共(p
我有一个 winforms 应用程序,它在网络服务请求期间被锁定 我已经尝试使用 doEvents 来保持应用程序解锁,但它仍然不够响应, 我怎样才能绕过这个锁定,让应用程序始终响应? 最佳答案 最好
我正在努力在我的项目中获得并保持领先的 0。以下是当前相关的代码: Dim jobNum As String jobNum = Left(r1.Cells(1, 1), 6) r2.Cells(1
我正在尝试在我的 Canvas 中定位元素相对于我的背景。 窗口被重新调整大小,保持纵横比。 背景随着窗口大小而拉伸(stretch)。 问题是一旦重新调整窗口大小,元素位置就会不正确。如果窗口的大小
一直在玩弄 Hibernate 和 PostgreSQL,试图让它按预期工作。 但是由于某种原因,当我尝试将具有@OneToMany 关系的对象与集合中的多个项目保持一致时,除了第一个项目之外,所有项
我想将某些东西提交到 github 存储库,但我(显然)没有任何权利这样做。我对那个 repo 做了一个分支,提交了我的更改并提交了一个 pull-request。 现在,问题是过了一段时间其他人已经
这是一个初学者问题,我仍在考虑“在 OOP 中”,所以如果我错过了手册中的答案或者答案很明显,我深表歉意。 假设我们有一个抽象类型, abstract type My_Abstract_type en
我们正在开展的一些项目在 jQuery 1.4.2 或更早版本中有着深厚的根基,介于缺乏最新版本的性能优势(或语法糖)、使用现已弃用的方法的耻辱以及部署一个积极维护的库的 3 年以上旧版本,升级现在迫
我看到在FMDB 2.0中,作者为线程添加了FMDatabaseQueue。例子是: // First, make your queue. FMDatabaseQueue *queue = [FMDa
我在 NSScrollView 中有一个 NSTableView。 NSTableView 的内容是通过绑定(bind)到 NSArrayController 来提供的,而 NSArrayContro
我在 TreeView 上有一个节点,我手动填充该节点并希望保持排序。通过用户交互,TreeViewItem 上的标题可能会更改,它们应该移动到列表中的适当位置。 我遍历一个 foreach,创建多个
我从主 NSWindow 打开一个 NSWindow。 DropHereWindowController *dropHereWindowController = [[DropHereWindowCon
我需要放置一个 form 3 按钮,当我单击该按钮时,将其显示为按下,其他按钮向上,当我单击另一个按钮时,它应该为“向下”,其他按钮应为“向上” 最佳答案 所有按钮的属性“Groupindex”必须设
我有一个使用 AnyEvent::MQTT 订阅消息队列的 perl 脚本。 目前我想要它做的就是在收到消息时打印出来。我对 perl 完全陌生,所以我正在使用它附带的演示代码,其中包括将 STDIN
如何在 .NET 应用程序中保持 TreeView 控件的滚动位置?例如,我有一个树形 View 控件,并经历了一个向其添加各种节点的过程,并将它们固定在底部。在此过程中,我可以滚动浏览 TreeVi
我维护了大量的 vbscripts,用于在我的网络上执行各种启动脚本,并且有一些我在几乎所有脚本中使用的函数。 除了复制和粘贴之外,有没有人对我如何创建可重用 vbscript 代码库有建议。我并不反
我有一些关于 Azure 自托管的问题。 假设用户 Alex 在物理机 M 上设置了 Windows 自托管代理。当 Alex 注销且计算机进入休眠状态时,代理将脱机。现在,当 Bob 登录同一台计算
我是一名优秀的程序员,十分优秀!