1.什么是管道?
管道分为无名管道和命名管道,本文中如无特殊说明均指无名管道。
管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
A.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
B.只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
C.单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
D.数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2.如何创建管道?
2.1 包含头文件
#include <unistd.h>
int pipe(int pipefd[2]);
返回值:成功,返回0,否则返回-1。
参数数组包含pipe使用的两个文件的描述符:fd[0]:读端,fd[1]:写端。
3.管道数据的读写
管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等。
从管道中读取数据:
如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)
内核代码pipe.c里没有pipe的接口,而是定义成了系统调用接口sys_pipe,。内核里有如下定义
SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
return sys_pipe2(fildes, 0);
}
4.实例1
#include<unistd.h>
#include<memory.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int r_num;
int pipe_fd[2];
char buf_r[100];
char* p_wbuf;
memset(buf_r,0,sizeof(buf_r));
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(1);
if((r_num=read(pipe_fd[0],buf_r,100))>0)
{
printf("%d numbers read from pipe is:\n %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
{
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write success!\n");
if(write(pipe_fd[1]," PIPE",5)!=-1)
printf("parent wirte2 succes!\n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
}
4.1函数解释
函数首先创建了一个管道,接着用fork创建了一个进程,在子进程中首先关闭管道的写端,再从管道中读取数据,接着关闭管道读端,最后子进程退出;在父进程中,首先关闭管道的读端,接着向管道中写入两次数据,接着关闭管道写端,等待子进程退出,最后退出程序;
4.2 fork()函数
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
4.3程序的编译、运行
[wss@localhost pipe]$ls
pipe.c
[wss@localhost pipe]$gcc pipe.c -o pipe
[wss@localhost pipe]$ls
pipe pipe.c
[wss@localhost pipe]$./pipe
parent write success!
parent wirte2 succes!
10 numbers read from pipe is:
Hello PIPE
[wss@localhost pipe]$
5.实例2
这个例子是官方给的标准例子,程序首先创建一个管道,然后创建一个子进程,之后父进程关闭管道的读端,然后向管道写入命令行传入的参数,然后关闭管道的写端,父进程退出;在子进程中,首先关闭管道写端,接着读管道,数据读取完成,程序退出;
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2)
{
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0)
{ /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
}
else
{ /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
5.1程序的编译运行
[wss@localhost pipe2]$ls
pipe2.c
[wss@localhost pipe2]$gcc pipe2.c -o pipe2
[wss@localhost pipe2]$ls
pipe2 pipe2.c
[wss@localhost pipe2]$./pipe2 Hello-pipe
Hello-pipe
[wss@localhost pipe2]$
在Linux内核的实现中,read默认是阻塞的,write默认是非阻塞的,当然,用户也可以通过fcntl来改变文件的读写模式。