管道(pipe)是无名管道,他是进程资源的一部分,随着进程的结束而消失。并且它只能在拥有公共祖先进程的进程内通信。而有名管道(FIFO)的出现则解决了这个问题。FIFO提供了一个路径名与它关联。这样可以通过访问该路径就能使得两个进程之间相互通信。此处的FIFO严格遵守“先进先出”原则。读总是从头开始的,写总是从尾部进行的。匿名管道和FIFO都不支持lseek函数对他们操作。Linux下建立有名管道的函数是mkfifo。
函数原型: int mkfifo(const char * pathname,mode_t mode);
函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。
例如执行下面代码来创建一个FIFO。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
int main()
{
int ret;
ret = mkfifo("My_FIFO",0666);
if(0 != ret)
{
perror("mkfifo");
}
return 0;
}
可以看到,它以P开头,表面它是一个FIFO文件。它是真实存在于磁盘上的,不仅仅在内存中。进程结束了这个文件仍然在。FIFO和匿名管道一样,默认下要考虑阻塞。
- 当使用O_NONBLOCK标志的时候,打开FIFO文件,读取操作会立即返回。但是如果没有进程读取FIFO文件,那么写入FIFO的操作会返回ENXIO错误代码。
- 不使用O_NONBLOCK标志时,打开FIFO的操作必须等待其他进程写入FIFO后才执行,当然写入FIFO的操作也必须等到其他进程来读取FIFO以后才能执行。
当存在这个FIFO文件的时候,再次创建这个FIFO会显示File exists。首先,第一种情形的测试。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int ret;
int fd;
ret = mkfifo("My_FIFO",0666);
if(0 != ret)
{
perror("mkfifo");
}
pid_t pid;
pid = fork();
if(0 < pid)
{
fd = open("My_FIFO",O_NONBLOCK|O_WRONLY);
if(0 > fd)
{
perror("open FIFO");
}
else
{
write(fd,"Hello",5);
printf("Write Over
");
}
exit(0);
}
if(0 == pid)
{
sleep(2); //在设置O_NONBLOCK标志的情形下,让父进程先写入
}
if(-1 == pid)
{
perror("fork");
}
return 0;
}
运行结果如下:
可以看到会发生错误,因为它没有阻塞。那么接着试一下直接读一个FIFO文件,看看会发生什么。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int ret;
int fd;
char str[30] = {0};
ret = mkfifo("My_FIFO",0666);
if(0 != ret)
{
perror("mkfifo");
}
pid_t pid;
pid = fork();
if(0 < pid)
{
fd = open("My_FIFO",O_NONBLOCK|O_RDONLY); //注意这里和上面不一样,改成O_RDONLY
if(0 > fd)
{
perror("open FIFO");
}
else
{
read(fd,str,sizeof(str));
printf("Read Over
");
}
exit(0);
}
if(0 == pid)
{
sleep(2); //在设置O_NONBLOCK标志的情形下,让父进程直接读取
}
if(-1 == pid)
{
perror("fork");
}
return 0;
}
看到的结果是对一个空的FIFO文件打开并执行read操作是没有问题的。
先以只读方式打开,如果没有进程已经为写而打开一个 FIFO, 只读 open() 成功,并且 open() 不阻塞。
下面,当不设置O_NONBLOCK标志的时候,FIFO和匿名管道的处理方式是一样的。管道这个名字是非常形象的,一个管道必须有两端(就是在一个进程中必须读,另一个进程必须写),只有这样,才能正常操作,否则进程将会阻塞。例如下面这样。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
int main()
{
int ret;
int fd;
char str[30] = {0};
ret = mkfifo("My_FIFO",0666);
if(0 != ret)
{
perror("mkfifo");
}
pid_t pid;
pid = fork();
if(0 < pid)
{
fd = open("My_FIFO",O_WRONLY);
if(0 > fd)
{
perror("Write FIFO");
}
else
{
write(fd,"Hello",5);
printf("Write Over
");
}
wait(NULL);
exit(0);
}
if(0 == pid)
{
sleep(2);
// fd = open("My_FIFO",O_RDONLY);
// read(fd,str,5);
exit(0);
}
if(-1 == pid)
{
perror("fork");
}
return 0;
}
我们仅仅在父进程中进行了写(write),没有其他进程在读(read)。这样造成的结果是进程一直阻塞在这里,如下
没有输出结果,阻塞在这里不动了。而当我们加上注释掉了那两句话以后,程序就会有输出,结果如下:
或者说,这也体现了进程的并发行,管子有了一端以后,还必须有另一端,这才能构成管道。
测试一下,FIFO用于两个无关进程直接的通信。首先建立我们有两个进程,一个是test1,另一个是test2.
//test1的源代码
//test1是写FIFO
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int fd,ret;
char str[10] = {"Hello"};
ret = mkfifo("fifo",0666); //test1.c中创建FIFO文件
fd = open("fifo",O_WRONLY); //只写方式打开
write(fd,str,5);
close(fd);
return 0;
}
//test2的源代码
//test2是读FIFO
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int fd;
char str[10] = {0};
fd = open("fifo",O_RDONLY);
read(fd,str,5);
printf("%s
",str);
close(fd);
return 0;
}
我们把test1和test2的源代码生成可执行文件后,打开两个终端。如果我先运行test1,然后运行test2.那么test2将读取到FIFO中的数据。如下所示。
我们没有设置O_NONBLOCK,先运行test1之后,会发现test1阻塞在这里。等我们把test2也运行了之后,test1不在阻塞,运行结束,然后test2也成功打印出了Hello。
换个运行顺序,我们先运行test2,然后运行test1.这样会发现test2阻塞在这里。等我们把test1也运行了之后,test2不在阻塞,向屏幕打印Hello.
如果我们不想让FIFO阻塞,那么打开文件的时候设置为可读可写即可。
fd = open("fifo", O_RDWR);
当然,如果FIFO是空的,那么即使设置了可读可写,read()操作仍旧会阻塞。这样的运行结果和上面所说的是一致的。自己运行一下才能深刻理解。这里不好用图片说明。
调用 write() 函数向 FIFO 里写数据,当缓冲区已满时 write() 也会阻塞。
通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会退出。