子进程似乎在 while 循环中陷入睡眠

问题描述

我有一个 C 程序,它在循环中的某个点分叉子进程。子进程等待父进程完成它的工作(一些数值计算)。如果出现问题,父进程中止,子进程应从分叉时的状态继续,并通过一些修改重试计算。否则,父进程继续运行,子进程应该被杀死。

父子进程之间的通信是通过内存映射文件进行的,该文件只有1个字节作为表示父进程状态的字符。

内存映射是这样完成的

    char child_flag[]="W";
    
    fp1 = fopen( "child_interface.dat","wb");
    // the interface file has two bytes,but only one is meaningful to the program
    fwrite(child_flag,1,sizeof(child_flag),fp1); 
    fclose(fp1);
    printf("child_interface.dat created\n");
    
    if(mmap_child_flag() ==0) {
        printf("memory map of parent-child interface successful.\n");
        fflush(stdout);
    }

子进程中的等待循环是这样的

child_pid = fork();                     
if (child_pid ==0) { /* child process,wait for parent process to finish*/

    mmap_child_flag();

    while(child_file[0]=='W' ){  //Child waits
        usleep(100000);
    }
    if(child_file[0]=='R'){ // run child process (as a new parent process)
        child_file[0]='W';
        goto label2;
    }
    if(child_file[0]=='K'){ //Kill child process
        exit(0);
    }
}

问题是子进程似乎陷入了睡眠 while 循环,即使父进程已将状态设置为“K”(在内存映射的文件中检查)。这段代码已经在多台基于linux的超级计算机上运行过,行为似乎很不一致。在某些平台上,它可以顺利运行,但在其他一些平台上,它经常卡在 while 循环中。有时,如果我在调用 usleep 之后在 while 循环中添加一些语句,它就可以正常运行。

但是,我不确定 sleep while 循环是否是此问题的根本原因。我的猜测是,因为除了检查内存中的一个字节之外,该进程几乎无事可做,所以系统让它一直休眠,并以某种方式“忘记”让它检查内存。 Linux 系统会出现这种情况吗?

这是进行实际映射的函数

/* Memory map for parent-child processes interface */
int mmap_child_flag()
{
    int fd_child;    
    struct stat st_child; 
    
    // open files
    if ((fd_child = open("child_interface.dat",O_RDWR)) == -1){
        perror("open child_interface.dat");
        exit(1);
    }
    // stat
    if (stat("child_interface.dat",&st_child) == -1){
        perror("stat of child_interface.dat");
        exit(1);
    }
    // map,child_file is global char array
    child_file = mmap(0,st_child.st_size,PROT_WRITE,MAP_SHARED,fd_child,0);
    if (child_file == (char *)(-1)) {
        perror("mmap child_interface.dat");
        exit(1);
    }
    return 0;
}

解决方法

共享内存区域有利于共享大量数据,但对于进程之间的通信来说是一种糟糕的方式。原因是你无法收到某事已更改的通知,如果共享内存的其他用户死亡,你也不会收到通知。

要在两个进程之间进行通信,如果您需要创建单向通信通道,请使用 pipe(),或者如果您需要双向通信,请使用 socketpair()。您可以使用 poll() 等待对方发送一些数据。如果另一端的进程终止,您也会收到通知。

您使用的是这样的循环:

while(child_file[0]=='W' ){  //Child waits
    usleep(100000);
}

这很糟糕,因为您平均浪费了 50 毫秒的时间,而这些时间本可以花在做一些有用的事情上。除此之外,还有一个问题是编译器和 CPU 有时都会改变写入内存的顺序。如果 child_file 中的数据多于开始时的标志,则这可能是一个问题,除非您使用 atomics 或显式障碍。

,

问题是子进程似乎陷入了 sleep while 循环,即使父进程已将状态设置为“K”(在内存映射文件中检查)。

你的程序有几个奇怪的地方,其中之一就是你完全使用共享内存来完成这项任务。请参阅下文以了解更好的方法。

当前方法的问题

然而,就目前的问题而言,您有一个同步问题。映射内存的内容正在子进程范围之外更改,但您没有理由怀疑可能是这种情况。因此,编译器可以假设,如果等待循环条件在第一次求值时满足,那么在随后的每次求值时也将满足。

对于更复杂的交互,您可能需要设置进程共享互斥锁或类似的来保护对共享内存的访问,但为此,将 child_file 声明为指向的指针可能就足够了volatile char

更好的方法

您希望子进程等待来自父进程的一个或两个字节的指令。您目前通过轮询共享内存段的内容来完成此操作,但正如您所发现的那样,设置和使用起来很复杂。使用管道将所需的信息从父级传递给子级会容易得多:

  • setup:声明一个数组。致电pipe()
  • child use:child 对管道执行阻塞 read()
  • parent 使用:write() 准备好时将消息发送到管道,然后关闭它。或者直接关闭它。

请注意,管道本身提供了足够的同步,并且不需要等待循环。另请注意,子级可以检测到父级死亡而无需发送任何消息的情况,而您的共享内存方法不支持这种情况。