• 进程间通信(五)


    有名管道:FIFO

    到目前为止,我们只是可以在两个相关的程序之间传递数据,也就是说,由一个共同的祖先进程启动的程序。通常这并不是十分方便,因为我们希望不相关的进程也可以交换数据。

    我们可以使用FIFO来实现这个操作,通常称之为有名管道。一个有名管道是在文件系统中作为名字存在的一个特殊的文件类型(记住,在Linux一切皆文件),但是行为类似于我们已经了解的无名管道。

    我们可以通过命令行或是在一个程序内部创建有名管道。由于历史原因,用于创建有名管道的命令为mknod:

    $ mknod filename p

    然而,mknod命令并不在X/Open命令列表中,所以他并不是在所有的类Unix系统上都可用。更好的命令行方法是使用

    $ mkfifo filename

    在程序内部,我们可以使用两个不同的命令:

    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *filename, mode_t mode);
    int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0);

    类似于mknod命令,我们可以使用mknod函数创建许多特殊的文件类型。使用值为dev_t类型的0值以及使用S_IFIFO的或文件访问模型是创建有名管道函数的唯一可移植用法。在我们的例子中我们会使用简化的mkfifo。

    试验--创建有名管道

    作为fifo1.c,我们只需要输入下面的代码:

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

    int main()
    {
        int res = mkfifo("/tmp/my_fifo",0777);
        if (res == 0)
            printf("FIFO created/n");
        exit(EXIT_SUCCESS);
    }

    我们可以用下面的命令来查看这个管道:

    $ ls -lF /tmp/my_fifo
    prwxr-xr-x   1 rick   users 0 July 10 14:55 /tmp/my_fifo|

    注意,输出的第一个字符是p,表明是一个管道。后面的|是ls命令的-F选项添加上的,也表明是一个管道。

    工作原理

    程序使用mkfifo函数创建一个特殊的文件。尽管我们请求一个0777的模式,但是这个模式被用户掩码设置进行了修改,就如同在普通的文件创建中一样,所以最终的文件模式为755。如果我们的umask设置不同,例如为0002,我们就会看到不同的权限。

    我们可以使用rm命令或是在一个程序中通过使用unlink系统调用就如同移除一个普通文件一样删除FIFO。

    访问FIFO

    有名管道的一个非常有用的特性就在于,因为他们出现在文件系统中,我们可以像平时使用文件名一样来使用他们。在我们使用我们创建的FIFO进行更多的编程之前,我们可以使用通常的文件命令来探索FIFO文件的行为。

    试验--访问FIFO文件

    1 首先,让我们试着读取(空)FIFO文件:

    $ cat < /tmp/my_fifo

    2 现在试着向FIFO写入数据。我们需要使用另一个不同的终端,因为第一个终端现在正被挂起,等待数据出现在FIFO中。

    $ echo "sdsdfasdf" > /tmp/my_fifo

    我们可以看到由cat命令输出的结果。如果我们没有向FIFO发送任何数据,cat命令就会一直挂起直到我们中断他,通常是使用Ctrl+C。

    3 我们可以通过将第一个命令放入后端而同时完成两个操作:

    $ cat < /tmp/my_fifo &
    [1] 1316
    $ echo “sdsdfasdf” > /tmp/my_fifo
    sdsdfasdf
    [1]+  Done                    cat </tmp/my_fifo
    $

    工作原理

    因为在FIFO中没有数据,cat与echo程序就会阻塞,分别等待数据到达与某个进程读取数据。

    看一下第三步,cat进程初始在后台被阻塞。当echo命令使得一些数据可用时,cat命令就会读取数据并将其输出到标准输出。注意到cat命令会立即退出而不会等待更多的数据。他并没有阻塞,因为第二个命令将数据完全放入FIFO中之后,管道将会被关闭,所以在cat程序中的read调用会返回0,表明文件的结束。

    现在我们已经看到当我们使用命令行程序FIFO是如何动作的,现在让我们更详细的了解一下程序接口,这是当我们访问FIFO时允许我们在读取与写入的动作上执行更多控制的地方。

    注意:与pipe调用所创建的管道不同,FIFO是作为一个命名文件而存在的,而不是一个打开的文件描述符,而且他在可以读取与写入之后必须打开。我们可以使用我们在前面对文件进行操作时的open与close函数来打开与关闭一个FIFO。open调用需要传递一个FIFO的路径名,而不是一个常规文件。

    使用open打开FIFO

    打开FIFO的主要限制在于使用O_RDWR模型的程序也许会并不会打开一个FIFO进行读取与写入。如果一个程序这样做,结果是未定义的。这是一个合理的限制,因为通常我们使用FIFO只是在一个方向上传递数据,所以我们并不需要O_RDWR模型。进程将会读取其自己的输出端。

    如果我们希望在程序之间进行双向的数据传递,最好是使用一对FIFO,或是管道,每个用于一个方向,或是通过显示的关闭与重新打开FIFO来改变数据流的方向。我们将会在本章的稍后部分来探讨双向的数据交换。

    打开一个FIFO与打开一个常规文件的另一个不同就在于带有O_NONBLOCK选项的打开标记(open的第二个参数)。使用这个打开模式不仅会改变open调用是如何处理的,而且会改变在返回的文件描述符上read与write请求是如何处理的。

    O_RDONLY,O_WRONLY与O_NONBLOCK标记有四种合法的组合。我们将会依次考虑每一个。

    open(const char *path, O_RDONLY);

    在这种情况下,open调用会阻塞;直到有一个进程打开同一个FIFO用于写入时这个调用才会返回。这种情况类似于前面的cat例子。

    open(const char *path, O_RDONLY | O_NONBLOCK);

    open调用将会成功而且会立即返回,尽管此时的FIFO还没有被任何进程打开用于写入数据。

    open(const char *path, O_WRONLY);

    在这种情况下,open调用会阻塞,直到有一个进程打开同一个FIFO用于读取。

    open(const char *path, O_WRONLY | O_NONBLOCK);

    这个调用会立即返回,但是如果没有进程打开FIFO用于读取,open就会返回错误-1,而FIFO也不会被打开。如果有进程使得FIFO打开用于读取,返回的文件描述符可以用于写入FIFO。

    注意:O_NONBLOCK与O_RDONLY和O_WRONLY的使用之间的区别在于,在非阻塞的情况下,如果没有进程使得管道打开用于读取,用于写入的open调用就会失败,但是非阻塞的读取调用不会失败。O_NONBLOCK标记不会影响close调用的行为。

    试验--打开FIFO文件

    下面我们来看一下如何使用带有O_NONBLOCK标记的open来同步两个进程。我们不会使用多个例子程序,相反我们会编写一个测试程序,fifo2.c,这个程序会通过传递不同的参数使得我们来探讨FIFO的行为。

    1 我们由头文件与#define开始,并且检测所提供的命令行参数的正确个数:

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

    #define FIFO_NAME "/tmp/my_fifo"

    int main(int argc, char **argv)
    {
        int res;
        int open_mode = 0;

        if (argc < 2)
        {
            fprintf(stderr, "Usage: %s <some comination of O_RDONLY O_WRONLY O_NONBLOCK> /n", *argv);
            exit(EXIT_FAILURE);
        }

    2 假设程序传递了正确的参数,我们现在由这些参数设置open_mode的值:

        argv++;
        if (strncmp(*argv, "O_RDONLY", 8) == 0) open_mode |= O_RDONLY;
        if (strncmp(*argv, "O_WRONLY", 8) == 0) open_mode |= O_WRONLY;
        if (strncmp(*argv, "O_NONBLOCK", 10) == 0) open_mode |= O_NONBLOCK;

        argv++;
        if(*argv)
        {
            if(strncmp(*argv, "O_RDONLY", 8) == 0) open_mode |= O_RDONLY;
            if(strncmp(*argv, "O_WRONLY", 8) == 0) open_mode |= O_WRONLY;
            if(strncmp(*argv, "O_NONBLOCK", 10) == 0) open_mode |= O_NONBLOCK;
         }

    3 我们现在检测FIFO是否存在,如果需要我们要进行创建。然后FIFO会被打开并产生输出。最后FIFO会被关闭。

        if (access(FIFO_NAME, F_OK) == -1)
        {
            res = mkfifo(FIFO_NAME, 0777);
            if(res != 0)
            {
                fprintf(stderr, "Could not create fifo %s/n", FIFO_NAME);
                exit(EXIT_FAILURE);
            }
        }

        printf("Process %d open FIFO/n", getpid());
        res = open(FIFO_NAME, open_mode);
        printf("Process %d result %d/n", getpid(), res);
        sleep(5);
        if(res != -1) (void)close(res);
        printf("Process %d finished/n", getpid());
        exit(EXIT_SUCCESS);
        }

    工作原理

    这个程序允许我们在命令行上指定我们希望使用的O_RDONLY,O_WRONLY与O_NONBLOCK的组合。通过将已知的字符串与命令行参数进行比较,如果字符串匹配就会设置相应的标记。这个程序使用access函数来检测FIFO是否已经存在,如果需要则会创建这个文件。

    我们并没有销毁FIFO,因为我们并没有办法来区分是否还有其他的程序正在使用FIFO。

    带有O_NONBLOCK的O_RDONLY与O_WRONLY

    现在我们有了测试程序,让我们来试验一些组合;注意我们将文件程序放入后台:

    $ ./fifo2 O_RDONLY &
    [1] 152
    Process 152 opening FIFO
    $ ./fifo2 O_WRONLY
    Process 153 opening FIFO
    Process 152 result 3
    Process 153 result 3
    Process 152 finished
    Process 153 finished

    这是有名管道的最通常用法。他会使用读取进程启动并且在open调用中等待,然后当第二个程序打开FIFO时,两个程序都会继续。注意在open调用时,读取进程与写入进程都进行了同步。

    注意:当一个Linux进程被阻塞时,他并不消耗CPU资源,所以这种进程同步方法是很有效的。

    带有O_NONBLOCK的O_RDONLY与O_WRONLY

    这一次,读取进程会执行open调用并立即继续,尽管并不存在写入进程。写入进程也会在open调用之后立即继续,因为FIFO已经打开用于读取。

    $ ./fifo2 O_RDONLY O_NONBLOCK &
    [1] 160
    Process 160 opening FIFO
    $ ./fifo2 O_WRONLY
    Process 161 opening FIFO
    Process 160 result 3
    Process 161 result 3
    Process 160 finished
    Process 161 finished
    [1]+ Done                     fifo2 O_RDONLY O_NONBLOCK

    这两个例子是open模式的最通常组合。我们可以使用其他的一些组合来试验这个程序。

  • 相关阅读:
    6 【程序6 最大公约数和最小公倍数】
    5 【程序5 判断分数等级】
    4 【程序4 分解质因数】
    3 【程序3 水仙花数】
    2【程序2 输出素数】
    1 【程序1 不死神兔】
    终极解决傻X阿里钱盾新手开店及老卖家复核身份证照片模糊无法对焦问题
    struct和typedef struct彻底明白了
    CentOS 6.5 编译安装 LNMP环境
    Another MySQL daemon already running with the same unix socket
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2461941.html
Copyright © 2020-2023  润新知