管道

管道是Unix系统IPC的最古老方式,有两种局限性:

(1)   历史上它们是半双工的(即数据只能在一个方向上流动),虽然现在某些系统提供了全双工管道,但是为了可移植性,不要抱有绝对的全双工假设。

(2)   管道只能在具有公共祖先的两个进程之间使用(一般都是用于父子进程之间)。

管道是通过调用pipe函数创建的:

#include <unistd.h>

int pipe(int fd[2]);

返回值:成功,返回0;失败,返回-1

说明:

fd返回两个文件描述符:fd[0]用于读,fd[1]用于写,fd[1]的输出刚好是fd[0]的输入。

即shell为每一条命令单独创建一个进程,然后管道将前一条命令的标准输出与后一条命令的标准输入相连接。

注:

POSIX.1允许实现支持全双工管道,对于这些实现,fd[0]和fd[1]以读/写方式打开。

如下给出了两种描绘半双工管道的方法,左图中管道的两端在一个进程中相互连接,右图中则强调数据需要通过内核在管道中流动:

管道通常在单个进程中没有太大用处,下图显示了父子进程之间的管道:进程先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC管道

fork之后具体要做什么取决于我们想要的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程关闭管道的写端(fd[1]):

当管道的一端被关闭后,下列两条规则起作用:

(1)   当read一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。

(2)   当write一个读端已被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。

如下为管道程序实例:

实例一:创建一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据:

 [root@benxintuzi ipc]# cat pipe1.c
#include <unistd.h>
#include <stdio.h> #define MAXLINE 1024 int main(void)
{
int n;
int fd[];
pid_t pid;
char line[MAXLINE]; if (pipe(fd) < )
printf("pipe error\n");
if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid > ) { /* parent */
close(fd[]);
write(fd[], "hello world\n", ); /* write data to fd[1] */
} else { /* child */
close(fd[]);
n = read(fd[], line, MAXLINE); /* read data from fd[0] */
write(STDOUT_FILENO, line, n); /* write data to standard output */
} return ();
} [root@benxintuzi ipc]# ./pipe1
[root@benxintuzi ipc]# hello world [root@benxintuzi ipc]#

实例二:编写一个程序,其功能是每次一页地显示已产生的输出。为了避免先将所有数据写到一个临时文件中,然后再调用系统中有关程序显示该文件,我们希望通过管道将输出直接送到分页程序。为此,先创建一个管道,fork一个子进程,使子进程的标准输入成为管道的读端,然后调用exec,执行分页程序:(说明点:1. 在调用fork之前,先创建一个管道。调用fork之后,父进程关闭读端,子进程关闭写端,然后子进程调用dup2,使其标准输入成为管道的读端。当执行分页程序时,其标准输入就是管道的读端了; 2. 我们使用环境变量PAGER来获得用户分页程序名,如果没有成功,则使用系统默认值,这是环境变量的常见用法。)

 [root@benxintuzi ipc]# cat pipe2.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h> #define DEF_PAGER "/bin/more" /* default pager program */
