linux进程间通信(IPC)

1.进程通信概念

进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过内核与其它进程之间的互相通信来协调它们的行为。

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
linux进程间通信(IPC)_第1张图片

2.进程通信的方式

linux进程间通信(IPC)_第2张图片

3.进程通信-pipe

管道分为有名管道和无名管道;

  • 无名管道:是一种半双工的通信方式,数据只能单向流动(即一方只能写,另一方只能读),而且只能在具有亲缘关系的进程间使用,进程的亲缘关系一般指的是父子关系。
  • 有名管道:也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3.1无名管道

无名管道也成为匿名管道,通过调用pipe()函数创建:

#include 
//返回:成功返回0,出错返回-1
int pipe (int fd[2]);    

fd参数返回两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。fd[1]的输出是fd[0]的输入。下图描述了无名管道的通信实现过程:
linux进程间通信(IPC)_第3张图片
代码实现:

#include
#include
#include
#include
int main()
{
   
    int fd[2];
    int ret = pipe(fd); //创建管道
    if(ret == -1) {
   
        perror("pipe error\n");
        return -1;
    }
    //创建子进程,fork调用一次,返回两次,子进程返回0,父进程返回子进程ID
    pid_t id = fork(); 
    if(id == 0) {
    //子进程执行动作
        close(fd[0]); //关闭读
        char* child="I am child!";
        for(int i = 0; i < 5; ++i) {
   
            write(fd[1], child, strlen(child) + 1);
            sleep(2);
        }
    }
    else if(id > 0) {
    //父进程执行动作
        close(fd[1]); //关闭写
        char msg[100];
        for(int j = 0; j < 5; ++j) {
   
            memset(msg,'\0',sizeof(msg));
            ssize_t s = read(fd[0], msg, sizeof(msg));
            if(s > 0) {
   
                msg[s-1] = '\0';
            }
            printf("%s\n",msg);
        }
    }
    else {
   
        perror("fork error\n");
        return -1;
    }
    return 0;
}

管道读取数据的四种情况分析:

读取数据情况 不同情况分析
读端(fd[0])不读且未关闭,写端(fd[1])一直写 管道数据满时,再次调用write()往管道写数据时,将会被堵塞直到数据被读出才返回
写端(fd[1])不写且未关闭,读端(fd[0])一直读 读取完管道剩余数据后,再次调用read()读取管道数据时,将会被堵塞直到有数据可读才返回
读端(fd[0])一直读且未关闭,写端(fd[1])写部分数据后不写了且关闭 读取完管道剩余数据后,再次调用read()读取管道数据时,将会返回0(像读到文件末尾一样),此时不会堵塞。
写端(fd[1])一直写且未关闭,读端(fd[0])读了部分数据不读了且关闭 读端(fd[0])关闭后,再次调用write()往管道写数据时,此时会收到SIGPIPE信号,通常会导致进程异常终止
如果一个管道读端一直在读数据,而管道写端的引⽤计数⼤于0决定管道是否会堵塞,引用计数大于0,只读不写会导致管道堵塞。如果一个管道写端一直在写数据,而管道读端的引用计数大于0决定是否会堵塞,引用计数大于0,只写不读会导致管道堵塞

无名管道特点:

  • 管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
  • 管道只允许单向通信。
  • 管道内部保证同步机制,从而保证访问数据的一致性。
  • 面向字节流,管道容量是64K,即65535字节
  • 管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。

3.2有名管道

有名管道,即FIFO,遵循先进先出原则,通过调用mkfifo()函数创建:

#include 
#include 
/*
*参数:
 - filename 有名管道文件名(包括路径);
 - mode 权限(读写0666)
*成功返回 0 ,失败返回-1 并设置 errno 号
*/
int mkfifo(const char *filename, mode_t  mode)

对有名管道的操作是通过文件IO 中的open read write 等操作的,
进程open打开有名管道以只读方式打开,返回的文件描述符就代表管道的读端,反之为写端。
- O_RDONLY:以只读打开,读端
- O_WRONLY:以只写打开,写端
- O_RDWR:读写方式打开
另外由于普通文件在读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能, 这里的非阻塞标志可以设定为O_NONBLOCK。

读进程 写进程
阻塞打开管道且管道内没有数据,则一直阻塞到有数据 阻塞打开管道,则写操作将一直阻塞到数据可以被写入
非阻塞打开管道,则不论管道内是否有数据,进程都会立即执行,没有数据则返回0 非阻塞打开管道而不能写入全部数据,则读操作进行部分写入或者调用失败
写进程代码实现:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
   
	char buf[50] = {
   0};
	
	//创建有名管道
    if(mkfifo("./fifo",0777) != 0 ) {
    
        if(errno == EEXIST) {
    //已存在,或可在mkfifo前用access函数判断文件是否存在
            printf("File exists\n");
        }
        else {
   
            perror("mkfifo fail ");
            exit(1);
        }
    }
    int fd_fifo, fd_file;
    fd_fifo = open("./fifo",O_WRONLY);//只写方式打开管道,默认阻塞
    if(fd_fifo < 0) {
   
        perror("open fifo fail: ");
        exit(1);
    }
    fd_file = open(argv[1],O_RDONLY);//只读方式打开源文件,进行复制到管道中
    if(fd_file < 0) {
   
        perror("open source fail ");
        exit(1);
    }
    //循环读取文件内容
    ssize_t size;
    while(1) {
   
    	memset(buf, 0, sizeof(buf));
        size = read(fd_file,buf,50); //文件中读取数据,返回读取到多少数据
        if(size <= 0) {
   
            break;
        }
        write(fd_fifo,buf,size);
    }
    close(fd_file);//关闭读的源文件
    close(fd_fifo);
    return 0;
}

读进程代码实现:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
   
    char buf[50] = {
   0};
    //创建有名管道,或可在mkfifo前用access函数判断文件是否存在
    if(mkfifo("./fifo",0777) != 0 ) {
   
        if(errno == EEXIST) {
   //有名管道存在的情况
            printf("File exists\n");
        }
        else {
   
            perror("mkfifo fail ");
            exit(1);
        }
    }
    int fd_fifo,fd_file;  
    fd_fifo = open("./fifo",O_RDONLY);//读方式打开,默认阻塞
    if(fd_fifo < 0) {
   
        perror("open fifo fail: ");
        exit(1);
    }
    //把从有名管道中读取的数据,写到文件中,只读,没有创建,清空打开
    fd_file = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd_file < 0) {
   
        perror("fd_w open fail ");
        exit(1);
    }
    //fifo 中循环读取数据,然后写到文件中
    ssize_t size;
    while(1) {
   
  	  	memset(buf, 0, sizeof(buf));
        size = read(fd_fifo,buf,50); //读有名管道内容,返回读取多少个数据
        if(size <= 0) {
   
            break;
        }
        write(fd_file

你可能感兴趣的:(Linux进程间通信,linux,多进程,c语言,c++)