如何在C中的pthread中为system()调用启用SIGINT信号



下面关于系统((的手册说,它会阻止通过系统((调用运行的任何二进制程序的SIGINT和SIGQUIT信号。https://man7.org/linux/man-pages/man3/system.3.html#:~:text=%20system((%20library%20函数,%20命令%20已%20完成。

Psedo代码:

thread_1()
{
...
system("binary application");
}

main() {
...
pid = pthread_create(thread_1);
pthread_cancel(pid);
}

pthread_cancel向线程1发出SIGINT,这会杀死线程1,但不会杀死二进制应用程序。

如何使";二进制应用程序";接收SIGINT信号?

下面关于系统((的手册说,它会阻止通过系统((调用运行的任何二进制程序的SIGINT和SIGQUIT信号。

不,不是。上面写着:

在执行命令期间,SIGCHLD将被阻止,并且SIGINT并且SIGQUIT将被忽略,在调用system((的过程中。(这些信号将在执行命令的子进程。(

(添加了强调。(是调用system()的进程的信号处理受到影响,而不是运行命令的(单独的(进程。此外,这是有目的的,你不应该轻易地试图干扰它

pthread_cancel向线程1 发出SIGINT

怀疑。POSIX没有记录线程取消是如何实现的,但发送SIGINT是一个不太可能的选择,因为它的默认行为是终止进程pthread_cancel()的Linux手册确实说它是通过信号实现的,但它也说,如果实时信号可用,则使用第一个实时信号,否则使用SIGUSR2。当system()运行时,这两个都没有被记录为被阻塞。

,它们杀死线程1,但不杀死二进制应用程序。

是的,在该函数运行时强制终止调用system()的线程不会终止执行指定命令的单独进程。这与信号被屏蔽无关。如果您想在命令完成之前终止正在运行的进程,那么您可能正在寻找kill()函数。为此,你需要找到你想杀死的孩子的PID,这可能需要付出相当大的努力和麻烦。

总的来说,如果你不喜欢system()的语义,那么你最好推出自己的版本,基于fork()exec*(),这样你就可以拥有你想要的控制级别。

手册页还显示:

(这些信号将在执行命令的子进程。(

所以要回答您的问题"如何使";二进制应用程序";接收SIGINT信号";没关系,反正会的。阻塞发生在调用命令的线程中,而不是命令进程中。

编辑:

要回答@Hanu下面的评论,请使用系统调用的wait((集合:您可以从那里获得系统((调用中命令的pid,并且您可以安全地关闭子线程或根据wait(((的结果采取行动。但我不知道如果进程终止,你需要清理哪些资源:Linux将释放与系统调用的进程相关的所有资源:操作系统在pthread完成时如何清理pthread和处理资源之间存在区别-请参阅这个SO答案。

不使用system(),而是将fork()作为子进程,并在该子进程中使用execl("/bin/sh", "-c", "system-command-goes-here", (char *)0);

当您调用fork()时,它会返回两次:一次是在具有正值的父级中,即进程标识符";pid";,儿童进程;在孩子身上有一次是零值。

要向子进程发送INT信号,只需使用kill(pid, SIGINT);

如果线程退出(被取消或终止(,您可以在线程中使用pthread_cleanup_push(kill_int, (intptr_t)pid)来终止子进程,使用

static void kill_int(void *pidptr)
{
const pid_t  pid = (intptr_t)pidptr;
pid_t        p;
if (pid > 1)
kill(pid, SIGINT);
}

以下是一些您可能会发现有用的公共域帮助程序函数运行.h

/* SPDX-License-Identifier: CC0-1.0 */
#ifndef   RUN_H
#define   RUN_H
#include <unistd.h>
#include <sys/types.h>
/* Execute command.  First parameter is the binary to execute (path or name),
and the second parameter is the argument array.  First element in the
argument array is command name, and the last element must be (char *)0.
Returns the child process ID if successful, -1 with errno set if error.
*/
pid_t run(const char *, const char *[]);
/* Execute shell command.  The parameter is the shell command,
otherwise this behaves like run().
*/
pid_t run_sh(const char *);
/* Check if child process has exited.
Returns the PID of the child if it has returned,
with the status (use WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), WTERMSIG())
stored at the location specified by the int pointer, if not NULL.
Returns 0 if the child hasn't exited yet, or
-1 if an error occurred (with errno set).
try_reap() tries to reap a specific child,
try_reap_any() checks if any child processes have exited, and
try_reap_group() checks if a child belonging to a process group has exited.
*/
pid_t try_reap(pid_t, int *);
pid_t try_reap_any(int *);
pid_t try_reap_group(pid_t, int *);
/* Wait until a specific child exits.
Returns the child PID with status set if not NULL,
or -1 if an error occurs.
*/
pid_t reap(pid_t, int *);
/* Wait until all child processes have exited.
If non-NULL, the callback is called for each reaped child.
If the callback returns nonzero, the function returns immediately
without waiting for other children.
Returns 0 if success, callback return value if it returns nonzero,
or -1 with errno set if an error occurs.
*/
pid_t reap_all(int (*report)(pid_t, int));
pid_t reap_group(pid_t, int (*report)(pid_t, int));
#endif /* RUN_H */

实现,run.c:

/* SPDX-License-Identifier: CC0-1.0 */
#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#ifndef  RUN_FAILURE_EXIT_STATUS
#define  RUN_FAILURE_EXIT_STATUS  69
#endif
static inline int has_slash(const char *cmd)
{
while (*cmd)
if (*(cmd++) == '/')
return 1;
return 0;
}
pid_t run(const char *cmd, const char *args[])
{
int    ctrl[2] = { -1, -1 };
int    cause;
pid_t  child, p;
/* Sanity checks. */
if (!cmd || !*cmd || !args) {
errno = EINVAL;
return -1;
}
/* Create a close-on-exec control pipe. */
if (pipe2(ctrl, O_CLOEXEC) == -1) {
/* Failed; errno already set. */
return -1;
}
/* Fork the child process. */
child = fork();
if (child == (pid_t)-1) {
/* Failed; errno set. */
cause = errno;
close(ctrl[0]);
close(ctrl[1]);
errno = cause;
return -1;
} else
if (!child) {

/* This is the child process. */
/* Close parent end of control pipe. */
close(ctrl[0]);
/* Try and execute the command. */
if (has_slash(cmd))
execv(cmd, (char *const *)args);
else
execvp(cmd, (char *const *)args);
/* Failed. Try and report cause to parent. */
cause = errno;
{
const char       *ptr = (const char *)(&cause);
const char *const end = (const char *)(&cause) + sizeof cause;
ssize_t           n;
while (ptr < end) {
n = write(ctrl[1], ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1 || errno != EINTR)
break;
}
}
exit(RUN_FAILURE_EXIT_STATUS);
}
/* This is the parent process. */
/* Close child end of control pipe. */
close(ctrl[1]);
/* Try reading from the control pipe. */
{
char       *ptr = (char *)(&cause) + sizeof cause;
char *const end = (char *)(&cause) + sizeof cause;
int         err = 0;
ssize_t     n;
while (ptr < end) {
n = read(ctrl[0], ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (!n) {
break;
} else
if (n != -1) {
err = EIO;
break;
} else
if (errno != EINTR) {
err = errno;
break;
}
}
/* If we failed, and didn't get a full cause,
use the error from the read. */
if (err && ptr != end)
cause = err;
}
/* Close parent end of the control pipe. */
close(ctrl[0]);
/* If we failed, reap the child and exit. */
if (cause) {
do {
p = waitpid(child, NULL, 0);
} while (p == -1 && errno == EINTR);
errno = cause;
return -1;
}
/* Everything looks okay! */
return child;
}
pid_t run_shell(const char *command)
{
const char *args[4] = { "sh", "-c", command, (char *)0 };
return run("/bin/sh", args);
}
pid_t try_reap(const pid_t pid, int *status)
{
int   temp_status;
pid_t p;
if (pid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(pid, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t try_reap_any(int *status)
{
int   temp_status;
pid_t p;
do {
p = waitpid(-1, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t try_reap_group(pid_t pgid, int *status)
{
int   temp_status;
pid_t p;
if (pgid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(-1, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t reap(const pid_t pid, int *status)
{
int   temp_status;
pid_t p;
if (pid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(pid, &temp_status, 0);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
int reap_all(int (*report)(pid_t pid, int status))
{
int   status, retval;
pid_t p;
while (1) {
p = waitpid(-1, &status, 0);
if (p == -1) {
if (errno == ECHILD)
return 0;
else
if (errno != EINTR)
return -1;
} else
if (p > 0 && report) {
retval = report(p, status);
if (retval)
return retval;
}
}
}
int reap_group(pid_t pgid, int (*report)(pid_t pid, int status))
{
int   status, retval;
pid_t p;
if (pgid <= 1) {
errno = EINVAL;
return -1;
}
while (1) {
p = waitpid(-pgid, &status, 0);
if (p == -1) {
if (errno == ECHILD)
return 0;
else
if (errno != EINTR)
return -1;
} else
if (p > 0 && report) {
retval = report(p, status);
if (retval)
return retval;
}
}
}

下面是一个使用示例,example.c,它运行由命令行参数指定的二进制文件:

/* SPDX-License-Identifier: CC0-1.0 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "run.h"
int main(int argc, char *argv[])
{
pid_t  child, p;
int    status;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *argv0 = (argc > 0 && argv && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "n");
fprintf(stderr, "Usage: %s [ -h | --help ]n", argv0);
fprintf(stderr, "       %s COMMAND [ ARGS ... ]n", argv0);
fprintf(stderr, "n");
return EXIT_FAILURE;
}
child = run(argv[1], (const char **)(argv + 1));
if (child == -1) {
fprintf(stderr, "%s: Cannot execute: %s.n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
fprintf(stderr, "%s: Started process %d.n", argv[1], (int)child);
p = reap(child, &status);
if (p == -1) {
fprintf(stderr, "%s: Cannot reap child: %s.n", argv[1], strerror(errno));
return EXIT_FAILURE;
} else
if (p != child) {
fprintf(stderr, "%s: Internal bug: reaped the wrong child process (%d, expected %d).n", argv[1], (int)p, (int)child);
return EXIT_FAILURE;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS) {
fprintf(stderr, "%s: Exited successfully.n", argv[1]);
return EXIT_SUCCESS;
} else {
fprintf(stderr, "%s: Exited with status %d.n", argv[1], WEXITSTATUS(status));
return WEXITSTATUS(status);
}
} else
if (WIFSIGNALED(status)) {
fprintf(stderr, "%s: Died from signal %d.n", argv[1], WTERMSIG(status));
return EXIT_FAILURE;
} else {
fprintf(stderr, "%s: Child process vanished!n", argv[1]);
return EXIT_FAILURE;
}
}

要将所有这些联系在一起,Makefile:

CC      := gcc
CFLAGS  := -Wall -O2
LDFLAGS :=
PROGS   := example
all: $(PROGS)
clean:
rm -f *.o $(PROGS)
%.o:%.c
$(CC) $(CFLAGS) -c $^
example: run.o example.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@

请注意,此论坛使用Tabs,因此需要运行sed -e 's|^ *|t|' -i Makefile来修复缩进。要进行编译,只需运行make即可。要运行,请运行例如

./example date

父进程检查子进程退出的方式和原因,并将报告进程标识符(pid(和退出状态。

最新更新