一、进程间通信概念

首先,需要了解一下什么是进程间通信。
进程之间的相互通信的技术,称为进程间通信(InterProcess Communication,IPC)。
下图列出 4 种实现所支持的不同形式的 IPC。
之前进程间交换信息的方法只能是由 fork 或 exec 传送文件。
进程间通信 (IPC)方式有:
(1)管道
(2)消息队列
(3)信号量
(4)共享存储
(5)套接字
其中消息队列、信号量、共享存储统称为 XSI IPC通信方式

下面我们开始一一详细讲解:

二、管道

管道是 UNIX 系统 IPC 的最古老形式。所有 UNIX 系统都提供此种通信机制。管道有以下两种局限性。
(1)历史上,它们是半双工的(即数据只能在一个方向上流动)。现在,某些系统提供全双工管道,但是为了最佳的可移植性,我们决不应预先假定系统支持全双工管道。
(2)管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用 fork 之后,这个管道就能在父进程和子进程之间使用了。
尽管有这两种局限性,半双工管道仍是最常用的 IPC 形式。
其中管道又分为,有名管道 无名管道。

1、无名管道

无名管道是一个与文件系统无关的内核对象,主要用于父子进程之间的通信,需要用专门的系统调用函数创建。
#include <unistd.h>
int pipe(int pipefd[2]);
返回值:若成功,返回 0;若出错,返回 -1.

(1)函数功能

主要用于创建管道文件,利用参数返回两个文件描述符。
其中 pipefd[0] 用于从所创建的无名管道中读取数据,pipefd[1] 用于向该管道写入数据,pipefd[1] 的输出是 pipefd[0] 的输入。

(2)基于无名管道实现进程间通信的编程模型

《1》父进程调用 pipe 函数在系统内核中创建无名管道对象,并通过该函数的输出参数 pipefd,获得分别用于读写该管道的两个文件描述符 pipefd[0] 和 pipefd[1] 
《2》父进程调用 fork 函数,创建子进程。子进程复制父进程的文件描述符表,因此子进程同样持有分别用于读写该管道的两个文件描述符 pipefd[0] 和 pipefd[1]

《3》负责写数据的进程关闭无名管道对象的读端文件描述符 pipefd[0],而负责读数据的进程则关闭管道的写端文件描述符 pipefd[1]
《4》父子进程通过无名管道对象以半双工的方式传输数据。如果需要在父子进程间实现双向通信,较一般化的做法是创建两个管道,一个从父流向子,一个从子流向父
《5》父子进程分别关闭自己所持有的写端或读端文件描述符。在与一个无名管道对象相关联的所有文件描述符都被关闭以后,该无名管道对象即从系统内核中被销毁

(3)示例说明

