管道基本概念
管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际上是获得两个文件描述符:一个用与读取而另一个用于写入。任何从管道写入端写入的数据,可以从管道读取端读出。
管道通信具有以下特点:
(1)管道是半双工的,数据只能向一个方向流动,需要双方通信时,要建立起两个管道。
(2)管道存放在内存中,是一种独立的文件系统。
无名管道的创建与读写
系统调用pipe()用于创建一个管道,其函数原型如下:
pipe()将建立一对文件描述符,放到参数 pipefd 中。 pipefd[0] 文件描述符用来从管道中读取数据, pipefd[1]用于写入数据到管道。
单个进程中的管道几乎没有任何意义,通常,进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的通道。
fork出子进程后,父进程的文件描述表也复制到子进程,于是如下图:
关闭掉父进程的fd[0], 以及子进程的fd[1],则父进程可以发送消息给子进程,反之亦然。
源代码:pipe_test.c
#include
#include
void child_process(int pipefd[])
{
int i = 0;
char writebuf[128] = {0};
/*子进程关闭读文件描述符*/
close(pipefd[0]);
while(1)
{
sprintf(writebuf, "write pipe : %d", i);
/*子进程往管道写入数据*/
write(pipefd[1], writebuf, strlen(writebuf));
printf("write pipe: %s\n", writebuf);
i = (i + 1) % 10;
sleep(1);
}
}
void father_process(int pipefd[])
{
char readbuf[128] = {0};
/*父进程关闭写文件描述符*/
close(pipefd[1]);
while(1)
{
/*父进程从管道中读取数据*/
read(pipefd[0], readbuf, sizeof(readbuf));
printf("read pipe: %s\n", readbuf);
}
}
int main(int argc, char** argv)
{
int pipefd[2];
pid_t pid;
if( pipe(pipefd) == -1)
{
perror("pipe:");
return -1;
}
pid = fork();
if(pid == 0)
{
child_process(pipefd);
}
else if(pid != -1)
{
father_process(pipefd);
}
else
{
perror("fork:");
}
return 0;
}
运行结果:子进程写数据,父进程读出数据并且打印。
注意:匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
有名管道FIFO
FIFO是first in first out(先进先出)的缩写,FIFO也称为“命名管道”。FIFO是一种特殊类型的管道,它在文件系统中有一个相应的文件,称为管道文件。
FIFO文件可以通过 mkfifo() 函数创建。在FIFO文件创建之后,任何一个具有适当权限的进程都可以打开FIFO文件。
mkfifo()函数原型为:
参数:
pathname 一个FIFO文件的路径名。
mode 与普通文件 creat() 函数中的mode参数相同。
返回值:
如果要创建的文件已经存在,返回-1, errno 为 EEXIST 错误, 成功返回0。
一般文件的I/O函数都可以用于FIFO,如open()、read()、write()、close()等等。
源代码fifo_write_test.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
void handle_sig(int sig)
{
printf("signal pipe\n");
exit(-1);
}
int main(int argc, char** argv)
{
int fd;
int ret;
char buf[128];
int i = 0;
/*处理管道信号*/
signal(SIGPIPE, handle_sig);
if(mkfifo("./fifo", 0640) == -1)
{
if(errno != EEXIST)
{
perror("mkfifo");
return -1;
}
}
/*只写方式打开管道*/
fd = open("./fifo", O_WRONLY);
if(fd == -1)
{
perror("open");
return -1;
}
while(1)
{
sprintf(buf, "data %d", i++);
/*往管道写数据*/
ret = write(fd, buf, strlen(buf));
printf("write fifo [%d] %s\n", ret, buf);
sleep(1);
}
return 0;
}
源码中,通过mkfifo来创建FIFO文件,并且以只写 的方式打开,只有当两边的管道都打开的时候才能写进去,否则阻塞在 write() 函数上。
源代码fifo_read_test.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char** argv)
{
int fd;
int ret;
char buf[128];
if(mkfifo("./fifo", 0640) == -1)
{
if(errno != EEXIST) /*如果错误类型是fifo文件已经存在,则继续执行*/
{
perror("mkfifo");
return -1;
}
}
/*以只读方式打开管道*/
fd = open("./fifo", O_RDONLY);
if(fd == -1)
{
perror("open");
return -1;
}
while(1)
{
memset(buf, 0, sizeof(buf));
/*读管道*/
ret = read(fd, buf, sizeof(buf) - 1);
printf("read fifo [%d] : %s\n", ret, buf);
sleep(1);
}
return 0;
}
源代码中,通过 mkfifo() 函数创建FIFO管道,如果已经存在那么就直接只读方式打开,如果另一端没有被打开,则阻塞在 read() 函数上,如果另一端打开后关闭,则 read() 一直读到EOF也就是0个字节。
管道容量
管道有它的容量大小,通过man 7 pipe来查看。默认情况下为64k字节,也可以通过fcntl函数来查看:
例如:
#include
#include
#include
#include
#include
#include
#include
#include
#define _GNU_SOURCE
int main()
{
if(mkfifo("./fifo", 0640) == -1)
{
if(errno != EEXIST)
{
perror("mkfifo");
return -1;
}
}
/*只写方式打开管道*/
int fd = open("./fifo", O_WRONLY | O_NONBLOCK);
if(fd == -1)
{
perror("open");
return -1;
}
printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
fcntl(fd, F_SETPIPE_SZ, 4096 * 3); /*必须填写4096的整数倍*/
printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
return 0;
}
注意事项
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1.如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3.如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。讲信号时会讲到怎样使SIGPIPE信号不终止进程。
4.如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
查看系统限制
pathconf函数返回配置文件的限制值,是与文件或目录相关联的运行时限制。path参数是你想得到限制值的路径,name是想得到限制值的名称,name的取值主要有以下几个取值: