【问题标题】:Communication between parent and child results in system lockup亲子沟通导致系统死机
【发布时间】:2021-12-29 00:15:49
【问题描述】:

我正在尝试通过在父程序中分叉子程序来在两个程序之间创建一些通信。

当我单独执行子程序时,它可以工作。这样做的目的是,如果有人在输入 1、2 或 3 后输入,则该程序将该数字打印为一个单词。但是如果按0并进入,程序就会退出。

现在我试图让父程序以一种方式执行子程序,它所做的只是退出程序,同时显示操作的进度。

当我执行我的程序时,我看到:

Child to start
Parent running OK

这表明子程序正在运行,否则我会看到:

Exec failed

因此,系统没有让我看到任何实际有用的输出,而是决定逐渐放慢速度,直到我移动鼠标时鼠标光标移动不顺畅,然后它到达了它不会移动的地步'对键盘没有反应,所以我不得不按住电源按钮来重置我的电脑。

我该如何解决这个问题,以便它可以与任何可以在我按 0 并从其中进入时退出的程序(我小时候使用的)一起工作?

这是我的父母代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

//Setup child read and write file handle named parr and parw respectively
//and parent read and write file handle named  to parr and parw respectively

#define kidr wrp[0]
#define kidw rdp[1]
#define parw wrp[1]
#define parr rdp[0]


int main(){
  int cmd=0;
  //setup and start pipes
  int wrp[2],rdp[2];
  if (pipe(wrp) == -1 || pipe(rdp) == -1){printf("ERROR: cant run pipes.\n");return -1;}

  //Start fork
  pid_t f=fork();

  if (f > 0){
  int wstat; //wait state data
  char buff[100]; //our data buffer
  close(kidr); //we are parent so close child handles
  close(kidw);
  struct timeval tv;
  fd_set readfds;
  tv.tv_sec = 1;
  tv.tv_usec = 0;
  printf("Parent running OK\n");
  while(1){
      //process other async events here

      pid_t wpid=waitpid(-1,&wstat,WNOHANG);
      if (wpid==-1){printf("Wait PID error\n");break;}
      if (wpid > 0){printf("Children closed OK\n");break;}
      //Process data only when child data is readable via pipe
      FD_ZERO(&readfds);FD_SET(parr, &readfds);
      select(parr+1, &readfds, NULL, NULL, &tv); 
      if(FD_ISSET(parr, &readfds)){
        memset(buff,0,99);
        int rd=read(parr, buff, 50);
        //doesnt seem to reach this point...
        if (rd > 0){
        printf("Got: %s\n", buff);
        }else{
          if (cmd==0){
        printf("sending data...\n");
        char*dat="0\n"; //parent sends 0 and the enter button.
        cmd++; //so this doesn't get called again
        write(parw,dat,strlen(dat));
          }
        }
      }
  }
  //close everything and exit
  close(parr);
  close(parw);
  return 0;
  }
  if (f==0){
  printf("Child to start\n");
  //Child mode.
  //Close parents
  close(parr);close(parw);
  //make stdio as child handles
  dup2(kidr,STDIN_FILENO);dup2(kidw,STDOUT_FILENO);
  //close old child handles
  close(kidw);close(kidr);
  execlp("/path/to/forkt","forkt",NULL);
  //We shouldn't get here unless 'ls' command isnt found
  printf("Exec failed\n");
  _exit(-1);
  }

  if (f==-1){
  //If fork() doesnt work...
  printf("Fork error\n");
  }

  return 0;
}

这是我为孩子编写的代码,我对其进行了编译,因此将其命名为 forkt。

#include <stdio.h>
#include <stdlib.h>
int main(){
  printf("The child has started\n\n");
  char c[100];
  while (1){
printf("Enter number or 0 to exit: \n>");
scanf("%s",c);
if (c[0]=='1'){printf("one\n");}
if (c[0]=='2'){printf("two\n");}
if (c[0]=='3'){printf("three\n");}
if (c[0]=='0'){return 0;}
  }
}

更新

我接受了通过 gdb 调试器运行我的父代码的建议。

我使用 gcc -g 开关编译了我的代码,然后使用 gdb a.out 执行它

