c-child进程似乎在一段时间内陷入了睡眠状态



我有一个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 createdn");

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);
}
}

问题是,即使父进程已将状态设置为"K"(已在内存映射的文件中签入(,子进程似乎也会陷入sleep while循环。这段代码已经在几台基于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;
}

问题是,即使父进程已将状态设置为"K"(已在内存映射的文件中签入(,子进程似乎也会陷入sleep while循环。

您的程序有一些奇怪的地方,其中之一是您正在使用共享内存执行此任务。有关更好的方法,请参见下文。

当前方法的问题

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

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

更好的方法

您希望子级等待来自父级的单字节或双字节指令。目前,您可以通过轮询共享内存段的内容来实现这一点,但正如您所发现的,设置和使用起来很复杂。使用管道将所需信息从父级传递到子级会容易得多:

  • setup:声明一个数组。致电pipe()
  • 子级使用:子级在管道上执行阻塞read()
  • 父级使用:write()准备好后将消息发送到管道,然后关闭它。或者只是关闭它

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

共享内存区域有利于共享大量数据,但在进程之间进行通信是一种糟糕的方式。原因是您无法获得某个内容已更改的通知,如果共享内存的另一个用户死亡,您也无法获得通知。

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

你使用的是这样一个循环:

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

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

最新更新