问题描述
我有一个 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()
准备好时将消息发送到管道,然后关闭它。或者直接关闭它。
请注意,管道本身提供了足够的同步,并且不需要等待循环。另请注意,子级可以检测到父级死亡而无需发送任何消息的情况,而您的共享内存方法不支持这种情况。