如何使用C中的信号将多个值从子进程传递到父进程?

时间:2018-04-18 00:23:15

标签: c process signals fork

我必须将存储在子进程中的3个单独的值(例如“10”,“15”,“20”)传递给父进程。我不能使用共享内存或管道,只能使用信号和处理程序。如何仅使用信号将这些值从子项传递给父项?我尝试传递退出代码但你只能有一个退出值。我需要传递3个单独的值。有什么建议?另外,我的父母有两个孩子。这两个孩子都必须将自己的3个独立值传递给父级,以便父级可以最终添加它们。

2 个答案:

答案 0 :(得分:0)

使用rt_sigqueueinfo()将信号从孩子发送到父母。您也可以使用sigqueue()

请参考此示例 send signal to parent with data

答案 1 :(得分:0)

好吧,正如我在其中一条评论中所承诺的,这是解决问题的方法。首先要说的是这个解决方案在Linux中不能正常工作,因为linux没有对发送的大量信号进行信号传递的保证(当目标进程没有足够的时间来处理它们时),所以BSD已经使用过版本(也应该运行,但未在Mac OS / X和其他unices上测试过)。我将通过四个步骤解释解决方案,因为第一步可能足以通过作业,但还不足以将其视为将信息从孩子传递给父母的正确工作方式。此外,由于发布代码的空间有限,我只发布了代码的第一个版本,所有其他版本必须从github下载(提供的链接)。

使用freebsd 11.0对覆盆子pi进行了实施,这使得信号传输可靠。失败的系统是Debian 9.4,内核版本为4.9.0-6-amd64

第一版

正如家庭作业问题所述,父母唯一要做的就是添加数字并打印结果。然后,一个好的计划是将一个数字N编码为父,作为n类型的SIGUSR1信号。父母只需计算信号并在孩子完成时打印数字(我们在wait(2)系统调用成功返回时确定此信息)

child.c

/* 00001 */ #include <sys/types.h>
/* 00002 */ #include <sys/time.h>
/* 00003 */ #include <signal.h>
/* 00004 */ #include <stdio.h>
/* 00005 */ #include <stdlib.h>
/* 00006 */ #include <unistd.h>
/* 00007 */ #include <time.h>

/* 00008 */ #define F(fmt) "<%d>:"__FILE__":%d:%s: " fmt, my_pid, __LINE__, __func__

/* 00009 */ pid_t my_pid;

/* 00010 */ int main()
/* 00011 */ {
/* 00012 */         pid_t parent_pid = getppid();
/* 00013 */         struct timeval now;
/* 00014 */         gettimeofday(&now, NULL);
/* 00015 */         srand((int)now.tv_sec ^ (int)now.tv_usec);
/* 00016 */         my_pid = getpid();
/* 00017 */         int sum = 0;

/* 00018 */         int n = rand() % 3 + 1;
/* 00019 */         int i;
/* 00020 */         for (i = 0; i < n; i++) {
/* 00021 */                 int number = rand() % 10 + 10;
/* 00022 */                 printf(F("Sending %d\n"), number);
/* 00023 */                 int j;
/* 00024 */                 for (j = 0; j < number; j++) {
/* 00025 */                         printf(F("kill(%d, SIGUSR1);\n"), parent_pid);
/* 00026 */                         kill(parent_pid, SIGUSR1);
/* 00027 */                 }
/* 00028 */                 sum += number;
/* 00029 */         }
/* 00030 */         printf(F("my sum = %d\n"), sum);
/* 00031 */ }

(每个孩子随机选择1到4个数字,在10 ... 19范围内随机选择,并将该SIGUSR1个信号发送给父母)

父母是这样的:

parent.c

/* 00001 */ #include <errno.h>
/* 00002 */ #include <signal.h>
/* 00003 */ #include <stdio.h>
/* 00004 */ #include <stdlib.h>
/* 00005 */ #include <string.h>
/* 00006 */ #include <sys/time.h>
/* 00007 */ #include <sys/types.h>
/* 00008 */ #include <sys/wait.h>
/* 00009 */ #include <time.h>
/* 00010 */ #include <unistd.h>

/* 00011 */ #define F(fmt) "[%d]:"__FILE__":%d:%s: " fmt, my_pid, __LINE__, __func__

/* 00012 */ #define NCHILDREN 8

/* 00013 */ char child[] = "child";

/* 00014 */ pid_t my_pid;

/* 00015 */ int sum = 0; /* here's where the number of signals is accumulated */

/* 00016 */ void handler(int sig, siginfo_t *info, void *unused)
/* 00017 */ {
/* 00018 */         switch (sig) {
/* 00019 */         case SIGUSR1: sum++; break;
/* 00020 */         }
/* 00021 */ }