然后在 gdb 中,我为第一行代码设置了一个断点,然后使用“run”命令,然后我继续使用“step”命令,直到找到此处的崩溃点:

  pid_t f=fork();
  if (f > 0){ // <- right here

这表明,如果孩子在没有父母的情况下自行运行,即使孩子运行良好,孩子也会以某种方式创建锁定(?)?

【问题讨论】:

  • 使用调试器准确找出进程在做什么。其中一个可能处于紧密循环中。一个问题是 tv 没有在每个循环中被重置。
  • 我添加了一个更新来显示调试结果,尽管我单步执行每条指令,但在我的代码的子进程中执行 fork() 后系统崩溃,对我来说没有任何建议无限循环
  • “崩溃点”是什么意思。之前没有提到任何崩溃。调试器中究竟显示了什么?请注意,您还可以让 gdb 调试子价格以查看它在做什么。
  • “崩溃点”是指系统刚刚锁定的点。它显示了每一行代码,直到上面显示的 pid 比较,然后它显示了与我手动执行程序相同的结果。我会在管道处理方面做错吗?

标签: c io fork child-process stdio


【解决方案1】:

AFAICS,在孩子发回一些东西之前,父母不会给孩子写任何东西,但孩子在从父母那里得到一些东西之前不会发送任何东西。那是一个僵局。缓冲也有问题。管道不是“交互式设备”,因此在缓冲区已满、流关闭或您调用 fflush() 之前不会刷新输出流。

这里有一些与你的代码非常相似的替代代码:

forkt.c

#include <stdio.h>

int main(void)
{
    printf("The child has started\n\n");
    fflush(stdout);
    char c[100];
    while (1)
    {
        printf("Enter number or 0 to exit:\n>");
        fflush(stdout);
        if (scanf("%s", c) != 1)
            return 0;
        fprintf(stderr, "Child received: [%s]\n", c);
        if (c[0] == '1')
        {
            printf("one\n");
        }
        if (c[0] == '2')
        {
            printf("two\n");
        }
        if (c[0] == '3')
        {
            printf("three\n");
        }
        if (c[0] == '0')
        {
            return 0;
        }
        fflush(stdout);
    }
}

这对于fflush(stdout) 的调用集合最为明显。

parent.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define kidr wrp[0]
#define kidw rdp[1]
#define parr rdp[0]
#define parw wrp[1]

int main(void)
{
    int cmd = 0;
    int wrp[2], rdp[2];

    fprintf(stderr, "Parent process: PID %d\n", getpid());

    if (pipe(wrp) == -1 || pipe(rdp) == -1)
    {
        fprintf(stderr, "ERROR: cant run pipes.\n");
        exit(1);
    }

    pid_t f = fork();

    if (f == -1)
    {
        fprintf(stderr, "Fork error\n");
        exit(1);
    }

    if (f > 0)
    {
        int wstat;
        char buff[100];
        close(kidr);
        close(kidw);

        fprintf(stderr, "Parent running OK - child %d\n", f);
        while (1)
        {
            pid_t wpid = waitpid(-1, &wstat, WNOHANG);
            if (wpid == -1)
            {
                fprintf(stderr, "Wait PID error\n");
                break;
            }
            if (wpid > 0)
            {
                fprintf(stderr, "Child %d exited\n", wpid);
                break;
            }

            for (cmd = 3; cmd >= 0; cmd--)
            {
                char buffer[30];
                int nb = snprintf(buffer, sizeof(buffer), "%d\n", cmd);
                int wr = write(parw, buffer, strlen(buffer));
                if (wr != nb)
                {
                    fprintf(stderr, "Parent failed to write: %d\n", wr);
                    exit(1);
                }
                fprintf(stderr, "Parent sent: %s", buffer);
                memset(buff, 0, 99);
                int rd = read(parr, buff, 50);
                if (rd > 0)
                {
                    fprintf(stderr, "Got: [[%.*s]]\n", rd, buff);
                }
                else
                {
                    fprintf(stderr, "Parent read failed\n");
                    exit(1);
                }
            }
        }

        close(parr);
        close(parw);
        return 0;
    }

    if (f == 0)
    {
        fprintf(stderr, "Child %d to start\n", getpid());
        close(parr);
        close(parw);
        dup2(kidr, STDIN_FILENO);
        dup2(kidw, STDOUT_FILENO);
        close(kidw);
        close(kidr);
        execlp("forkt", "forkt", NULL);
        fprintf(stderr, "Exec failed\n");
        _exit(-1);
    }

    return 0;
}

这里的手术范围更广。

当我运行代码时,有一次我得到了输出:

Parent process: PID 94693
Parent running OK - child 94694
Parent sent: 3
Child 94694 to start
Got: [[The child has started

]]
Parent sent: 2
Child received: [3]
Got: [[Enter number or 0 to exit:
>]]
Parent sent: 1
Child received: [2]
Got: [[three
Enter number or 0 to exit:
>]]
Parent sent: 0
Child received: [1]
Got: [[two
Enter number or 0 to exit:
>]]
Child received: [0]
Parent sent: 3
Got: [[one
Enter number or 0 to exit:
>]]
Parent sent: 2
Parent read failed

请注意,孩子的提示与输出混淆了。

【讨论】:

    猜你喜欢
    • 2018-10-13
    • 2021-04-18
    • 2019-11-23
    • 1970-01-01
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多