【问题标题】:Detect when a fifo is opened from a program检测何时从程序打开 fifo
【发布时间】:2012-09-21 05:44:23
【问题描述】:

我有一种情况,我需要检查 fifo 的另一端是否已打开它,但是我不能使用 open,否则程序将开始执行操作。

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

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

您可能会说我在显示器上以错误的顺序打开了 fifo(我在读取 fifo 之前打开了 Write 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)

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

编辑 2:

竞态条件发生在第一个 fifo 打开级别。 我必须在监视器打开之前打开服务器上的第一个 fifo。

【问题讨论】:

    标签: c unix race-condition fifo


    【解决方案1】:

    您可能希望使用带有sem_open() 的命名信号量,该信号量在文件系统级别对每个程序都是可见的,以便同步两个程序。基本上,您的监控程序将等待锁定的信号量,直到服务器增加它。此时,所有的先进先出将被初始化,您可以在先进先出处于已知状态的情况下继续使用您的显示器。

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

    这是我所设想的过程......我相信它应该有效地解决你的比赛条件:

    在监视器中:

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

    在服务器中:

    1. 创建 FIFO 的
    2. 打开fifo1(阻止直到监视器打开它进行写入)
    3. 打开fifo2(非阻塞)
    4. 现在服务器已打开两个 FIFO,解锁信号量

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

    【讨论】:

    • 嗯,我认为有一个问题:如果我在打开 fifo 之前解锁服务器上的信号量,监视器仍然会“赢得”先打开 fifo 的竞赛。而且我无法在打开后解锁信号量,因为它是一个阻塞打开:\
    • 在打开 fifo 时,因为 RDONLY 是一个阻塞调用,您甚至可以在他们拥有 WCHAN = pipe_wait 的进程屏幕上看到它
    • 可以在调用open() 之后 使用命名信号量,让您知道服务器已准备好在其打开的FIFO 上执行read()。换句话说,在释放信号量之前,您的监视器无法在 FIFO 上执行写入操作……基本上,读取器解锁信号量,允许写入器写入 FIFO。
    • 我想你误解了这个问题,我必须在监视器打开(在服务器上)第一个 fifo 之前。两个打开fifos后都没有问题。
    • @Fire-Dragon-DoL :手动忙等待会浪费 CPU 周期...pollselect 让进程进入睡眠状态,并在有内容可供阅读时将其唤醒。其次,您提到服务器必须保持阻塞状态,但是当其他任何人打开 FIFO 进行写入时它会解除阻塞,包括客户端......那么服务器何时允许从open() 解除阻塞?
    【解决方案2】:

    如果您以正确的顺序执行 open(),则不存在竞争条件。 (唯一可能的竞争是干扰相同 fifos 的第三个进程)来自精美手册:

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

    这意味着排序

    {进程1:打开(fifo1,RO);进程2:打开(fifo1,WO); }

    ...

    { 进程1:打开(fifo2,WO);进程2:打开(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);
    }
    

    【讨论】:

    • 我明白了,但我不明白为什么我遇到了竞态条件问题(我遇到的问题是 fifo 在以只读模式打开之前早早以只写模式打开)
    • 好吧,我们也不知道,直到您向我们展示一些真实的代码。在管道的任一端打开应该阻塞,直到另一端打开。添加涉及 /proc/、readdir()、stat() 等的检查只会为其他种族打开一个窗口。 Open() 是原子的;两边的两个 open() 是一个同步原语。 (或其中一个永远阻塞)
    • 我无法显示代码,这是一个“大”项目(至少有 20 个文件),否则我会这样做。
    • 好吧,您可以将其简化为一个演示您的问题的最小示例。由于没有中间代码(见我的回答),这应该是相当微不足道的。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      至少我到现在为止的发现让我明白没有办法检测fifo是否打开,除非你也打开它。

      编辑 1:

      正如 Jason 所说,有两种方法(但在我的作业中都不允许):

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

      但是,第一个需要一些老师不想要的东西:在 proc/PID/fd 内部观察。我听到的第二个需要root权限,这又是我不能做的事情。希望这会在未来对其他人有所帮助。

      【讨论】:

      • 从技术上讲,如果您想知道 FIFO 是否被另一个进程打开,并且您不想尝试打开文件本身以避免阻塞,您可以通过 /proc/ 进行搜索*PID*/fd(将 PID 替换为数字进程 ID)以查看哪些客户端进程已经打开了您的 FIFO ... /proc/*PID*/fd 中的值是指向的符号链接该进程打开的文件。如果您知道客户端 PID 值是多少,您的搜索时间可以进一步缩短,否则您可以简单地对所有正在运行的进程进行暴力搜索。
      • 另一种选择是在您的 FIFO 上调用 fuser ...您可以使用 popen() 打开一个到将运行该命令然后返回结果的子进程的管道。
      • 嗯,这很有趣,也许它们是正确的答案。但是,老师明确表示不要使用 /proc/PID/fd (您可能不知道这一点,但问题不在于作业)。至于 fuser,我听说它需要 root 权限(所以我不能使用它)。是真的吗?
      • fuser 本身用作非root 用户没有问题...在没有root 权限的情况下使用fuser 的主要问题是不属于当前用户的进程可能不会出现在fuser的回复中。
      • 这可能是个问题,因为我不知道谁在运行服务器
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-25
      相关资源
      最近更新 更多