Linux进程IPC浅析[进程间通信概述与管道]

Linux进程IPC浅析[进程间通信]

  1. 进程间通信概述IPC
  2. 匿名管道pipe
  3. 命名管道fifo
  4. 匿名管道和命名管道之间对比

进程间通信概述IPC

进程间的通讯目的:

  1. 数据传输 :一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间
  2. 共享数据:多个进程想要操作共享数据,一个进程对共享数据修改,其他进程应该立即看到
  3. 通知事件:一个进程需要向另外一个或一组进程发送消息,通知它发生了某种时间
  4. 资源共享:多个进程之间共享同样的资源,为了做到这一点,需要内核提供锁和同步机制
  5. 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够即使知道它的状态改变

Linux进程间通讯(IPC)由以下几个部分发展而来:

  1. 早期UNIX进程间通信
  2. 基于System V进程间通讯
  3. 基于Socket进程间通信和POSIX进程间通信

Unix进程通信方式包括:管道,FIFO,信号

System V进程通信方式包括:System V消息队列,System V信号灯,System V共享资源

POSIX进程间通信包括:posix消息队列,posix信号灯,posix共享内存

现代进程间通信方式:

  1. 管道(pipe)和命名管道(FIFO)
  2. 信号(signal)
  3. 消息队列
  4. 共享内存
  5. 信号量(进程的信号量)
  6. 套接字(socket)

管道通信

概念:

 管道是针对本地计算机的两个进程而设计的一种通信方法
 管道建立后,实际上是获得了两个文件描述符:一个用于读取,另外一个用于写入
 管道是单工的,数据只能流向一个方向,需要双通时,需要建立两个管道
 数据的读入和读出:一个进程向管道的一端写入,被管道另一端的进程读出
 写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从管道缓冲区的头部开始读取

管道分类:(内核中的一块缓存,写入是尾部,读入是头部,是最常见的IPC机制)
1:匿名管道(pipe)

在关系进程中进程(父进程和子进程,兄弟进程之间)
由pipe系统调用,管道由父进程建立
管道位于内核空间,其实是一块缓存

2:命名管道(FIFO)

两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在
通过系统调用mkfifo创建

匿名管道创建:

#include<unistd.h>
int pipe(int fd[2]);
返回:成功返回0,出错返回-1
两个文件描述符数组:
    fd[0]:为pipe的读端
    fd[1]:为pipe的写端
fd[0]用于读取管道,fd[1]用于写入管道

管道的读写特性:
1:通过打开两个管道来创建一个双向的管道
2:管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞
3:当一个进程往管道中不断地写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道放满数据的则会报错
4:不完整管道(有一端被关闭掉)
当读一个写端已被关闭的管道的时候,在所有数据被读取后,read返回0,以表示到达了文件尾部
如果写一个读端被关闭的管道,则产生信号SIGPIPE,如果忽略该西i你好或捕捉该信号从处理程序返回,则write返回-1,同时ermo设置为EPIPE

在网络编程的过程中,有时候会用到类似于不完整管道这样的过程(服务器挂掉),这个时候需要去检测.

