博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux进程IPC浅析[进程间通信概述与管道]
阅读量:3750 次
发布时间:2019-05-22

本文共 14175 字,大约阅读时间需要 47 分钟。

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
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
#include
#include
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
#include
#include
#include
#include
#define BUFFER_SIZE 1024int 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
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#define BUFFER_SIZE 1024int 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:  2017年04月09日 13时06分19秒 *       Revision:  none *       Compiler:  gcc *         Author:   (),  *        Company:   * * =========================================================================== */#include
#include
#include
#include
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
#include
#include
#include
#include
#include
//信号处理函数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
FILE *popen(const char*cmdstring,const char *type);返回值:成功返回文件指针,出错返回NULLint 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
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 1024int 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
#include
int mkfifo(const char *pathname,mode_t mode);返回:若成功则返回0,出错返回-1只要对FIFO有适当访问权限,FIFO可用在任何两个没有任何关系的进程之间通信本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在在文件系统中只有一个索引块存放文件的路径,没有 数据快,所有数据存放在内核中命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞命名mkfifo创建命名管道(命令内部调用mkfifo函数)对FIFO的操作与操作普通文件一样 一旦已经用mkfifp创建了一个FIFO,就可用open打开它 一般的文件I/O(close,read,write,unlink等)都可用于FIFOFIFO相关出错信息: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
#include
#include
#include
#include
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
#include
#include
#include
#include
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下来玩一下

转载地址:http://jzbsn.baihongyu.com/

你可能感兴趣的文章
php(变量)
查看>>
php(单双引号的区别)
查看>>
php(数据类型)
查看>>
php(运算符及流程控制)
查看>>
php(自定义函数与系统自带函数)
查看>>
php(从一个文件中调用另一个文件的变量)
查看>>
php(数组及相关操作)
查看>>
php(about error and time)
查看>>
利用php对数据库进行操作
查看>>
二叉树及其(前中后)序遍历
查看>>
2020.8.29 ssdh
查看>>
python容器
查看>>
python 字符串和切片操作
查看>>
Linux 系统 创建目录等相关操作 以及文件系统层次机构
查看>>
Linux 系统 一、用户相关操作 二、vim相关操作
查看>>
安装Linux 系统,安装虚拟机
查看>>
Linux系统 实操题 添加磁盘、格式化磁盘、挂载、目录、权限等操作
查看>>
python 模块的用法 用户登录界面 加 盐值混淆
查看>>
python 深浅拷贝和赋值
查看>>
C进制转换
查看>>