进程间通信之管道
1、介绍
管道和有名管道是最早的进程间通信机制之一,管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
2、特点
管道:本质是一个伪文件(实为内核使用环形队列机制,借助内核缓冲区(4k)实现)。
有名管道:不同于管道,它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。
半双工的,数据只能向一个方向流动;
需要双方通信时,需要建立起两个管道;
由两个文件描述符引用,一个表示读端,一个表示写端;
规定数据从管道的写端流入管道,从读端流出(写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据,不支持诸如lseek()等文件定位操作);
数据一旦被读走,便不在管道中存在,不可反复读取。
3、管道的读写行为
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
总结:
① 读管道: 1. 管道中有数据,read返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)。
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)。
② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)。
2. 管道读端没有全部关闭:
(1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数。
4、有名管道读写行为
1.读取数据:如果有进程写打开FIFO,且当前FIFO内没有数据
(1)阻塞读(fd=open(FIFO_NAME,O_RDONLY);),则将一直阻塞等待数据。
(2)非阻塞读(O_RDONLY | O_NONBLOCK),则返回-1,当前errno值为EAGAIN,提醒以后再试。
所以对于阻塞读操作而言,造成阻塞的原因有:
(1)FIFO内有数据,但有其它进程在读这些数据
(2)FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
2.写入数据:
(1)对于设置了阻塞标志的写操作:
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。(PIPE_BUF ==>> /usr/include/linux/limits.h)
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回(多个进程都写数据交错)。
(2)对于没有设置阻塞标志的写操作:
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
5、管道创建
int pipe(int fd[2])
fd为filedescriptors的缩写,其为一个二元数组,用于存放pipe函数所创建管道的两个文件描述符,fd[0]存放管道读取端的文件描述符,fd[1]用于存放管道写入端的文件描述符。通常,进程pipe()创建管道后,再fork一个子进程,调用exec函数族使子进程执行所需程序,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。通信方式:父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
6、有名管道创建
int mkfifo(const char * pathname, mode_t mode)
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EXIST错误,所以一般典型的调用代码首先会检查是否返回该错误。 一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
7、有名管道程序
父进程传递参数和有名管道名给子进程,子进程做完操作,写管道数据,父进程阻塞直到读到数据
父进程
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <string.h> #include <errno.h> #include <sys/stat.h> #include <time.h> int main(void) { char tgid[6] = "12345"; int pid; int x; srand(time( NULL )); x =rand(); printf("x:%d\n", x); char fifoname[10] = "xxx"; if ((pid = fork()) < 0){ perror ( "failed to fork " ); return 1; } else if (pid > 0){ //printf("parent:%d\n", getpid()); int m = mkfifo(fifoname,0664); if(m == -1) { perror("mkfifo"); return 1; } int fd = open(fifoname, O_RDONLY);////没有O_NONBLOCK,所以read默认open会阻塞,直到子进程写打开并且写入数据。除非O_RDONLY | O_NONBLOCK才不阻塞 if(fd == -1) { perror("open"); return 1; } char buf[5] = "0"; int r; while(1)//还是搞了个whild { r = read(fd, buf, sizeof(buf)); if(strcmp(buf, "1") == 0) { break;//读到子进程python给的值后才执行下面的 } } close(fd); unlink(fifoname);//删除指定参数,即管道 //printf("read:%s\n", buf); return 0; } else{ //printf("child:%d\n", getpid()); char *const p[]={"python", "/home/child.py", tgid, fifoname, NULL};//指向char的常量指针数组 if(execv("/usr/bin/python",p)<0) //if(execl("/usr/bin/python","python","/home/child.py",tgid,fifoname,NULL)<0)//tgid为传给子进程python的,pipename是有名管道名 //if(execl("./test","./test",tgid,NULL)<0)//C语言 { fprintf(stderr,"execl failed:%s\n",strerror(errno)); return 1; } } //return 0; }
子进程:
#!/usr/bin/python import sys import os def main(argv): print 'python pid:%s' % (os.getpid()) print 'python tgid:%s' % (sys.argv[1]) f = os.open(sys.argv[2], os.O_WRONLY) time.sleep(10) print 'python end sleep' os.write(f, "1") os.close(f) if __name__ == "__main__": main(sys.argv[1:])
子进程-C语言
#include <stdio.h> #include <sys/prctl.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <error.h> #include <fcntl.h> int main(int argc, char **argv) { int oret; int wret; prctl(PR_SET_PDEATHSIG, SIGHUP); printf("child.c tgid:%s\n", argv[1]); printf("child.c fifoname:%s\n", argv[2]); sleep(10); oret = open(argv[2], O_WRONLY); if(oret < 0) { perror("open error"); return 1; } wret = write(oret,"1",2); if(wret == -1) { perror("write error"); return 1; } close(oret); return 0; }
定义参考:
https://blog.csdn.net/u011068702/article/details/54914774
https://blog.csdn.net/firefoxbug/article/details/8137762