有名管道的打开规则
有名管道比管道多了一个打开操作:open。
FIFO的打开规则:
如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
注意:
读取管道打开时需要等待写入管道的打开,同时写入打开管道又需要等待读取管道的打开
Deadlock不会产生,因为这里“等待”的*不*是另一方的open是否*已经完成*
举个例子,
如果open FIFO时都不指定O_NONBLOCK, A进程首先 open FIFO for reading, 它会block.
然后B进程 open FIFO for writing, 由于进程A正在open for reading, 因此B的open操作会
立即返回,然后A的open 操作返回。
对打开规则的验证参见附2。
2.4有名管道的读写规则
从FIFO中读取数据:
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
- 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
- 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
- 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。
- 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
向FIFO中写入数据:
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
对于设置了阻塞标志的写操作:
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <sys/select.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> int main(int argc, char *argv[]) { int i, rfd, wfd, len = 0, fd_in, vfd; int err, cnt; char str[32]; int flag, stdinflag; fd_set write_fd, read_fd; struct timeval net_timer; if(access("fifoz", F_OK) < 0) { err = mkfifo("fifoz", S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); if(err < 0) { perror("mkfifoz"); //exit(EXIT_FAILURE); } } if(access("fifoy", F_OK) < 0) { err = mkfifo("fifoy", S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); if(err < 0) { perror("mkfifoy"); //exit(EXIT_FAILURE); } } vfd = open("fifoz", O_RDONLY|O_NONBLOCK); if((wfd = open("fifoz", O_WRONLY|O_NONBLOCK)) < 0) { perror("open fifoz"); return -1; } if((rfd = open("fifoy", O_RDONLY|O_NONBLOCK)) < 0) { perror("open fifoy"); return -1; } vfd = open("fifoy", O_WRONLY|O_NONBLOCK); if(rfd <= 0 || wfd <= 0) return 0; printf("\nzhengfang...\n"); while(1) { FD_ZERO(&read_fd); FD_SET(rfd, &read_fd); FD_SET(fileno(stdin), &read_fd); net_timer.tv_sec = 5; net_timer.tv_usec = 0; memset(str, 0, sizeof(str)); if(i = select(rfd+1, &read_fd, NULL, NULL, &net_timer) <= 0) continue; if(FD_ISSET(rfd, &read_fd)) { cnt = read(rfd, str, sizeof(str)); printf("yuhuanzhang%d:%s\n", cnt, str); } if(FD_ISSET(fileno(stdin), &read_fd)) { printf("----------fifo1----------\n"); fgets(str, sizeof(str), stdin); len = write(wfd, str, strlen(str)); } } close(rfd); close(wfd); }
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <sys/select.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> int main(int argc, char *argv[]) { int i, rfd, wfd, len = 0, fd_in, vfd; int err, cnt; char str[32]; int flag, stdinflag; fd_set write_fd, read_fd; struct timeval net_timer; if(access("fifoz", F_OK) < 0) { err = mkfifo("fifoz", S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); if(err < 0) { perror("mkfifoz"); //exit(EXIT_FAILURE); } } if(access("fifoy", F_OK) < 0) { err = mkfifo("fifoy", S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); if(err < 0) { perror("mkfifoy"); //exit(EXIT_FAILURE); } } //printf("sleep 2s...\n"); //sleep(2); if((rfd = open("fifoz", O_RDONLY|O_NONBLOCK)) < 0) { perror("open fifoz"); return -1; } vfd = open("fifoz", O_WRONLY|O_NONBLOCK); vfd = open("fifoy", O_RDONLY|O_NONBLOCK); if((wfd = open("fifoy", O_WRONLY|O_NONBLOCK)) < 0) { perror("open fifoy"); return -1; } printf("\nyuhuanzhang...\n"); while(1) { FD_ZERO(&read_fd); FD_SET(rfd, &read_fd); FD_SET(fileno(stdin), &read_fd); net_timer.tv_sec = 5; net_timer.tv_usec = 0; memset(str, 0, sizeof(str)); if(i = select(rfd+1, &read_fd, NULL, NULL, &net_timer) <= 0) continue; if(FD_ISSET(rfd, &read_fd)) { cnt = read(rfd, str, sizeof(str)); printf("zhengfang%d:%s\n", cnt, str); } if(FD_ISSET(fileno(stdin), &read_fd)) { printf("----------fifo2-----------\n"); fgets(str, sizeof(str), stdin); len = write(wfd, str, strlen(str)); } } close(rfd); close(wfd); }
现在简要说明一下遇到的问题:
要通过FIFO来进行进程间通信,用阻塞模式打开的话,进程会block在open函数上。估计谁也不希望在一个ipc类的initialize函数里面打开fifo文件时被block住。所以解法就是用非阻塞模式打开(|O_NONBLOCK),打开的问题我们解决了。
但这样我们还是没有办法像操作普通文件一样用阻塞方式读取数据,就算是用fcntl函数clear掉O_NONBLOCK标识也不行,这是因为上面的一段话“If no process has the pipe open for writing, read() will return 0 to indicate end-of-file. ”,也就是说在没有进程用写模式打开fifo文件的时候,去掉不去掉O_NONBLOCK标识read函数都会直接返回。恰巧我是想用echo向fifo中去写数据。echo不是一个常驻进程,所以在echo还没执行或者执行完以后,我们是没有办法去阻塞读fifo的。(一般的解决方法有:1.用循环,这个办法太土,不予讨论。2.还可以用select或者poll去异步监控fd事件,但感觉很不值当,虽然网上都说要用这种方法。3.用O_ASYNC去触发信号的方法没试过,感觉太繁琐。)
山穷水尽啦,疑无路啦,可这句诗还有后半句,叫"柳暗花明又一村"。
那就是在用只读非阻塞方式打开fifo文件的同时,然后再用只写阻塞方式再次打开该fifo文件。
再来看上面绿色的部分:
A process that uses both ends of the connection in order to communicate with itself should be very careful to avoid deadlocks.
既然man里面没说不许同一个进程同时打开fifo的两端,所以我们这么做一般也不会被人鄙视的。这样的具体原因也在上面的第二段绿色文字中:
When attempting to read from an empty pipe or FIFO:
If some process has the pipe open for writing and O_NONBLOCK is clear, read() will block the calling thread until some data is written or the pipe is closed by all processes that had the pipe open for writing.
也就是说我们自己保持一个该fifo文件的只写阻塞fd,我们也不会通过这个只写阻塞fd做任何输入操作,只是为了让那个只读fd能在读取数据时block在read函数中。
同时有下面这句作保障:
It can be opened by multiple processes for reading or writing.
从而保障了我们在用只写阻塞方式多打开了一次该fifo文件后,不会对外部的echo操作有任何影响。
for read only will succeed even if no-one has opened on the write side
yet; opening for write only will fail with ENXIO (no such device or
address) unless the other end has already been opened.