gpt4 book ai didi

c - C fork()和kill()同时无法正常工作?

转载 作者:太空宇宙 更新时间:2023-11-04 02:27:53 25 4
gpt4 key购买 nike

主程序:启动一定数量的子进程,然后立即发送SIGINT

int     main()
{
pid_t childs[CHILDS];
char *execv_argv[3];
int n = CHILDS;

execv_argv[0] = "./debugging_procs/wait_time_at_interrupt";
execv_argv[1] = "2";
execv_argv[2] = NULL;

for (int i = 0; i < n; i++)
{
childs[i] = fork();
if (childs[i] == 0)
{
execv(execv_argv[0], execv_argv);
if (errno != 0)
perror(strerror(errno));
_exit(1);
}
}

if (errno != 0)
perror(strerror(errno));

// sleep(1);

for (int i = 0; i < n; i++)
kill(childs[i], SIGINT);

if (errno != 0)
perror(strerror(errno));

// Wait for all children.
while (wait(NULL) > 0);

return 0;
}

分叉程序:等待任何信号,如果SIGINT被发送,打开某个文件并将SIGINT和当前pid写入该文件,并等待指定的秒数(在这种情况下,我从主程序发送2)。
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

void sigint_handler(int signum)
{
int fd = open("./aux/log1", O_WRONLY | O_APPEND);
char buf[124];

(void)signum;
sprintf(buf, "SIGINT %d\n", getpid());
write(fd, buf, strlen(buf));
close(fd);
}

int main(int argc, char **argv)
{
int wait_time;

wait_time = (argv[1]) ? atoi(argv[1]) : 5;
signal(SIGINT, &sigint_handler);

// Wait for any signal.
pause();
sleep(wait_time);
return 0;
}

问题是,孩子们应该写的日志文件没有 n行,这意味着不是所有的孩子都写了。有时没有人写任何东西,主程序根本不 wait(这意味着在本例中不调用 sleep())。
但如果我在主程序中取消注释 sleep(1),一切都会按我的预期工作。
我怀疑子进程没有足够的时间听 SIGINT
我正在处理的程序是一个任务控件,当我运行如下命令时:
我的行为不稳定。当我调用restart时,发送一个 restart my_program; restart my_program,然后调用一个新的 SIGINT,然后发送另一个 fork(),就像上面的例子一样。
如何确保所有子级都将在没有 SIGINT行的情况下解析 SIGINT?我正在测试我的程序,如果它能处理在SigIt发送之后不立即退出的程序。
例如,如果我在子程序的顶部添加 sleep(1),它不会被打印,主程序也不会等待任何东西,除非我 printf("child process started\n");一秒钟。即使只有一个子进程也会发生这种情况。

最佳答案

一切正常。有些子进程在设置信号处理程序之前,甚至在开始执行子二进制文件之前,就会被信号杀死。
在父进程中,在不存在子进程的情况下,而不是只是wait(),可以检查每个进程的标识和退出状态。替换为

{
pid_t p;
int status;

while ((p = wait(&status)) > 0) {
if (WIFEXITED(status))
printf("Child %ld exit status was %d.\n", (long)p, WEXITSTATUS(status));
else
if (WIFSIGNALED(status))
printf("Child %ld was killed by signal %d.\n", (long)p, WTERMSIG(status));
else
printf("Child %ld was lost.\n", (long)p);
fflush(stdout);
}
}

您将看到“丢失”的子进程被信号终止。这意味着子进程在准备捕获信号之前被终止。
我编写了自己的示例程序对,并进行了完整的错误检查。我决定使用 while (wait(NULL) > 0);sigprocmask()来代替信号处理程序,只是为了展示另一种方法来做同样的事情(并且不限于信号处理程序中的异步信号安全函数)。
母公司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>

const char *signal_name(const int signum)
{
static char buffer[32];
switch (signum) {
case SIGINT: return "INT";
case SIGHUP: return "HUP";
case SIGTERM: return "TERM";
default:
snprintf(buffer, sizeof buffer, "%d", signum);
return (const char *)buffer;
}
}

static int compare_pids(const void *p1, const void *p2)
{
const pid_t pid1 = *(const pid_t *)p1;
const pid_t pid2 = *(const pid_t *)p2;

return (pid1 < pid2) ? -1 :
(pid1 > pid2) ? +1 : 0;
}

