PIPE&FIFO

来源:互联网 发布:卖家淘宝店名可以改吗 编辑:程序博客网 时间:2024/05/29 14:30
Linux管道的实现机制
在Linux中,管道是一种使用非常频繁的通信机制。
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,
管道可以克服使用文件进行通信的两个问题,具体表现为:
(1) 限制管道的大小。实际上,管道是一个固定大小的缓冲区。
在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。
使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,
随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
(2) 读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。
当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。

管道的结构
在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。
通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如图下图所示。
 
 
管道的读写
管道写函数通过将字节复制到 VFS 索引节点指向的物理内存而写入数据,
而管道读函数则通过复制物理内存中的字节而读出数据。
当然,内核必须利用一定的机制同步对管道的访问,
为此,内核使用了锁、等待队列和信号。

当写进程向管道中写入时,它利用标准的库函数write(),
系统根据库函数传递的文件描述符,可找到该文件的 file 结构。
file 结构中指定了用来进行写操作的函数(即写入函数)地址,
于是,内核调用该函数完成写操作。写入函数在向内存中写入数据之前,
必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:
(1)内存中有足够的空间可容纳所有要写入的数据
(2)内存没有被读程序锁定
 
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。
否则,写入进程就休眠在 VFS 索引节点的等待队列中,
接下来,内核将调用调度程序,而调度程序会选择其他进程运行。
写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,
或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。
当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。
管道的读取过程和写入过程类似。
但是,进程可以在没有数据或内存被锁定时立即返回错误信息,
而不是阻塞该进程,这依赖于文件或管道的打开模式。
反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。
当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。
   
无名管道(PIPE)的特点
(1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。
写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据
(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
        int fd[2];
        pid_t pid;
        char szCmd[32] = "";
        char szTmp[32] = "";
        if (0 != pipe(fd))
        {
                cout << "create pipe error" << endl;
                return -1;
        }

        if (0 == (pid = fork())) // in child process
        {
                close(fd[1]);
                int offset = 0;
                read(fd[0], szCmd, sizeof(szCmd));
                for (;;)
                {
                        memcpy(szTmp, szCmd+offset, sizeof(szCmd));
                        offset += strlen(szTmp) + 1;
                        cout << "cmd = " << szTmp << endl;
                        if (0 == strcmp(szTmp, "q"))
                                break;
                        system(szTmp);
                }
                close(fd[0]);
                exit(0);
        }
        else if (pid > 0) // in parent process
        {
                close(fd[0]);
                write(fd[1], "ls -al", 7);
                write(fd[1], "cd ..", 6);
                write(fd[1], "q", 2);
                close(fd[1]);
        }

        return 0;
}


命名管道(FIFO),FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。
这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,
就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;
如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;
如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

fifo_write.cpp

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

using std::cout;
using std::endl;

int main()
{
        const char *fifoname = "/home/xjiahzh/tmp/xxxxtttt";
        if (-1 == access(fifoname, F_OK))
        {
                if (0 != mkfifo(fifoname, 0777))
                        exit(-1);
        }

        int pipefd = open(fifoname, O_WRONLY);
        if (-1 != pipefd)
        {
                int writefd = open("/home/xjiahzh/tmp/6451.txt", O_RDONLY, 0644);
                int sum = 0;
                char buff[PIPE_BUF + 1] = "";
                int tmp = read(writefd, buff, PIPE_BUF);
                while (tmp > 0)
                {
                        tmp = write(pipefd, buff, tmp);
                        if (-1 == tmp)
                        {
                                cout << "write fifo failure..." << endl;
                                exit(-1);
                        }
                        sum += tmp;
                        tmp = read(writefd, buff, PIPE_BUF);
                }
                close(pipefd);
                close(writefd);
        }

        return 0;
}

fifo_read.cpp

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <limits.h>

using std::cout;
using std::endl;

int main()
{
        const char *fifoname = "/home/xjiahzh/tmp/xxxxtttt";
        int pipefd = open(fifoname, O_RDONLY);
        int sum = 0;
        if (-1 != pipefd)
        {
                int readfd = open("readfile", O_WRONLY|O_CREAT, 0644);
                char buff[PIPE_BUF + 1] = "";
                int tmp = 0;
                do
                {
                        tmp = read(pipefd, buff, PIPE_BUF);
                        write(readfd, buff, tmp);
                        sum += tmp;
                }while (tmp > 0);

        }
        else
                exit(-1);

        cout << "read from fifo finish, total data is " << sum << endl;

        return 0;
}









0 0
原创粉丝点击