管道

在linux操作系统中,为了系统的安全性,有用户空间和内核空间之分。每个进程都工作在独立的0~3G用户空间,互不影响,但是有些时候不同进程之间也需要进行数据交互,这就是进程间通信。进程间之所以能通信,是因为系统中所有进程共享了3G到4G的内核空间。进程间通信方式有:管道、信号、共享内存映射(mmap)、本地套接字

管道的实质:使用环形队列机制实现的内核空间的一段缓冲区

管道的分类:

  1. 匿名管道; 在程序中通过pipe函数创建,只能用于有血缘关系的进程间通信,如:父子进程、兄弟进程。
  2. 有名管道:既可以在程序中通过mkfifo函数创建,也可以在命令行执行mkfifo命令创建。有名管道可用于任何关系的进程间通信

管道的特性:

  1. 数据在管道中同一时刻只能单向流动,因此管道是一种半双工的通信方式
  2. 管道中的数据只能读取一次,不可反复读取。

管道的读写行为:

1. 读管道:(1)管道中有数据,则返回实际读取到的字节数;(2)管道中无数据,且有进程把持管道的写端,这时候读管道会阻塞。(3)管道中无数据,且管道的写端没有进程把持,这时候读管道会返回0,相当于读到了文件结尾。
2. 写管道:(1)管道的读端未关闭,且管道未满,则返回实际写入的字节数;(2)管道的读端未关闭,且管道已满,阻塞等待读端把数据读走,再往里写数据。(3)读端被关闭,进程异常终止,收到SIGPIPE信号

这里重点分析匿名管道,上面说了匿名管道在使用前要通过pipe函数创建。

//包含头文件
#include <unistd.h>

//函数原型
int pipe(int pipefd[2]);   
int pipe2(int pipefd[2], int flags);	//通过flag参数设置管道的阻塞与非阻塞

pipe函数作用:创建并打开管道
pipe函数参数:pipefd[2],文件描述符数组,里面包含管道的读端和写端,fd[0]表示读端,fd[1]表示写端
pipe函数返回值:成功返回0,失败返回-1,并设置errno。

示例:

//pipe进程间通信 实现 ls | wc -l 命令效果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>

void sys_err(const char * err_str)
{
    perror(err_str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd[2];
    int ret = -1;
    int pid;
    
    ret = pipe(fd);
    if(ret < 0)
    {
        sys_err("pipe error\r\n");
    }
    
	pid = fork();
    if(pid > 0)   //父进程
    {
        close(fd[1]);                   //关闭子进程写端
        dup2(fd[0], STDOUT_FILENO);     //重定向标准输入到管道的读端
        execLp("wc", "wc", "-l", NULL); //一去不回
    }
    else if(pid == 0) //子进程
    {
        close(fd[0]);                   //关闭父进程读端
        dup2(fd[1], STDOUT_FILENO);     //重定向标准输出到管道的写端
        execlp("ls", "ls", NULL);  
    }
    else
    {
    	sys_err("fork error\r\n");
    }
    
    return 0;
}

顺带再啰嗦一下mkfifo,mkfifo完成进程间通信跟文件的读写操作类似,其实就是一个进程往管道文件中写数据,一个进程从管道文件中读数据,只不过管道中的数据并不像实际磁盘文件那样,数据可以反复读取,管道文件中的数据只能读取一次。