//示例一
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>int main()
{int pipefd[2];if (pipe (pipefd) == -1){perror ("pipe");exit (EXIT_FAILURE);}pid_t pid;if((pid=fork())<0){perror("fork");}else if(pid==0){printf("这是子进程,pid=%d,",getpid());printf("父进程的pid=%d\n",getppid());if (close (pipefd[1]) == -1){perror ("close");exit (EXIT_FAILURE);}char text[20];ssize_t readed = read (pipefd[0], text, 20);if (readed == -1){perror ("read");exit (EXIT_FAILURE);}printf("%s\n", text);if (close (pipefd[0]) == -1){perror ("close");exit (EXIT_FAILURE);}}else{sleep (1);printf("这是父进程,pid=%d\n",getpid());if (close (pipefd[0]) == -1){perror ("close");exit (EXIT_FAILURE);}ssize_t written = write (pipefd[1], "hello world", 12);if (written == -1){perror ("write");exit (EXIT_FAILURE);}if (close (pipefd[1]) == -1){perror ("close");exit (EXIT_FAILURE);}}return 0;
}
输出结果:
这是子进程,pid=2799,父进程的pid=2798
这是父进程,pid=2798
hello world
//示例二
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>  int main(void){  int result,n;  int fd[2];  pid_t pid;  char line[256];  if(pipe(fd) < 0){  perror("pipe");  return -1;  }  if((pid = fork()) < 0){  perror("fork");  return -1;  }else if(pid > 0){ //parent  close(fd[0]);  if(fd[1] != STDOUT_FILENO){  dup2(fd[1],STDOUT_FILENO);  }  execl("/bin/ls","ls",(char*)0);  }else{ //child  close(fd[1]);  while((n =read(fd[0],line,256)) > 0){  if(write(STDOUT_FILENO,line,n) != n){  perror("write");  exit(-1);  }  }  close(fd[0]);  }  return 0;
}
输出结果:
a.out
test.c
test.c~

(4)示例解析

创建了一个从父进程到子进程的管道,将父进程的读关闭,子进程的写关闭。使得父进程经由该管道想子进程传送数据。管道方向如下:
当管道的一端被关闭后,下列两条规则其作用:
(1)当读(read)一个写端已经被关闭的管道时,在所有数据都被读取后,read 返回 0,表示文件结束。
(2)如果写(write)一个读端已经被关闭的管道,则产生信号 SIGPIPE。如果忽略该信号或者捕获该信号并从其处理程序返回,则 write 返回 -1,errno 设置为 EPIPE。
在写管道(或FIFO)时,常量 PIPE_BUF 规定了内核中管道缓冲区的大小,如果对管道调用 write,而且要求写的字节数小于等于 PIPE_BUF,则此操作不会与其他进程对同一管道(或FIFO)的 write 操作交叉进行。但是,若有多个进程同时写一个管道(或FIFO),而且我们要求写的字节数超过 PIPE_BUF 字节数时,那么我们所写的数据可能会与其他进程所写的数据相互交叉。用 pathconf 或 fpathconf 函数可以确定 PIPE_BUF 的值

(5)函数 popen 和 pclose

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

《1》函数解析

常见的操作是创建一个连接到另一个进程的管道,然后读其输出或向其输入端发送数据,为此,标准 I/O 库提供了两个函数 popen 和 pclose。这两个函数实现的操作是:创建一个管道,fork 一个子进程,关闭未使用的管道端,执行一个 shell 运行命令,然后等待命令终止。

《2》函数使用

函数 popen 先执行 fork,然后调用 exec 执行 cmdstring,并且返回一个标准 I/O 文件指针。如果 type 是“r”,则文件指针连接到 cmdstring 的标准输出。如果 type 是“w”,则文件指针连接到 cmdstring 的标准输入。
pclose 函数关闭标准 I/O 流,等待命令终止,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose 返回的终止状态与 shell 已执行 exit (127) 一样。
cmdstring 由 Bourbe shell 以下列方式执行:
sh -c cmdstring
这表示 shell 将扩展 cmdstring 中的任何特殊字符。例如,可以使用:
fp = popen ("ls *.c", "r");
或者
fp = popen ("cmd 2>$1", "r");

《3》示例说明

#include<stdio.h>  int main(void)
{  char line[256];  FILE* fpin;  int n;  if((fpin = popen("/bin/ls","r")) == NULL){  perror("popen");  return -1;  }  while(fgets(line, 256, fpin) != NULL){  if(fputs(line,stdout) == EOF){  perror("fputs");  return -1;  }  }  if(pclose (fpin) == -1){perror ("pclose");return -1;}return 0;
}
输出结果:
a.out
test.c
test.c~

2、有名管道

(1)有名管道简介

有名管道亦称 FIFO,是一种特殊的文件,它的路径名存在于文件系统中。通过 mkfifo 命令可以创建管道文件

//创建管道文件
# mkfifo myfifo//在文件系统中,管道文件被显示成这样子
# ls -la myfifo
prw-r--r-- 1 root root 0 Jun  3 13:49 myfifo
查看 mkfifo --help
# mkfifo --help
用法:mkfifo [选项]... 名称...
以指定的名称创建先进先出文件(FIFO)。长选项必须使用的参数对于短选项时也是必需使用的。-m, --mode=模式     设置权限模式(类似chmod),而不是rwxrwxrwx 减umask-Z, --context=CTX    将每个创建的目录的SELinux 安全环境设置为CTX--help       显示此帮助信息并退出--version     显示版本信息并退出

可以看到创建管道时是可以添加权限的:

创建管道
# mkfifo -m 0666 myfifo查看管道权限
# ls -la myfifo
prw-rw-rw- 1 root root 0 Jun  3 14:52 myfifo

即使是毫无亲缘关系的进程,也可以通过管道文件通信。

//在一个终端执行:
# echo 'hello,FIFO!' > myfifo //在另一个终端执行:
# cat myfifo
hello,FIFO!

管道文件在磁盘上只有 i 节点没有数据块,也不保存数据。

(2)基于有名管道实现进程间通信的逻辑模型

(3)函数 mkfifo

有名管道不仅可以用于 shell 命令,也可以在代码中使用。
shell编程之前讲过了,参看:UNIX再学习 -- shell编程
基于有名管道实现进程间的通信的编程模型:
其中除了 mkfifo 函数时专门针对有名管道的,其它函数都与操作普通文件没有任何差别。
有名管道是文件系统的一部分,如不删除,将一直存在。

下面介绍一下函数 mkfifo:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
返回值:成功返回 0,失败返回 -1.
《1》参数解析

pathname:文件路径名

mode:权限模式
《2》函数功能
创建有名管道文件
《3》示例说明
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#define MYFIFO "myfifo"  int main(void)
{  char buffer[256];  pid_t pid;  int fd;  unlink(MYFIFO);  if(mkfifo(MYFIFO,0666) < 0){  perror("mkfifo");  return -1;  }  if((pid = fork())<0){  perror("fork");  return -1;  }else if(pid > 0){  char s[] = "hello world!";  fd = open(MYFIFO,O_RDWR);  write(fd,s,sizeof(s));  close(fd);  }else{  fd = open(MYFIFO,O_RDONLY);  read(fd,buffer,256);  printf("%s\n",buffer);  close(fd);  exit(0);  }  waitpid(pid,NULL,0);  return 0;
}
输出结果:
hello world!

4、FIFO用途

FIFO有以下两种用途:
(1)shell 命令使用 FIFO 将数据从一条管道传送到另一条时吗,无需创建中间临时文件。
(2)客户进程-服务器进程应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程二者之间传递数据。

3、有名管道和无名管道区别

讲了这么多,我们来看看两者的区别。
参看:进程中通信的‘无名管道’和‘有名管道’的用法和二者的区别
根据基于无名/有名管道实现进程间通信的逻辑模型我们可以得出:
若管道对象在使用时内核产生,不使用时就不产生时,那么这一定是无名管道;若在使用时内核中产生了一个管道文件,且不使用时还于内核中存在,那么往往是有名管道。
(1)无名管道特点
《1》只能用于具有亲缘关系的进程之间通信(父子进程或者兄弟进程)。
《2》是一个单工(半双工)的通信模式,具有固定的读写端。
《3》每次使用都需要创建管道对象。
(2)有名管道特点
《1》可以在互不相关的进程之间实现通信。
《2》该管道是通过路径名来指出,在文件系统中是可以看到的,在建立管道后可以当做普通文件来使用读写操作。
《3》严格遵循先进先出的规则,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾。且不支持如 lseek()等文件定位操作。
产生的管道文件在磁盘上只有 i 节点没有数据块,不保存数据。
我们来查看一下管道文件类型:
# stat myfifo 文件:"myfifo"大小:0           块:0          IO 块:4096   先进先出
设备:801h/2049d    Inode:2128483     硬链接:1
权限:(0666/prw-rw-rw-)  Uid:(    0/    root)   Gid:(    0/    root)
最近访问:2017-06-03 14:52:53.952811041 +0800
最近更改:2017-06-03 14:52:53.952811041 +0800
最近改动:2017-06-03 14:52:53.952811041 +0800
创建时间:-
值得注意的是:
当使用 open() 来打开 FIFO 文件时,O_NONBLOCK 旗标会有影响
1、当使用O_NONBLOCK 旗标时,打开 FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开 FIFO 文件来读取,则写入的操作会返回 ENXIO 错误代码。 
2、没有使用 O_NONBLOCK 旗标时,打开 FIFO 来读取的操作会等到其他进程打开 FIFO 文件来写入才正常返回。同样地,打开 FIFO 文件来写入的操作会等到其他进程打开 FIFO 文件来读取后才正常返回。
类似于管道,若用 write 写一个尚无进程为读而打开的 FIFO,则产生信号 SIGPIPE。若某个 FIFO 的最后一个写进程关闭了 FIFO,则将为该 FIFO 的读进程产生一个文件结束标志。

4、linux下shell编程之管道

在 Linux 下我们可以采用管道操作符 “|”来连接多个命令或进程,在连接的管道线两边,每个命令执行时都是一个独立的进程。前一个命令的输出正是下一个命令的输入。这些进程可以同时进行,而且随着数据流在它们之间的传递可以自动地进行协调,从而能够完成较为复杂的任务。管道我们也并不陌生,之前讲 xargs 用法时有用到的。
参看:C语言再学习 -- Xargs用法详解
一般形式:[命令1] | [命令2] | [命令3]
实例:
ls 命令查看
# ls
sh.sh  text.txt  可以可以指定查找脚本文件
# ls | grep *sh
sh.sh  

UNIX再学习 -- 进程间通信之管道相关推荐

  1. UNIX再学习 -- 信号

    终于讲到信号部分,很多比较重要的应用程序都需处理信号.第 9 章需要先了解信号机制再看,所以先跳过不讲.现在开始详解信号. 一.信号概念 信号是提供异步事件处理机制的软件中断. 这些异步事件可能来自硬 ...

  2. UNIX再学习 -- 进程关系

    APUE 第 10 章信号讲完,回过头来看一下第 9 章的进程关系.终端登录和网络登录部分,我们只讲 Linux 系统的. 一.终端登录 我记得我们讲 root 登录设置时有提到,参看:C语言再学习 ...

  3. UNIX再学习 -- 标准I/O

    这部分之前有所总结: 参看:C语言再学习 -- 文件 参看:C语言再学习 -- 输入/输出 参看:UNIX再学习 -- 文件描述符 对比:UNIX再学习 -- 文件I/O 一.流 文件I/O中,所有的 ...

  4. UNIX再学习 -- 文件描述符

    在 UNIX/Linux 系统中,一切皆文件,这句话想必都有听过.对于文件的操作几乎适用于所有的设备,这也就看出了文件操作的重要性了.在C语言再学习部分有讲过标准I/O文件操作,参看:C语言再学习 - ...

  5. UNIX再学习 -- shell编程

    UNIX环境高级编程看了三章,遇到不少重定向等shell命令.本想到Linux时再讲,看来有必要提前了.之前有看过一本<嵌入式Linux软硬件开发详解>这本书里有简单介绍了一部分shell ...

  6. UNIX再学习 -- 记录锁

    APUE第 3 章,参看:UNIX再学习 -- 文件I/O  fcntl 函数它的记录锁功能我们当时没讲.接下来就详细说明下. 一.读写冲突 1.如果两个或两个以上的进程同时向一个文件的某个特定的区域 ...

  7. UNIX再学习 -- 守护进程(转)

    参看:守护进程 一.什么是守护进程 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程.它是一个生存期较长的进程,通常独立于控制 ...

  8. UNIX再学习 -- 线程

    终于要讲到线程部分,线程和进程让人够头痛的内容. 一.线程概念 老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境 首先需要了解下,什么是线程. Linux 下的线程,可能 ...

  9. UNIX再学习 -- 函数abort

    abort 函数之前有讲过的,参看:C语言再学习 -- 关键字return和exit ()函数 然后我们在讲 8 中进程终止时,也说过.参看:UNIX再学习 -- exit 和 wait 系列函数 下 ...

最新文章

  1. 中国大学生源质量排行榜150强
  2. bootstrap轮播,播放到最后一张图片的时候,就不正确了。
  3. Python学习笔记-day1(while流程控制)
  4. 【收藏】CMD命令提示符窗口中的快捷键、小技巧和常用命令
  5. ios 请求头设置token_HTTP中的OPTIONS请求
  6. php 电压 异常,tv断线警告是什么原因
  7. (源码)智能优化算法—藤壶交配优化算法(Barnacles Mating Optimizer,BMO)
  8. MACD神器 通达信指标公式 副图 源码 无加密 无未来
  9. 大学c语言程序中if语句,浅谈C语言中if语句
  10. 严蔚敏《数据结构》——二叉树
  11. iOS 9 spotlight搜索 3DTouch
  12. 为什么Java程序员工资高?
  13. oneDNS解决google等登陆问题
  14. 2019迅雷校园招聘!后端工程师岗位两次技术面题目总结和解析
  15. 《SQL基础》04. SQL-DQL
  16. ppt扇形图怎么显示数据_PPT里的扇形图/饼图怎么做才更有创意?
  17. VMware Workstation虚拟机显示屏幕太小问题解决方法
  18. 服务器阵列状态显示verify,VerifyServerName 协议选项 [VERIFY]
  19. 视频标准 - CCIR601,CCIR656
  20. Modbus通讯协议从一窍不通到原来如此

热门文章

  1. bootstrap-datetimepicker bootstrap-datepicker bootstrap-timepicker 时间插件
  2. PYTHON 函数的返回值
  3. 时光已荏苒,我还怎么让你遇见最美年华里的我
  4. 【.NET基础】--委托、事件、线程(2)
  5. Oracle9在Windows7下的安装
  6. Sql Server通用分页存储过程
  7. Pig安装与配置教程
  8. VS.Net 2005 Beta2连接Team Foundation Server的问题
  9. 吴恩达 coursera AI 专项五第一课(上)总结+作业答案
  10. IP地址,子网掩码、默认网关,DNS服务器是什么意思