检测从程序打开fifo的时间

时间:2011-10-28 16:04:12

标签: c unix race-condition fifo

我有一种情况需要检查一个fifo的另一面是否已打开它,但是我不能使用open,否则程序将开始做的事情。

为什么我必须这样做:我有一个启动服务器程序(由我创建)的程序(监视器)。监视器使用此fifo进行通信,因为在服务器已启动时可以关闭/重新打开监视器。

问题是当监视器启动服务器时:在这种情况下,我必须以某种方式等待创建fifos,然后打开它们。 实际上我在显示器上使用了一段时间来检查何时创建了fifos,但是以这种方式打开了服务器可以执行的之前的(即使mkfifo之后的指令实际上是开放!!!)。

你可能会说我在显示器上以错误的顺序打开了fifos(我在读取fifo之前打开了写入FIFO(WRONLY)),问题是我无法恢复此命令,因为它是必需的服务器将在打开(RDONLY)fifo上等待客户端。

有关如何避免这种竞争条件的任何建议? 在检查是否创建了fifos之后,我实际上正在监视器中使用睡眠,这显然解决了问题,但我认为绝对不正确。

感谢所有人

修改1:

这就是目前的工作方式

服务器

mkfifo(fifo1)
mkfifo(fifo2)
open(fifo1 O_RDONLY)
open(fifo2 O_WRONLY)

监视器

while (fifo1 doesn't exists && fifo2 doesn't exists);
open(fifo1 O_WRONLY)
open(fifo2 O_RDONLY)

我认为竞争条件现在非常明确,重要的是要注意到fifos正在阻塞(只有RDONLY阻塞,即使另一方没有任何人也不会阻止=> 这是unix行为,不是我设计的)。

编辑2:

竞争条件发生在第一个十五开放水平。 我必须在显示器执行之前在服务器上打开第一个fifo。

4 个答案:

答案 0 :(得分:1)

您可能希望使用sem_open()的命名信号量,该信号量对文件系统级别的每个程序都是可见的,以便同步这两个程序。基本上你的监视程序将等待锁定的信号量,直到服务器增加它。此时,所有的fifo都将被初始化,你可以使用你的监视器移动foward,其中fifo处于已知状态。

确保在O_CREAT的初始调用中使用O_EXCLsem_open()标志,以便创建信号量是原子的。例如,如果监视器和服务器程序尚不存在,它将尝试在启动时创建信号量...如果它存在,则调用将失败,这意味着监视器或服务器,但不是两个程序,获得了创建信号量并初始化它的权利。当服务器初始化fifo时,监视器等待信号量...一旦fifo初始化,服务器释放信号量,然后监视器就能继续。

这是我想象的过程......我相信它应该有效地解决你的竞争条件:

在监视器中:

  1. 创建命名信号量并将其设置为锁定状态
  2. 启动服务器
  3. 等待FIFO在文件系统级别可见
  4. 打开fifo1进行书写(非阻止)
  5. 打开fifo2进行阅读(阻止服务器打开fifo2进行写入)
  6. 等待信号量(可能超时),直到服务器解锁,表明它已成功打开两个FIFO。
  7. 在服务器中:

    1. 创建FIFO
    2. 打开fifo1(阻止显示器打开它进行书写)
    3. 打开fifo2(非阻止)
    4. 现在服务器已打开两个FIFO
    5. ,解锁信号量

      所以基本上你的监视器无法继续,直到有一个“已知状态”,其中所有内容都已正确初始化...服务器通过命名的信号量向监视器指示状态。

答案 1 :(得分:1)

如果按正确的顺序执行open(),则没有竞争条件。 (唯一可能的比赛将是第三个过程干扰相同的fifos)从精细的手册:

“但是,它必须在两端同时打开才能继续对其进行任何输入或输出操作。打开FIFO以便正常读取阻塞,直到某个其他进程打开相同的FIFO 写作,反之亦然。“

这意味着订购

{进程1:开放(fifo1,RO); process2:open(fifo1,WO); }

...

{过程1:开放(fifo2,WO); process2:open(fifo2,RO); }

将永远成功(假设没有进程饥饿)每个fifo的操作顺序并不重要;对于fifo1,process1或process2可以先行(并且将被阻止,直到另一方成功)。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define FIFO1 "fifo1"
#define FIFO2 "fifo2"

void do_master(void);
void do_slave(void);
void randsleep(unsigned max);

/************************************/
void do_master(void)
{
int in,out, rc;
char buff[20];

randsleep(5);
mkfifo(FIFO1, 0644);
randsleep(7);
mkfifo(FIFO2, 0644);

out = open(FIFO2, O_WRONLY);
if (out == -1) {
    fprintf(stderr, "[Master]: failed opening output\n" );
    return;
    }
fprintf(stderr, "[Master]: opened output\n" );
in = open(FIFO1, O_RDONLY);
if (in == -1)  {
    fprintf(stderr, "[Master]: failed opening input\n" );
    close(out);
    return;
    }
fprintf(stderr, "[Master]: opened input\n" );

rc = write( out, "M2S\n\0" , 5);
fprintf(stderr, "[Master]: wrote %d\n", rc );

rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Master]: read %d: %s\n", rc, buff );
unlink(FIFO1);
unlink(FIFO2);
}
/***********************************/
void do_slave(void)
{
int in,out, rc;
unsigned iter=0;
char buff[20];

loop1:
in = open(FIFO2, O_RDONLY);
if (in == -1) {
    fprintf(stderr, "[Slave%u]: failed opening input\n", ++iter );
    randsleep(2);
    goto loop1;
    }
fprintf(stderr, "[Slave]: opened input\n" );

loop2:
out = open(FIFO1, O_WRONLY);
if (out == -1) {
    fprintf(stderr, "[Slave%u]: failed opening output\n", ++iter );
    randsleep(3);
    goto loop2;
    }
fprintf(stderr, "[Slave]: opened output\n" );

rc = write( out, "S2M\n\0" , 5);
fprintf(stderr, "[Slave]: wrote %d\n", rc );

rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Slave]: read %d:%s\n", rc, buff );
}
/*************************************/
void randsleep(unsigned max)
{
unsigned val;
val = rand();
val %= max;
sleep(val);
return;
}
/*************************************/
int main(void)
{
int rc;

switch (rc=fork()) {
    case -1: exit(1); break;
    case 0: do_slave(); break;
    default: do_master(); break;
    }
exit (0);
}

答案 2 :(得分:0)

考虑使用类型为SOCK_STREAM的Unix域套接字。服务器将bind其套接字转换为文件系统中的名称。每个客户端都有自己的与服务器的双向连接。

答案 3 :(得分:0)

至少我发现的'直到现在让我明白没有办法检测是否打开了一个fifo ,除非你打开它。

修改1:

正如杰森所说,有两种方式(但我的作业都不允许):

1)*您可以通过/ proc / PID / fd进行搜索(用数字进程ID替换PID)以查看哪些客户端进程已经打开了您的FIFO * 2)另一种选择是在FIFO上调用热熔器

然而,第一个需要老师不想要的东西:在proc / PID / fd里面观察。我听到的第二个需要root权限,这也是我不能做的事情。希望这将有助于将来的其他人。