zoukankan      html  css  js  c++  java
  • 进程间的通信—管道

    1、管道的类型

    管道包括三种:

    • 普通管道PIPE: 通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用.
    • 流管道s_pipe: 去除了第一种限制,为半双工,只能在父子或兄弟进程间使用,可以双向传输.
    • 命名管道name_pipe: 去除了第二种限制,可以在许多并不相关的进程之间进行通讯.

    2、无名管道(也称管道)

    管道是如何通信的:

    • 管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。
    • 一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
    • 数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。
    • 管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞,另一进程放入数据。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞,直到另一进程读出数据。
    • 管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
      管道通信

    管道是如何创建的:

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

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

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

    管道的读写:

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

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

    • 内存中有足够的空间可容纳所有要写入的数据;
    • 内存没有被读程序锁定。

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

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

    管道的linux函数原型及例程:

    #include <unistd.h>
    
    int pipe(int pipefd[2]);
    
    pipefd[0]用于读出数据,读取时必须关闭写入端,即close(pipefd[1]);
    pipefd[1]用于写入数据,写入时必须关闭读取端,即close(pipefd[0])。
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    void read_data(int *);  //进程读函数
    void write_data(int *); //进程写函数 
    
    int main(int argc,char *argv[])
    {
    	int pipes[2],rc;
    	pid_t pid;
    		
    	rc = pipe(pipes);	//创建管道                 
    	if(rc == -1){
    		perror("
    pipes
    ");
    		exit(1);
    	}
    		
    	pid = fork();	//创建进程 
    	switch(pid){
    		case -1:
    			perror("
    fork
    ");
    			exit(1);
    		case 0:
    			read_data(pipes);	//相同的pipes
    		default:
    			write_data(pipes);	//相同的pipes
    	}	
    	return 0;
    }
    
    //进程读函数
    void read_data(int pipes[])
    {
    	int c,rc;
    	
    	//由于此函数只负责读,因此将写描述关闭(资源宝贵)
    	close(pipes[1]);
    	
    	//阻塞,等待从管道读取数据, int 转为 unsiged char 输出到终端
    	while( (rc = read(pipes[0],&c,1)) > 0 ){  		
    		putchar(c);       		                       
    	}
    
    	exit(0);
    }
    
    //进程写函数
    void write_data(int pipes[])
    {
    	int c,rc;
    
    	//关闭读描述字
    	close(pipes[0]);                          
    
    	while( (c=getchar()) > 0 ){
    		rc = write( pipes[1], &c, 1);	//写入管道
    		if( rc == -1 ){
    			perror("Parent: write");
    			close(pipes[1]);
    			exit(1);
    		}
    	}
    
    	close(pipes[1]);
    	exit(0);
    }
    

    3、有名管道:

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

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

    有名管道的linux函数原型及例程:

    #include <sys/types.h>
    #include <sys/stat.h>
    
    int mkfifo(const char *pathname, mode_t mode);
    int mknod(const char *pathname, mode_t mode, dev_t dev);
    
    int mkfifo(const char *pathname, mode_t mode);
    参数: 
        pathname :要创建文件的名称
        mode :权限
        
    int mknod(const char *pathname, mode_t mode, dev_t dev);
    参数:
        pathname :要创建文件的名称
        mode :文件类型与权限
        dev :该文件对应设备的设备号,只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。
    例:mknod(FIFO_FILE, S_IFIFO|0666, 0); 
        FIFO_FILE是一个字符指针,指向文件名,
        S_IFIFO表示要创建一个FIFO文件,
        0666表示该文件的权限是所有人可读可写,
        0表示该文件不是一个设备文件。
    
    创建
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    void filecopy(FILE *,char *);
    
    int main(void)
    {
    	FILE *fp1;
    	long int i = 100000;
    	char buf[] = "I want to study Linux!
    ";
    	char *file1 = "data.txt";
    	
    	printf("begin!
    ");
    	
    	if((fp1 = fopen(file1,"a+")) == NULL )
    			printf("can't open %s
    ",file1);
    			
    	while(i--)
    	    filecopy(fp1,buf);
    
    	fclose(fp1);
    	
    	printf("over!
    ");
    	
    	return 0;
    }
    
    void filecopy(FILE *ifp,char *buf)
    {
    	char c;
    	int i,j;
    	j = 0;
    	i = strlen(buf)-1;	
    	while(i--){
    		putc(buf[j],ifp);
    		j++;
    	}
    	putc('
    ',ifp);
    }
    
    写入
    #include <unistd.h>  
    #include <stdlib.h>  
    #include <fcntl.h>  
    #include <limits.h>  
    #include <sys/types.h>  
    #include <sys/stat.h>  
    #include <stdio.h>  
    #include <string.h>  
      
    int main()  
    {  
        const char *fifo_name = "my_fifo";
    	char *file1 = "data.txt";	
        int pipe_fd = -1;  
        int data_fd = -1;  
        int res = 0;  
        const int open_mode = O_WRONLY;  
        int bytes_sent = 0;  
        char buffer[PIPE_BUF + 1];  
      
        if(access(fifo_name, F_OK) == -1)  
        {  
            //管道文件不存在, 创建命名管道
            res = mkfifo(fifo_name, 0777);  
            if(res != 0)  
            {  
                fprintf(stderr, "Could not create fifo %s
    ", fifo_name);  
                exit(EXIT_FAILURE);  
            }  
        }  
      
        printf("Process %d opening FIFO O_WRONLY
    ", getpid());  
        //以只写阻塞方式打开FIFO文件,以只读方式打开数据文件  
        pipe_fd = open(fifo_name, open_mode);  
        data_fd = open(file1, O_RDONLY);  
        printf("Process %d result %d
    ", getpid(), pipe_fd);  
      
        if(pipe_fd != -1)  
        {  
            int bytes_read = 0;  
            //向数据文件读取数据  
            bytes_read = read(data_fd, buffer, PIPE_BUF);  
            buffer[bytes_read] = '';  
            while(bytes_read > 0)  
            {  
                //向FIFO文件写数据  
                res = write(pipe_fd, buffer, bytes_read);  
                if(res == -1)  
                {  
                    fprintf(stderr, "Write error on pipe
    ");  
                    exit(EXIT_FAILURE);  
                }  
                //累加写的字节数,并继续读取数据  
                bytes_sent += res;  
                bytes_read = read(data_fd, buffer, PIPE_BUF);  
                buffer[bytes_read] = '';  
            }  
            close(pipe_fd);  
            close(data_fd);  
        }  
        else  
            exit(EXIT_FAILURE);  
      
        printf("Process %d finished
    ", getpid());  
        exit(EXIT_SUCCESS);  
    } 
    
    读取
    #include <unistd.h>  
    #include <stdlib.h>  
    #include <stdio.h>  
    #include <fcntl.h>  
    #include <sys/types.h>  
    #include <sys/stat.h>  
    #include <limits.h>  
    #include <string.h>  
      
    int main()  
    {  
        const char *fifo_name = "my_fifo";  
        int pipe_fd = -1;  
        int data_fd = -1;  
        int res = 0;  
        int open_mode = O_RDONLY;  
        char buffer[PIPE_BUF + 1];  
        int bytes_read = 0;  
        int bytes_write = 0;  
        //清空缓冲数组  
        memset(buffer, '', sizeof(buffer));  
      
        printf("Process %d opening FIFO O_RDONLY
    ", getpid());  
        //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名  
        pipe_fd = open(fifo_name, open_mode);  
        //以只写方式创建保存数据的文件  
        data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);  
        printf("Process %d result %d
    ",getpid(), pipe_fd);  
      
        if(pipe_fd != -1)  
        {  
            do  
            {  
                //读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中  
                res = read(pipe_fd, buffer, PIPE_BUF);  
                bytes_write = write(data_fd, buffer, res);  
                bytes_read += res;  
            }while(res > 0);  
            close(pipe_fd);  
            close(data_fd);  
        }  
        else  
            exit(EXIT_FAILURE);
      
        printf("Process %d finished, %d bytes read
    ", getpid(), bytes_read);
        exit(EXIT_SUCCESS);
    } 
    
  • 相关阅读:
    Comprehend-Elasticsearch-Demo5
    Mxnet使用TensorRT加速模型--Mxnet官方例子
    Mxnet模型转换ONNX,再用tensorrt执行前向运算
    MxNet模型转换Onnx
    基于Flask-APScheduler实现添加动态定时任务
    Golang习题
    算法题
    Celery使用指南
    flask拓展(数据库操作)
    flask进阶(上下文源管理源码浅析)
  • 原文地址:https://www.cnblogs.com/wanjianjun777/p/10483867.html
Copyright © 2011-2022 走看看