Linux进程间通讯之管道(pipe)、命名管道(FIFO)与信号(Signal)

 

整理自网络html

Unix IPC包括:管道(pipe)、命名管道(FIFO)与信号(Signal)node

 

管道(pipe)网络

管道可用于具备亲缘关系进程间的通讯,有名管道克服了管道没有名字的限制,所以,除具备管道所具备的功能外,它还容许无亲缘关系进程间的通讯;数据结构

实现机制:函数

管道是由内核管理的一个缓冲区,至关于咱们放入内存中的一个纸条。管道的一端链接一个进程的输出。这个进程会向管道中放入信息。管道的另外一端链接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不须要很大,它被设计成为环形的数据结构,以便管道能够被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另外一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另外一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。post

从原理上,管道利用fork机制创建,从而让两个进程能够链接到同一个PIPE上。最开始的时候,上面的两个箭头都链接在同一个进程Process 1上(链接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个链接也复制到新的进程(Process 2)。随后,每一个进程关闭本身不须要的一个链接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入链接,Process 2关闭输出到PIPE的链接),这样,剩下的红色链接就构成了如上图的PIPE。url

实现细节:spa

在 Linux 中,管道的实现并无使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。经过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。以下图设计

有两个 file 数据结构,但它们定义文件操做例程地址是不一样的,其中一个是向管道中写入数据的例程地址,而另外一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是一般的文件操做,而内核却利用这种抽象机制实现了管道这一特殊操做。code

 

关于管道的读写

      管道实现的源代码在fs/pipe.c中,在pipe.c中有不少函数,其中有两个函数比较重要,即管道读函数pipe_read()和管道写函数pipe_wrtie()。管道写函数经过将字节复制到 VFS 索引节点指向的物理内存而写入数据,而管道读函数则经过复制物理内存中的字节而读出数据。固然,内核必须利用必定的机制同步对管道的访问,为此,内核使用了锁、等待队列和信号。

     当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操做的函数(即写入函数)地址,因而,内核调用该函数完成写操做。写入函数在向内存中写入数据以前,必须首先检查 VFS 索引节点中的信息,同时知足以下条件时,才能进行实际的内存复制工做:

       ·内存中有足够的空间可容纳全部要写入的数据;

       ·内存没有被读程序锁定。

     若是同时知足上述条件,写入函数首先锁定内存,而后从写进程的地址空间中复制数据到内存。不然,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其余进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间能够容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存以后,内存被解锁,而全部休眠在索引节点的读取进程会被唤醒。

     管道的读取过程和写入过程相似。可是,进程能够在没有数据或内存被锁定时当即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程能够休眠在索引节点的等待队列中等待写入进程写入数据。当全部的进程完成了管道操做以后,管道的索引节点被丢弃,而共享数据页也被释放。

Linux函数原型

#include <unistd.h>

int pipe(int filedes[2]);

filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);

filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])。

程序实例:

复制代码
int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];
   
    if(pipe(fd)  0){                 /* 先创建管道获得一对文件描述符 */
        exit(0);
    }

    if((pid = fork())  0)            /* 父进程把文件描述符复制给子进程 */
        exit(1);
    else if(pid > 0){                /* 父进程写 */
        close(fd[0]);                /* 关闭读描述符 */
        write(fd[1], "\nhello world\n", 14);
    }
    else{                            /* 子进程读 */
        close(fd[1]);                /* 关闭写端 */
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }

    exit(0);
}
复制代码

 

命名管道(named PIPE)

因为基于fork机制,因此管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式链接进程。FIFO又叫作命名管道(named PIPE)。

FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另外一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间创建管道,因此FIFO实际上也由内核管理,不与硬盘打交道。之因此叫FIFO,是由于管道本质上是一个先进先出的队列数据结构,最先放入的数据被最早读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,由于Linux中全部事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道链接也随之消失。FIFO的好处在于咱们能够经过文件的路径来识别管道,从而让没有亲缘关系的进程之间创建链接

函数原型:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *filename, mode_t mode);
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );

其中pathname是被建立的文件名称,mode表示将在该文件上设置的权限位和将被建立的文件类型(在此状况下为S_IFIFO),dev是当建立设备特殊文件时使用的一个值。所以,对于先进先出文件它的值为0。

相关文章
相关标签/搜索