#define MAXLINE 1024 int main(int argc, char *argv[])
{
int n;
int fd[];
pid_t pid;
char *pager, *argv0;
char line[MAXLINE];
FILE *fp; if (argc != )
{
printf("usage: a.out <pathname>\n");
return (-);
} if ((fp = fopen(argv[], "r")) == NULL)
printf("can't open %s\n", argv[]);
if (pipe(fd) < )
printf("pipe error\n"); if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid > ) { /* parent */
close(fd[]); /* close read end */ /* parent copies argv[1] to pipe */
while (fgets(line, MAXLINE, fp) != NULL) {
n = strlen(line);
if (write(fd[], line, n) != n)
printf("write error to pipe\n");
}
if (ferror(fp))
printf("fgets error\n"); close(fd[]); /* close write end of pipe for reader */ if (waitpid(pid, NULL, ) < )
printf("waitpid error\n");
return ();
} else { /* child */
close(fd[]); /* close write end */
if (fd[] != STDIN_FILENO) {
if (dup2(fd[], STDIN_FILENO) != STDIN_FILENO)
printf("dup2 error to stdin\n");
close(fd[]); /* don't need this after dup2 */
} /* get arguments for execl() */
if ((pager = getenv("PAGER")) == NULL)
pager = DEF_PAGER;
if ((argv0 = strrchr(pager, '/')) != NULL)
argv0++; /* step past rightmost slash */
else
argv0 = pager; /* no slash in pager */ if (execl(pager, argv0, (char *)) < )
printf("execl error for %s\n", pager);
}
return ();
}
[root@benxintuzi ipc]# ./pipe2 pipe2.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h> #define DEF_PAGER "/bin/more" /* default pager program */
#define MAXLINE 1024 int main(int argc, char *argv[])
{
int n;
int fd[];
pid_t pid;
char *pager, *argv0;
char line[MAXLINE];
FILE *fp; if (argc != )
{
printf("usage: a.out <pathname>\n");
return (-);
} if ((fp = fopen(argv[], "r")) == NULL)
printf("can't open %s\n", argv[]);
if (pipe(fd) < )
printf("pipe error\n"); if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid > ) { /* parent */
close(fd[]); /* close read end */ /* parent copies argv[1] to pipe */
while (fgets(line, MAXLINE, fp) != NULL) {
--More--

实例三:父子进程同步函数的管道实现:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT、WAIT_CHILD:(说明点:父进程在调用TELL_CHILD时,经由上一个管道写一个字符“p”,子进程在调用TELL_PARENT时,经由下一个管道写一个字符“c”,相应的WAIT_XXX函数调用read读一个字符,没有读到字符时则阻塞)

 static int    pfd1[], pfd2[];

 void TELL_WAIT(void)
{
if (pipe(pfd1) < || pipe(pfd2) < )
printf("pipe error\n");
} void TELL_PARENT(pid_t pid)
{
if (write(pfd2[], "c", ) != )
printf("write error\n");
} void WAIT_PARENT(void)
{
char c; if (read(pfd1[], &c, ) != )
printf("read error\n"); if (c != 'p')
{
printf("WAIT_PARENT: incorrect data\n");
return ;
} } void TELL_CHILD(pid_t pid)
{
if (write(pfd1[], "p", ) != )
printf("write error\n");
} void WAIT_CHILD(void)
{
char c; if (read(pfd2[], &c, ) != )
printf("read error\n"); if (c != 'c')
{
printf("WAIT_CHILD: incorrect data\n");
return ;
}
}

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

#include <stdio.h>

FILE* popen(const char* cmdstring, const char* type);

返回值:成功,返回文件指针;失败,返回NULL

int pclose(FILE* fp);

返回值:成功,返回cmdstring的终止状态;失败,返回-1

说明:

函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。如果type是r,则文件指针连接到cmdstring的标准输出;如果type是w,则文件指针连接到cmdstring的标准输入,见下图:

pclose函数关闭标准I/O流,等待命令终止,然后返回shell的终止状态。

cmdstring由Bourne shell以下列方式执行:sh –c cmdstring

shell命令${PAGER:-more}的意思是:如果shell变量PAGER已经定义,且其值非空,则使用其值,否则使用字符串more。利用popen函数重写实例二

 [root@benxintuzi ipc]# cat pipe3.c
#include <stdio.h>
#include <sys/wait.h> #define MAXLINE 1024
#define PAGER "${PAGER:-more}" /* environment variable, or default */ int main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout; if (argc != )
{
printf("usage: a.out <pathname>\n");
return (-);
} if ((fpin = fopen(argv[], "r")) == NULL)
printf("can't open %s\n", argv[]); if ((fpout = popen(PAGER, "w")) == NULL)
printf("popen error\n"); /* copy argv[1] to pager */
while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF)
printf("fputs error to pipe\n");
}
if (ferror(fpin))
printf("fgets error\n");
if (pclose(fpout) == -)
printf("pclose error\n"); return ();
}
[root@benxintuzi ipc]# ./pipe3 pipe2.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h> #define DEF_PAGER "/bin/more" /* default pager program */
#define MAXLINE 1024 int main(int argc, char *argv[])
{
int n;
int fd[];
pid_t pid;
char *pager, *argv0;
char line[MAXLINE];
FILE *fp; if (argc != )
{
printf("usage: a.out <pathname>\n");
return (-);
} if ((fp = fopen(argv[], "r")) == NULL)
printf("can't open %s\n", argv[]);
if (pipe(fd) < )
printf("pipe error\n"); if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid > ) { /* parent */
close(fd[]); /* close read end */ /* parent copies argv[1] to pipe */
while (fgets(line, MAXLINE, fp) != NULL) {
--More--

协同进程:

当一个进程既要产生某个程序的输入,又读取该程序的输出时,它就变成了协同进程(coprocess)。协同进程通常在shell后台运行,其标准输入和标准输出通过管道连接到另一个程序。popen只提供连接到另一个进程的标准输入或标准输出的一个单向管道,而协同进程则有连接到另一个进程的两个单向管道:一个连接到其标准输入,另一个则来自其标准输出。

实例:从标准输入读取两个数,计算它们的和,然后将和写至其标准输出。

 [root@benxintuzi ipc]# cat coprocess.c
#include <unistd.h>
#include <stdio.h> #define MAXLINE 1024 int main(void)
{
int n, int1, int2;
char line[MAXLINE]; while ((n = read(STDIN_FILENO, line, MAXLINE)) > ) {
line[n] = ; /* null terminate */
if (sscanf(line, "%d%d", &int1, &int2) == ) {
sprintf(line, "%d\n", int1 + int2);
n = strlen(line);
if (write(STDOUT_FILENO, line, n) != n)
printf("write error\n");
} else {
if (write(STDOUT_FILENO, "invalid args\n", ) != )
printf("write error\n");
}
} return ();
} [root@benxintuzi ipc]# ./coprocess ^C
[root@benxintuzi ipc]#

实例:将上述程序编译成为add2协同进程,然后下列程序创建了两个管道,父进程、子进程各自关闭了它们不需要的管道端,必须使用两个管道:一个用作协同进程的标准输入,另一个用作它的标准输出。然后子进程调用dup2使管道描述符移至其标准输入和标准输出,最后调用了excel执行add2:

 [root@benxintuzi ipc]# gcc coprocess.c -o add2
[root@benxintuzi ipc]# cat coprocess2.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h> #define MAXLINE 1024 static void sig_pipe(int); /* our signal handler */ int main(void)
{
int n, fd1[], fd2[];
pid_t pid;
char line[MAXLINE]; if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
printf("signal error\n"); if (pipe(fd1) < || pipe(fd2) < )
printf("pipe error\n"); if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid > ) { /* parent */
close(fd1[]);
close(fd2[]); while (fgets(line, MAXLINE, stdin) != NULL) {
n = strlen(line);
if (write(fd1[], line, n) != n)
printf("write error to pipe\n");
if ((n = read(fd2[], line, MAXLINE)) < )
printf("read error from pipe\n");
if (n == ) {
printf("child closed pipe\n");
break;
}
line[n] = ; /* null terminate */
if (fputs(line, stdout) == EOF)
printf("fputs error\n");
} if (ferror(stdin))
printf("fgets error on stdin\n");
exit();
} else { /* child */
close(fd1[]);
close(fd2[]);
if (fd1[] != STDIN_FILENO) {
if (dup2(fd1[], STDIN_FILENO) != STDIN_FILENO)
printf("dup2 error to stdin\n");
close(fd1[]);
} if (fd2[] != STDOUT_FILENO) {
if (dup2(fd2[], STDOUT_FILENO) != STDOUT_FILENO)
printf("dup2 error to stdout\n");
close(fd2[]);
}
if (execl("./add2", "add2", (char *)) < )
printf("execl error\n");
}
exit();
} static void sig_pipe(int signo)
{
printf("SIGPIPE caught\n");
exit();
} [root@benxintuzi ipc]# ./coprocess2

FIFO

FIFO有时被称为命名管道,未命名的管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的祖先进程。但是,通过FIFO,不相关的进程之间也能交换数据。

使用如下函数创建FIFO:

#include <sys/stat.h>

int mkfifo(const char* path, mode_t mode);

int mkfifoat(int fd, const char* path, mode_t mode);

返回值:成功,返回0;失败,返回-1

说明:

mkfifoat与mkfifo相似,像之前其他*at系列函数一样,有3种情形:

(1)   如果path参数指定了绝对路径名,则fd被忽略,此时mkfifoat和mkfifo一样。

(2)   如果path参数指定了相对路径名,则fd参数是一个打开目录的有效文件描述符,路径名和目录有关。

(3)   如果path参数指定了相对路径名,并且fd参数指定了AT_FDCWD,则路径名以当前目录开始,mkfifoat和mkfifo类似。

当我们使用mkfifo或者mkfifoat函数创建FIFO时,要用open打开,确是,正常的I/O函数(如close、read、write、unlink)都需要FIFO。当open一个FIFO时,非阻塞标志(O_NONBLOCK)会产生如下影响:

(1)   没有指定O_NONBLOCK时,只读open要阻塞到某个其他进程为写而打开这个FIFO为止。类似地,只写open要阻塞到某个其他进程为读而打开这个FIFO为止。

(2)   如果指定了P_NONBLOCK,则只读open立即返回。但是,如果没有进程为读而打开这个FIFO,那么只写open将返回-1,并将errno设置为ENXIO。

一个给定的FIFO有多个写进程是很常见的,这就意味着,如果不希望多个进程所写的数据交叉,则必须考虑原子写操作。和管道一样,常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量。

FIFO主要有以下两种用途:

(1)   shell命令使用FIFO将数据从一条管道传送到另一条管道,无需创建中间临时文件。

实例:考虑这样一个过程,他需要对一个输入文件进行两次处理,示意图如下:

我们可以使用FIFO和tee命令如下处理:

mkfifo fifo1

prog3 < fifo1 &

prog1 < (输入文件) | tee fifo1 | prog2

执行流程如下:

(2)   客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务器进程之间传递数据。

实例:有一个服务器进程,它与很多客户进程相关,每个客户进程都可将请求写到一个该服务器进程创建的FIFO中。由于该FIFO有多个写进程,因此客户进程每次发送给服务器的数据长度要小于PIPE_BUF字节,这样就能避免客户进程之间的写交叉。

但是这种类型的FIFO设计有问题,服务器如何回应各个客户进程呢?一种解决方法是,每个客户进程都在其请求中包含它的进程ID,然后服务器进程为每个客户进程创建一个FIFO,所使用的路径名是以客户进程的进程ID为基础的。例如,服务进程可以用名字/tmp/serv1.XXXXX创建FIFO,其中XXXXX被替换成客户进程的进程ID,如下图所示:

Linux 进程间通信(一)(经典IPC:管道、FIFO)的更多相关文章

  1. 进程间通信系列 之 命名管道FIFO及其应用实例

    进程间通信系列 之 概述与对比   http://blog.csdn.net/younger_china/article/details/15808685  进程间通信系列 之 共享内存及其实例   ...

  2. linux 进程间通信机制(IPC机制)- 管道

    一,定义: 管道又可以分为无名管道和命名管道,两者的用途是不一样的. 无名管道PIPE:主要用于具有亲缘关系的进程之间的通信,无名管道的通信是单向的,只能由一段到另外一段:无名管道是临时性的,完成通信 ...

  3. Linux进程间通信方式--信号,管道,消息队列,信号量,共享内存

    1.概述 通信方法 无法介于内核态与用户态的原因 管道(不包括命名管道) 局限于父子进程间的通信. 消息队列 在硬.软中断中无法无阻塞地接收数据. 信号量 无法介于内核态和用户态使用. 内存共享 需要 ...

  4. Linux学习笔记25——命名管道(FIFO)

    1 命名管道(FIFO) 管道应用的一个重大缺陷就是没有名字,因此只能用于亲缘进程之间的通信.后来从管道为基础提出命名管道(named pipe,FIFO)的概念,该限制得到了克服.FIFO不同于管道 ...

  5. linux 进程间通信机制(IPC机制)一总览

    1.作用:进程间通信机制(Inter Process Communication,IPC),这些IPC机制的存在使UNIX在进程通信领域手段相当丰富,也使得程序员在开发一个由多个进程协作的任务组成的系 ...

  6. linux进程间通信之一:无名管道

    无名管道是linux中管道通信的一种原始方法,有以下特征: 1.单工通信模式,具有固定的读端和写端: 2.管道可以看成是一种特殊的文件,对于它的读写可以使用普通的read(),write()等文件IO ...

  7. linux 进程间通信机制(IPC机制)一消息队列

    消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法.每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构.我们可以通过发送消息来避免命名管道的同步和阻塞问题.但是消息 ...

  8. Linux进程间通信---管道和有名管道

    一.管道 管道:管道是一种半双工的通信方式,数据只能单方向流动,而且只能在具有亲缘关系的进程间使用,因为管道 传递数据的单向性,管道又称为半双工管道.进程的亲缘关系通常是指父子进程关系. 管道的特点决 ...

  9. Linux进程间通信(IPC)机制总览

    Linux进程间通信 Ø  管道与消息队列 ü  匿名管道,命名管道 ü  消息队列 Ø  信号 ü  信号基础 ü  信号应用 Ø  锁与信号灯 ü  记录锁 ü  有名信号灯 ü  无名信号灯(基 ...

随机推荐

  1. Contest Record

    Contest 1135 at HZOI Problem A: 优美的棋发现一个可以证明的规律就是了……忘记给<<运算的左边变量转化为long long类型了,结果挂了20分……以后一定记 ...

  2. 弄清楚CSS的匹配原理让你写出高效的CSS

    用了这么多年的CSS,现在才明白CSS的真正匹配原理,不知道你是否也跟我一样?看1个简单的CSS: DIV#divBox p span.red{color:red;} 按习惯我们对这个CSS 的理解是 ...

  3. Codeforces 97.B Superset

    A set of points on a plane is called good, if for any two points at least one of the three condition ...

  4. 省选模拟赛 arg

    1 arg (arg.cpp/in/out, 1s, 512MB)1.1 Description给出一个长度为 m 的序列 A, 请你求出有多少种 1...n 的排列, 满足 A 是它的一个 LIS. ...

  5. ACE服务端编程4:ACE跨平台之运行时初始化和关闭

    参考APG里的说法:平台差异及不兼容性的一个特别的方面,是对象的运行时初始化和程序关闭时这些对象的相应析构. ACE为了明确管理对象的清理,定义了ACE_Object_Manager类,这个类不仅涉及 ...

  6. 题解 UVA1184 【Air Raid】

    有向无环图(DAG)的最小路径覆盖的模板题. 定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点. 由题意可得这是一个有向图,而路径不能相交,于是我们知道这是无向图的不相交最小路径覆盖问 ...

  7. C语言 两个小知识点

    strlen 函数原型 extern unsigned int strlen(char *s); 在Visual C++ 6.0中,原型为size_t strlen(const char *strin ...

  8. 在Unity中实现屏幕空间反射Screen Space Reflection(2)

    traceRay函数 在上一篇中,我们有如下签名的traceRay函数 bool traceRay(float3 start, float3 direction, out float2 hitPixe ...

  9. 【洛谷 P3705】 [SDOI2017]新生舞会(费用流,01分数规划)

    题目链接 看到这题我想到了以前做过的一题,名字记不清了,反正里面有"矩阵"二字,然后是道二分图匹配的题. 经典的行列连边网络流. 第\(i\)行和第\(j\)列连边,费用为\(b[ ...

  10. 设置display:inline-block产生间隙

    display:inline-block产生间隙,是由于换行在内的空白符 display:inline-block在IE下仅仅是触发了layout,而它本是行布局,触发后,块元素依然还是行布局.所以需 ...