1、管道(PIPE)
从概念上说,管道是两个进程之间的一个connection,因此一个进程的标准输出就变成了另一个进程的标准输入。在Unix操作系统中,管道用于进程间通信(inter-process communication).
(1)Pipe是一种单向的通信方式,一个进程向管道中写数据,另一个进程从这个管道中读,数据只能向一个方向流动,需要双向通信时,要建立起两个管道。打开的管道是内存中的一段区域,它被看做是虚拟文件,但是它不是普通的文件,它只存在于内存中。
(2)创建管道的进程以及它的的子进程都可以使用管道进行读写操作,也就是只能用于父子进程和兄弟进程之间通信。
(3)如果一个进程尝试从还没有写入数据的管道中读数据,那么该进程会一直suspend到管道有数据写入。
(4)pipe系统调用会在进程的打开文件表中找到两个可用的位置,将这两个位置作为pipe的读和写的结束位置。写入的内容每次都添加在管道缓冲区的末尾,读数据是从缓冲区的头部读。
语法:
Parameters :
fd[0] will be the fd(file descriptor) for the
read end of pipe.
fd[1] will be the fd for the write end of pipe.
Returns : 0 on Success.
-1 on error.
管道的两端可分别用文件描述符fd[0]和fd[1]描述,fd[0]为管道读端,只能用于读;fd[1]为管道写端,只能用于写,一般文件的I/O函数都可以用于管道,如close、read、write等。
管道是FIFO模式的,类似队列数据结构,读数据的大小不需要和写数据的大小匹配,一次可以写512字节的数据,一次也可以读1字节的数据。
实例:
#include <stdio.h>
#include <unistd.h>
#define MSGSIZE 16
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";
int main()
{
char inbuf[MSGSIZE];
int p[2], i;
if (pipe(p) < 0)
exit(1);
/* continued */
/* write pipe */
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
for (i = 0; i < 3; i++) {
/* read pipe */
read(p[0], inbuf, MSGSIZE);
printf("% s\n", inbuf);
}
return 0;
}
输出:
~/SourceCode/learn/C_learn$ ./pipe1
hello, world #1
hello, world #2
hello, world #3
如果在一个进程中使用fork,文件描述符在父进程和子进程中都是打开的,如果在创建管道之后在调用fork,那么父进程和子进程之间就可以通过管道通信。
实例:
#include <stdio.h>
#include <unistd.h>
#define MSGSIZE 16
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";
int main()
{
char inbuf[MSGSIZE];
int p[2], pid, nbytes;
if (pipe(p) < 0)
exit(1);
/* continued */
if ((pid = fork()) > 0) {
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
// Adding this line will
// not hang the program
// close(p[1]);
wait(NULL);
}
else {
// Adding this line will
// not hang the program
// close(p[1]);
while ((nbytes = read(p[0], inbuf, MSGSIZE)) > 0)
printf("% s\n", inbuf);
if (nbytes != 0)
exit(2);
printf("Finished reading\n");
}
return 0;
}
输出:
~/SourceCode/learn/C_learn$ ./pipe2
hello, world #1
hello, world #2
hello, world #3
(hangs)
当读/写结束时,父进程和子进程都会block住,所以程序不会结束。这是因为读系统调用会读出它所请求的数据大小或者管道中的数据大小。
如果没有进程写或者打开管道,那么管道是空的,对管道执行读系统调用会返回EOF(0)。
如果其它的进程打开了管道去写它,那么read系统调用会认为有新数据会被写入,这时候pipe没有关闭,因此会hang住
一个小实验:
写一个C程序,创建两个进程P1和P2, P1进程中有一个字符串str1并传给P2,P2将收到字符串str1与另一个字符串str2连接起来,然后返回给P1进程,不使用字符串库函数。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
// We use two pipes
// First pipe to send input string from parent
// Second pipe to send concatenated string from child
int fd1[2]; // Used to store two ends of first pipe
int fd2[2]; // Used to store two ends of second pipe
char fixed_str[] = "abcd";
char input_str[100];
pid_t p;
if (pipe(fd1)==-1)
{
fprintf(stderr, "Pipe Failed" );
return 1;
}
if (pipe(fd2)==-1)
{
fprintf(stderr, "Pipe Failed" );
return 1;
}
scanf("%s", input_str);
p = fork();
if (p < 0)
{
fprintf(stderr, "fork Failed" );
return 1;
}
// Parent process
else if (p > 0)
{
char concat_str[100];
close(fd1[0]); // Close reading end of first pipe
// Write input string and close writing end of first
// pipe.
write(fd1[1], input_str, strlen(input_str)+1);
close(fd1[1]);
// Wait for child to send a string
wait(NULL);
close(fd2[1]); // Close writing end of second pipe
// Read string from child, print it and close
// reading end.
read(fd2[0], concat_str, 100);
printf("Concatenated string %s\n", concat_str);
close(fd2[0]);
}
// child process
else
{
close(fd1[1]); // Close writing end of first pipe
// Read a string using first pipe
char concat_str[100];
read(fd1[0], concat_str, 100);
// Concatenate a fixed string with it
int k = strlen(concat_str);
int i;
for (i=0; i<strlen(fixed_str); i++)
concat_str[k++] = fixed_str[i];
concat_str[k] = '\0'; // string ends with '\0'
// Close both reading ends
close(fd1[0]);
close(fd2[0]);
// Write concatenated string and close writing end
write(fd2[1], concat_str, strlen(concat_str)+1);
close(fd2[1]);
exit(0);
}
}
2、命名管道
命名管道(也称FIFO)是进程间通信的一种方式
(1)它是unix上对传统管道的扩充。传统的管道没有名称,并且仅限于具有血缘关系的进程之间通信,生命周期与这些进程的生命周期相同。
(2)命名管道的生命周期超出进程的生命周期,只要系统在运行,那么命名管道就可以一直存在。
(3)一般来说命名管道是一个文件,进程可以使用它进行进程间通信。它是一个存储在本地空间的FIFO类型的文件,多个进程可以通过读写该文件实现进程间通信。
(4)可以通过调用mkfifo创建管道,一旦以这种方式创建了管道,任何进程都可以打开它执行读写操作,就像普通文件一样。但是在进行任何读写操作之前,必须在两端同时打开管道。
创建FIFO文件:
int
mkfifo(
const
char
*pathname, mode_t mode);
mkfifo创建一个以pathname命名的FIFO文件,mode指定FIFO的权限,可以通过进程的umask修改权限,该FIFO文件的权限为(mode & ~umask).
对于FIFO文件,可以使用大部分的文件系统调用:open、read、write、close.