1:管道创建:

    /*
 * ===========================================================================
 *
 *       Filename:  pipe_create.c
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年04月05日 21时49分26秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>


int main(int argc,char* argv[]){
  int pipe_fd[2];
  if(pipe(pipe_fd) == -1){
    perror("create pipe error");
  }else{
    printf("create pipe success");
  }

  close(pipe_fd[0]);
  close(pipe_fd[1]);
  return 0;
}

管道的读写:
管道一般由父进程创建,然后再fork一个子进程(子进程会copy父进程的内存空间,所以其会将父进程中的管道信息copy到子进程中去)

2:怎么利用pipe进行单向通信

/*
 * ===========================================================================
 *
 *       Filename:  pipe_create2.c
 *    Description:
 *    父子进程之间通过管道来进行通信,父亲进程来读取子进程写到文件中的数据 
 *        Version:  1.0
 *        Created:  2017年04月07日 21时01分54秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>
#include<unistd.h>


#define BUFFER_SIZE 1024
int main(int argc,char*argv[]){
  //fd[0]对应的是读端,fd[1]对应的是写端
  int pipe_fd[2];
  pid_t pid;
  if(pipe(pipe_fd)== -1){
    perror("create pipe error");
  }else{
    perror("create pipe success");
  }
  pid = fork();
  if(pid < 0){
    perror("fork error");
  }else if(pid>0){
  //父进程的执行时间片,父进程作为读的调用读的端口
    close(pipe_fd[1]);
    char buffer[BUFFER_SIZE];
    memset(buffer,0,sizeof(buffer));
    while(read(pipe_fd[0],buffer,sizeof(buffer)) != 0){
        printf("read content:%s\n",buffer);
   }


  }else if(pid == 0){
  //子进程执行的时间片,调用写的端口
    close(pipe_fd[0]);

    char content [] = "helloworld";
    write(pipe_fd[1],content,sizeof(content));

  }
  wait(0);
  close(pipe_fd[0]);
  close(pipe_fd[1]);
  return 0;
}

3:利用管道来进行命令执行和输出筛选功能

/*
 * ===========================================================================
 *
 *       Filename:  pipe_create3.c
 *    Description:
 *    两个子进程之间通过管道去进行通信.这个时候第一个子进程负责去写,第二个子进程负责去读取 ,然后进行晒选
 *        Version:  1.0
 *        Created:  2017年04月07日 22时40分31秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>


char *cmd1[3] = {"/bin/cat","/etc/passwd",NULL};
char *cmd2[3] = {"/bin/grep","root",NULL};

int main(int argc,char * argv[]){
  int fd[2];
  pid_t pid;
  if(pipe(fd) != 0){
    perror("pipe create error");
  }
  int i = 0;
  //进程扇子,进程扇是子进程创建完毕执行完毕之后,直接退出;父亲进程需要等待两个子进程结束
  for(i = 0;i<2 ; i++){
    pid = fork();
    if(pid < 0){
      perror("pid fork error");
      exit(EXIT_FAILURE);
    }else if(pid > 0 ){
      //父进程执行的,父进程在子进程创建完毕之后需要wait等待回收
      if(i == 1){
        close(fd[0]);
        close(fd[1]);
        wait(0);
        wait(0);
      } 
    }else {
      //子进程执行的时间片,
      if(i == 0){
        //第一个子进程负责去写数据,关闭读端fd[0]
        close(fd[0]);
        //将标准输出重新定位到写端,作为数据输入
        if(dup2(fd[1],STDOUT_FILENO) != STDOUT_FILENO){
          perror("dup2 error");
        }
        //因为复制一份给了STDOUT了,所以直接进行
        close(fd[1]);
        if(execvp(cmd1[0],cmd1) != 0){
          perror("exec error");
        }
        break;
      }else if(i == 1){
        //第二个子进程负责去读取数据,关闭写端fd[1];
        close(fd[1]);
        //将标准输入定位为管道文件的读端
        if(dup2(fd[0],STDIN_FILENO) != STDIN_FILENO){
           perror("dup2 error");
        }
        close(fd[0]);
        if(execvp(cmd2[0],cmd2) != 0){
          perror("exec error");
        }
        break;
      }
    }
  }
  return 0;
}

4:管道的双工通信:利用两个管道在进程间进行通信

/*
 * ===========================================================================
 *
 *       Filename:  pipe_create2.c
 *    Description:
 *    父子进程之间通过两个管道来进行通信,父亲进程来读取子进程写到文件中的数据
 *    ,当父进程读取到子进程的相关数据之后,再去给子进程写一些数据
 *    注意:
 *        管道是阻塞方式的,所以管道文件时没有结束符号的,在读取内容的时候,如果想终端,必须关闭掉管道文件
 *        Version:  1.0
 *        Created:  2017年04月07日 21时01分54秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>
#include<unistd.h>


#define BUFFER_SIZE 1024
int main(int argc,char*argv[]){
  //fd[0]对应的是读端,fd[1]对应的是写端
  int pipe_fd[2];
  int pipe_fd2[2];
  pid_t pid;
  //创建第一个管道
  if(pipe(pipe_fd) != 0){
    perror("create pipe error");
  }else{
    perror("create pipe success");
  }
  //创建第二个管道
  if(pipe(pipe_fd2) != 0){
    perror("create pipe error");
  }else{
    perror("create pipe success");
  }
  pid = fork();
  if(pid < 0){
    perror("fork error");
  }else if(pid>0){
  //父进程的执行时间片,父进关闭第一个管道写的端口
    close(pipe_fd[1]);
    //父亲进程关闭第二个管道的读端
    close(pipe_fd2[0]);

    char buffer[BUFFER_SIZE];
    memset(buffer,0,sizeof(buffer));
    ssize_t size_in;
    while((size_in = read(pipe_fd[0],buffer,sizeof(buffer))) != 0){
        if(size_in > 0){
          printf("read content:%s\n",buffer);
          char content2[] = "nihao";
          //将文件写入到第二个管道中去
          write(pipe_fd2[1],content2,sizeof(content2));
          //因为管道文件是阻塞方式的,所以管道文件是没有结束符号的,只能通过close来关闭
          close(pipe_fd2[1]);
        }
    }

  }else if(pid == 0){
  //子进程执行的时间片,调用写的端口,关闭第一个管道的读端
    close(pipe_fd[0]);
    //关闭第二个管道的写端,调用第二个管道的读端口
    close(pipe_fd2[1]);
    char content [] = "helloworld\n";
    write(pipe_fd[1],content,sizeof(content));
    char buffer[BUFFER_SIZE];
    ssize_t size_out;
    while((size_out = read(pipe_fd2[0],buffer,BUFFER_SIZE)) != 0){
      if(size_out > 0){
         printf("content2:%s\n",buffer);
         write(pipe_fd[1],content,sizeof(content));
         close(pipe_fd[1]);
      }else{
        perror("read error\n");
        exit(EXIT_FAILURE);
      }
    }

  }
  wait(0);
  close(pipe_fd[0]);
  close(pipe_fd[1]);
  return 0;
}

5:不完整管道的读写1:

/*
 * ===========================================================================
 *
 *       Filename:  broken_pipe.c
 *    Description:  不完整管道,读取一个写端已经关闭的管道文件,当read返回0的时候
 *    说明已经到达了管道的尾部,父进程去读取.
 *        Version:  1.0
 *        Created:  201704月09日 130619秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<memory.h>

int main(int argc,char * argv[]){
  int pipe_fd[2];
  pid_t pid;
  if(pipe(pipe_fd) < 0){
    perror("create pipe error");
    exit(1);
  }
  pid = fork();
  if(pid < 0){
    perror("fork error");
    exit(1);
  }else if(pid == 0){
    //子进程执行时间片,先关闭读端,然后写一组字符串进去,子进程在写完之后关闭管道写端
    close(pipe_fd[0]);
    char content[] = "123456";
    write(pipe_fd[1],content,sizeof(content));
    close(pipe_fd[1]);
  }else {
    //父进程执行的时间片
    close(pipe_fd[1]);
    sleep(5);
    while(1){
      char c;
      //当写端管道被关闭后,读取出来的值为0
      if(read(pipe_fd[0],&c,1)==0){
        printf("\n read end\n");
        break;
      }else{
        printf("%c",c);
      }
    }
    close(pipe_fd[0]);
    wait(0);
  }
  return 0;
}

6:不完整管道的读写2

/*
 * ===========================================================================
 *
 *       Filename:  broken_pipe2.c
 *    Description:
 *    不完整管道,当写入一个读端被关闭的管道的时候,这个时候会产生一个
 *    SIGPIPE的信号,
 *    如果忽略或者捕获该信号从处理程序返回,则write返回-1,同时errno也会变为-1

 *        Version:  1.0
 *        Created:  2017年04月09日 13时33分29秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<string.h>
//信号处理函数
void sig_handler(int signo){
  if(signo == SIGPIPE){
    printf("SIGPIPE Occured\n");
  }
}

int main(int argc,char * argv[]){

  int pipe_fd[2];
  pid_t pid;
  if(pipe(pipe_fd) < 0){
    perror("create pipe error");
  }
  pid = fork();
  if(pid < 0 ){
    perror("fork pid error");
    exit(EXIT_FAILURE);
  }else if(pid > 0){
    //父亲进程去写
   close(pipe_fd[0]);
   //延时睡眠.确保管道的读端已经关闭了
   sleep(3);
   //注册信号捕获函数
   if(signal(SIGPIPE,sig_handler) == SIG_ERR){
      printf("signal sigpipe error");
      exit(EXIT_FAILURE);
   }
  //往读端的已经关闭的管道中写入数据
  char content[] = "helloworld";
  if(write(pipe_fd[1],content,sizeof(content)) != sizeof(content)){
     fprintf(stderr,"%s,%s\n",strerror(errno),(errno == EPIPE)?"EPIPE":",unknow");
  }
  close(pipe_fd[1]);
   wait(0);
  }else {
    //子进程去关闭管道两端
    close(pipe_fd[0]);
    close(pipe_fd[1]);

  }

  return 0;
}

标准库中的管道操作:

#include<stdio.h>
FILE *popen(const char*cmdstring,const char *type);
返回值:成功返回文件指针,出错返回NULL
int pclose(FILE *fp);
返回值:cmdtsring的终止状态,出错返回-1

使用popen()创建的管道必须使用pclose()关闭,其实,popen/pclose和标准文件输入和输出流中的fopen/fclose相似

Popen内部原理:
当 type 为r的时候,子进程负责将命令执行的结果写入管道中去(将标准输出重定向到管道的写段).父进程从管道中读取命令执行的结果,并且将其防止到FILE*类型的文件指向的结构体缓存中去
当type为w的时候,子进程也是需要去执行命令,但是子进程需要从管道中读取数据作为命令执行的输入.将标准输入重定向到管道的读端,父进程将结构体缓存中的数据写入管道
在标准库下面的管道:

/*
 * ===========================================================================
 *
 *       Filename:  pipe_f.c
 *    Description:  标准库函数中的管道相关操作
 *        Version:  1.0
 *        Created:  2017年04月09日 14时02分44秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<memory.h>
#include<malloc.h>

#define BUFFER_SIZE 1024
int main(int argc,char * argv[]){
  //通过popen打开管道,成功返回文件指针,失败返回NULL
  FILE * fp = popen("cat /etc/passwd","r");
  if(fp == NULL){
    printf("file open error\n");
    exit(EXIT_FAILURE);
  }
  char *buffer = (char*)malloc(BUFFER_SIZE*sizeof(char));
  memset(buffer,0,BUFFER_SIZE*sizeof(char));

  //从文件指针中去读取
  while(fgets(buffer,BUFFER_SIZE*sizeof(char)-1,fp)!=NULL){
    printf("%s",buffer);
  }
  free(buffer);
  pclose(fp);
  printf("-------------------------\n");
  //将type修改成w

  FILE* fp_w = popen("wc -l","w");

  fprintf(fp_w,"1\n2\n3\n");
  pclose(fp_w);

  return 0;
}

命名管道fifo

命名管道;
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
返回:若成功则返回0,出错返回-1

只要对FIFO有适当访问权限,FIFO可用在任何两个没有任何关系的进程之间通信
本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在
在文件系统中只有一个索引块存放文件的路径,没有 数据快,所有数据存放在内核中
命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞
命名mkfifo创建命名管道(命令内部调用mkfifo函数)

对FIFO的操作与操作普通文件一样
    一旦已经用mkfifp创建了一个FIFO,就可用open打开它
    一般的文件I/O(close,read,write,unlink等)都可用于FIFO
FIFO相关出错信息:
EACCES(无存取权限)
EEXIST(制定文件不存在)
ENAMETOOLLONG(路径名太长)
ENOENT(包含的目录不存在)
ENOSOC(文件系统剩余空间不足)
ENOTDIR(文件路径无效)
EROFS(指定的文件存在于只读文件系统中)

fifo管道的读端

/*
 * ===========================================================================
 *
 *       Filename:  fifo_read.c
 *    Description:  从fifo管道中去读取
 *        Version:  1.0
 *        Created:  2017年04月09日 14时38分07秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<memory.h>
#include<fcntl.h>

int main(int argc, char* argv[]){
  if(argc < 2){
    printf("缺少参数");
    exit(EXIT_FAILURE);
  }
  int fd = open(argv[1],O_RDONLY);
  if(fd < 0){
    perror("open file error\n");
    exit(EXIT_FAILURE);
  }else{
    printf("open file success\n");
  }
  char buffer[512];
  memset(buffer,0,sizeof(buffer));
  while(read(fd,buffer,sizeof(buffer)) < 0){
    perror("read error");
  }
  printf("%s\n",buffer);  

  return 0;
}

fifo管道的写端

/*
 * ===========================================================================
 *
 *       Filename:  fifo_write.c
 *    Description:  将数据写入到fifo文件中去
 *        Version:  1.0
 *        Created:  2017年04月09日 14时38分48秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<memory.h>
#include<fcntl.h>

int main(int argc, char* argv[]){

  if(argc < 2){
    printf("缺少参数\n");
    exit(EXIT_FAILURE);
  }
  int fd = open(argv[1],O_WRONLY);
  if(fd < 0){
    perror("open file error\n");
    exit(EXIT_FAILURE);
  }else{
    printf("open file success\n");
  }
  char content[] = "hellowolrd";

  while(write(fd,content,sizeof(content))!=sizeof(content)){
    printf("write error\n");
  }


  close(fd);

  return 0;
}

匿名管道和命名管道之间对比

相同点:

默认都是阻塞方式的读写

都适用于socket的网络通信

阻塞不完整管道(有一端关闭)
    单纯读的时候,在所有数据被读取后,read返回0,以表示达到了文件尾部
    单纯写的时候,则产生sigpipe,如果忽略或捕捉该信号,并从处理程序返回,则write返回-1,同事errno设置为EPIPE

阻塞完整通道(两端都开启)
    单纯读时,要么阻塞,要么读取到数据
    单纯写时,写到管道满的时候报错
非阻塞不完整管道(有一端关闭)
    单纯读时直接报错
    单纯写的时候,则产生sigpipe,如果忽略或捕捉该信号,并从处理程序返回,则write返回-1,同事errno设置为EPIPE
非阻塞完整管道(两端都开启)
    单纯读时直接报错
    单纯写的时候,写到管道满的时候会出错

不同点:

 打开方式不一致
 pipe通过fcntl系统调用来设置O_NOBLOCK来设置非阻塞性读写
 FIFO通过fcntl系统调用或者open函数来设置非阻塞性读写

以上就是一些关于进程IPC通信相关管道部分的总结.代码都是可以直接进行run的,有兴趣的copy下来玩一下

欢迎持续访问博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值