/* 00022 */ int main()
/* 00023 */ {
/* 00024 */         int i;

/* 00025 */         my_pid = getpid();

/* 00026 */         struct sigaction sa, oldsa; 
/* 00027 */         sa.sa_sigaction = handler;
/* 00028 */         sa.sa_flags = SA_RESTART; /* restart the wait(2) syscall below */
/* 00029 */         sigemptyset(&sa.sa_mask);
/* 00030 */         sigaddset(&sa.sa_mask, SIGUSR1);
/* 00031 */         sigaction(SIGUSR1, &sa, &oldsa);

/* 00032 */         pid_t res;

/* 00033 */         for (i = 0; i < NCHILDREN; i++) {
/* 00034 */                 res = fork();
/* 00035 */                 if (res > 0) /* parent */
/* 00036 */                         continue;
/* 00037 */                 else if (res < 0) {
/* 00038 */                         fprintf(stderr,
/* 00039 */                                 F("ERROR: fork: %s (errno = %d)\n"),
/* 00040 */                                 strerror(errno), errno);
/* 00041 */                         exit(EXIT_FAILURE);
/* 00042 */                 } else { /* child */
/* 00043 */                         execlp(child, child, NULL);
/* 00044 */                         fprintf(stderr,
/* 00045 */                                 F("ERROR: exec: %s: %s (errno = %d)\n"),
/* 00046 */                                 child, strerror(errno), errno);
/* 00047 */                         exit(EXIT_FAILURE);
/* 00048 */                 }
/* 00049 */         }

/* 00050 */         while (wait(NULL) >= 0); /* wait for children until no more */

/* 00051 */         sigaction(SIGUSR1, &oldsa, NULL);

/* 00052 */         printf(F("sum = %d\n"), sum);
/* 00053 */ }

此示例将使父级打印正确的总和,因为您可以进行探测(linux在某些情况下仅在sum = 1显着失败,因为它只能处理每种类型的第一个信号,导致单个数量的值1)linux的问题是如果接收器没有经过足够的调度以使系统能够接收信号并允许调用处理程序,则linux无法确保并传送多个信号。只需在行sleep(1)的{​​{1}}系统调用之后在child.c中插入kill(2)即可。

许多读者会抱怨解决方案的实现方式,因为这不是将信息传递给父级的好方法(没有将数字本身分隔以允许父级分离和识别它们的形式),以及某种方式区分传递的不同数字,并确定要包含的发件人。因此,父母不仅要打印总和,还要打印哪个数字来自哪个孩子或者为每个孩子分隔不同数字的方式。

第二版(第一次尝试)

在这个版本中,我们引入了60系统调用及其进入信号处理程序的方法来识别这种信号的发送者(sigaction(2) POSIX选项)。我将读者引用到手册页,因为它在那里有很好的文档记录(即使对于linux,它仍继续处理信号而无需保证信号传递)和files can be downloaded from here

父母要复杂得多。它有一个用于每个孩子的表和一个处理程序,它提取信号的发送者pid,搜索子时隙,并在其中存储接收到的信号的累积数量,并将SA_SIGINFO信号作为结束处理数据标记,允许我们存储累计值。

父母现在打印从每个孩子收到的号码,如果您尝试此示例,您将看到另一个问题:内核不保证信号的传递顺序。这意味着属于下一个号码的一些SIGUSR2信号在完成前一个号码的SIGUSR1信号之前被处理。因此,在发送并处理的最后一个SIGUSR2信号之前,我们无法发送新的SIGUSR1信号。我们更正了这一点,在下一个版本中引入了延迟SIGUSR2

第三版(有延迟)

在这个版本中,我们引入子程序的延迟,因此在我们给父母时间处理所有未决信号之前,不会发送任何号码。这样,在有时间处理父进程之前,不会向父进程发送与下一个数字对应的sleep(1)These are the files for this version

在这种情况下,您会观察到输出会在几个点延迟,以便父母有时间在发送新信号之前提供待处理信号。

VERSION FOUR(最终版本)

我们的最终方法试图消除延迟,让父母在收到SIGUSR2后立即向孩子发送信号,表示号码的结束,以确认接收号码和让孩子继续我会在最后评论一些竞争条件,可以使用已包含在其中的一些代码保存(因为我在帖子空间中有限,我将在github和{{3}上发布它})

对比赛条件的评论

在最终版本中,由于我们无法在SIGUSR2系统调用中启用中断,因此在子级中存在不可避免的竞争条件,因此我们无法在中断被阻止的情况下进入pause(2)系统调用,解锁它们并听取下一个中断。 pause(2)中的行:

child.c

允许调度父节点,发送ack信号,并在while循环中00061: printf(F("kill(%d, SIGUSR2);\n"), parent_pid); 00062: sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL); 00063: yet_unacknowledged = 1; 00064: kill(parent_pid, SIGUSR2); 00065: sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL); 00066: while (yet_unacknowledged) sleep(1); 的检查和yet_unacknowledged语句之间处理信号。这些行最初写为:

sleep(1)

但是这样,有时孩子会被阻止,因为已经从孩子的00066: pause(); /* wait for an interrupt */ 到父母和kill系统调用之间传递(和处理)了确认信号。

但是在检查执行pause(2)后无法启用SIGUSR2中断之后,唯一的方法就是考虑处理程序在中间执行并执行单pause() 1}}一次。接下来循环传递,检查将失败,并且最多只执行一次sleep(1)调用。

如何在linux

中实现所有这些功能

好吧,我从原来的目标扩展得更远,而且我不会在linux中展示完整的工作解决方案。由于linux无法确保将更多的一个信号传递给进程,因此实现的唯一方法是为父进程安装一个机制来确认每个接收到的信号,并且子进程在收到信号之前不发送下一个信号以前的承认。这完全超出了系统调用介绍的初学者课程的范围(更好的网络协议课程),所以如果你有兴趣,你将不得不向你的老师询问有关这方面的更多信息。