int main(int argc, char *argv[])
{
size_t count, r, i;
int status;
pid_t *child, *reaped, p;
char dummy;

if (argc < 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s COUNT PATH-TO-BINARY [ ARGS ... ]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program will fork COUNT child processes,\n");
fprintf(stderr, "each child process executing PATH-TO-BINARY.\n");
fprintf(stderr, "Immediately after all child processes have been forked,\n");
fprintf(stderr, "they are sent a SIGINT signal.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (sscanf(argv[1], " %zu %c", &count, &dummy) != 1 || count < 1) {
fprintf(stderr, "%s: Invalid count.\n", argv[1]);
return EXIT_FAILURE;
}

child = malloc(count * sizeof child[0]);
reaped = malloc(count * sizeof reaped[0]);
if (!child || !reaped) {
fprintf(stderr, "%s: Count is too large; out of memory.\n", argv[1]);
return EXIT_FAILURE;
}

for (i = 0; i < count; i++) {
p = fork();
if (p == -1) {
if (i == 0) {
fprintf(stderr, "Cannot fork child processes: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else {
fprintf(stderr, "Cannot fork child %zu: %s.\n", i + 1, strerror(errno));
count = i;
break;
}
} else
if (!p) {
/* Child process */
execvp(argv[2], argv + 2);
{
const char *errmsg = strerror(errno);
fprintf(stderr, "Child process %ld: Cannot execute %s: %s.\n",
(long)getpid(), argv[2], errmsg);
exit(EXIT_FAILURE);
}
} else {
/* Parent process. */
child[i] = p;
}
}

/* Send all children the INT signal. */
for (i = 0; i < count; i++)
kill(child[i], SIGINT);

/* Reap and report each child. */
r = 0;
while (1) {
p = wait(&status);

if (p == -1) {
if (errno == ECHILD)
break;
fprintf(stderr, "Error waiting for child processes: %s.\n", strerror(errno));
return EXIT_FAILURE;
}

if (r < count)
reaped[r++] = p;
else
fprintf(stderr, "Reaped an extra child process!\n");

if (WIFEXITED(status)) {
switch (WEXITSTATUS(status)) {
case EXIT_SUCCESS:
printf("Parent: Reaped child process %ld: EXIT_SUCCESS.\n", (long)p);
break;
case EXIT_FAILURE:
printf("Parent: Reaped child process %ld: EXIT_FAILURE.\n", (long)p);
break;
default:
printf("Parent: Reaped child process %ld: Exit status %d.\n", (long)p, WEXITSTATUS(status));
break;
}
fflush(stdout);

} else
if (WIFSIGNALED(status)) {
printf("Parent: Reaped child process %ld: Terminated by %s.\n", (long)p, signal_name(WTERMSIG(status)));
fflush(stdout);

} else {
printf("Parent: Reaped child process %ld: Lost.\n", (long)p);
fflush(stdout);
}
}

if (r == count) {
/* Sort both pid arrays. */
qsort(child, count, sizeof child[0], compare_pids);
qsort(reaped, count, sizeof reaped[0], compare_pids);
for (i = 0; i < count; i++)
if (child[i] != reaped[i])
break;
if (i == count)
printf("Parent: All %zu child processes were reaped successfully.\n", count);
}

return EXIT_SUCCESS;
}

儿童c:
#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

const char *signal_name(const int signum)
{
static char buffer[32];
switch (signum) {
case SIGINT: return "INT";
case SIGHUP: return "HUP";
case SIGTERM: return "TERM";
default:
snprintf(buffer, sizeof buffer, "%d", signum);
return (const char *)buffer;
}
}

int main(void)
{
const long mypid = getpid();
sigset_t set;
siginfo_t info;
int result;

printf("Child: Child process %ld started!\n", mypid);
fflush(stdout);

sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGTERM);
sigprocmask(SIG_BLOCK, &set, NULL);
result = sigwaitinfo(&set, &info);
if (result == -1) {
printf("Child: Child process %ld failed: %s.\n", mypid, strerror(errno));
return EXIT_FAILURE;
}

if (info.si_pid == 0)
printf("Child: Child process %ld terminated by signal %s via terminal.\n", mypid, signal_name(result));
else
if (info.si_pid == getppid())
printf("Child: Child process %ld terminated by signal %s sent by the parent process %ld.\n",
mypid, signal_name(result), (long)info.si_pid);
else
printf("Child: Child process %ld terminated by signal %s sent by process %ld.\n",
mypid, signal_name(result), (long)info.si_pid);
return EXIT_SUCCESS;
}

使用例如。
gcc -Wall -O2 parent.c -o parent
gcc -Wall -O2 child.c -o child

使用例如。
./parent 100 ./child

其中 sigwaitinfo()是要分叉的子进程数,每个进程运行 100
错误被输出到标准错误。从父输出到标准输出的每一行以 ./child开头,从任何子输出到标准输出的每一行以 Parent:开头。
在我的机器上,输出中的最后一行总是 Child:,这意味着每个子进程 Parent: All # child processes were reaped successfully.ed都是使用 fork()收获和报告的。没有丢失任何东西, wait()fork()没有问题。
(请注意,如果指定的子进程数超过允许派生的数目,则父程序不会认为这是一个错误,而只使用允许的子进程数进行测试。)
在我的机器上,分叉和收获100个子进程对父进程来说已经足够了,因此每个子进程都可以到达它准备捕获信号的部分。
另一方面,父进程可以快速处理10个子进程(运行 kill()),以至于每个子进程在准备处理该信号之前都会被INT信号杀死。
下面是运行 ./parent 10 ./child时非常典型的情况的输出:
Child: Child process 19982 started!
Child: Child process 19983 started!
Child: Child process 19984 started!
Child: Child process 19982 terminated by signal INT sent by the parent process 19981.
Child: Child process 19992 started!
Child: Child process 19983 terminated by signal INT sent by the parent process 19981.
Child: Child process 19984 terminated by signal INT sent by the parent process 19981.
Parent: Reaped child process 19982: EXIT_SUCCESS.
Parent: Reaped child process 19985: Terminated by INT.
Parent: Reaped child process 19986: Terminated by INT.
Parent: Reaped child process 19984: EXIT_SUCCESS.
Parent: Reaped child process 19987: Terminated by INT.
Parent: Reaped child process 19988: Terminated by INT.
Parent: Reaped child process 19989: Terminated by INT.
Parent: Reaped child process 19990: Terminated by INT.
Parent: Reaped child process 19991: Terminated by INT.
Parent: Reaped child process 19992: Terminated by INT.
Parent: Reaped child process 19993: Terminated by INT.
Parent: Reaped child process 19994: Terminated by INT.
Parent: Reaped child process 19995: Terminated by INT.
Parent: Reaped child process 19996: Terminated by INT.
Parent: Reaped child process 19983: EXIT_SUCCESS.
Parent: Reaped child process 19997: Terminated by INT.
Parent: Reaped child process 19998: Terminated by INT.
Parent: Reaped child process 19999: Terminated by INT.
Parent: Reaped child process 20000: Terminated by INT.
Parent: Reaped child process 20001: Terminated by INT.
Parent: All 20 child processes were reaped successfully.

在20个子进程中,16个子进程在执行第一行 ./parent 20 ./child(或 printf())之前被INT信号杀死。(我们可以在 fflush(stdout)行之前向parent.c添加一个 printf("Child: Child process %ld executing %s\n", (long)getpid(), argv[2]); fflush(stdout);,以查看是否有任何子进程在执行之前被终止。)
在剩余的四个子进程(19982、19983、19984和19992)中,一个子进程(19982)在第一个 execvp()printf()之后,但在它设法运行 fflush()之前终止,这会阻塞信号并为子进程捕获信号做好准备。
只有剩下的三个子进程(19983、19984和19992)捕获父进程发送的INT信号。
如您所见,只需添加完整的错误检查,并添加足够的输出(以及 setprocmask()在有用的地方,因为标准输出在默认情况下是缓冲的),就可以运行几个测试用例,并构建一个更好的总体情况。
我正在处理的程序是一个任务控件,当我运行如下命令时:重新启动我的程序;重新启动我的程序,我会得到不稳定的行为。当我调用restart时,发送一个SIGINT,然后调用一个新fork(),然后发送另一个SIGINT,就像上面的例子一样。
在这种情况下,您在新fork准备就绪之前发送信号,因此 default disposition of the signal(Termination,for INT)定义了发生的情况。
解决这一根本问题的办法各不相同。注意,它是许多 init system问题的核心。如果孩子(这里是 fflush(stdout);合作)很容易解决问题,但在所有其他情况下都很困难。
一种简单的合作方法是让子进程在准备好采取行动时向其父进程发送一个信号。为了避免杀死对此类信息没有准备的父进程,可以使用默认忽略的信号(例如 my_program)。
选择睡眠一段时间,以便新的子进程有足够的时间准备采取行动,这是一种常见的、但相当不可靠的缓解此问题的方法。(特别是,所需的持续时间取决于子进程优先级和计算机上的总负载。)

关于c - C fork()和kill()同时无法正常工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48358121/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com