进程间的通信——无名管道
宗旨:技术的学习是有限的,分享的精神是无限的。
一、进程间的通信
(1)同主机进程间数据交互机制:无名管道(PIPE),有名管道(FIFO)、消息队列和共享内存。无名管道多用于亲缘关系进程间通信,无名管道可用于任意同主机进程间通信,但管道是单向的,会出现同时读写问题。消息队列可以实现同主机上任意多进程间通信,但存放的数据量很有限,应用于少量的数据传递。共享内存可实现同主机任意进程间大数据通信,但共享空间数据访问时存在竞争问题。
(2)同主机进程间同步机制:信号量(semaphore)
(3)同主机进程间异步机制:信号(signal)
(4)网络主机间数据交互机制:套接字(socket)
二、无名管道
使用管道将一个命令的输出作为另一个命令的输入(运行时一个命令将创建一个进程),这种管道是临时的,命令执行完后将自动消失,这类管道称为无名管道。
无名管道是一种特殊类型的文件,在内核中对应的资源即一段特殊内存空间,内核在这段空间中以循环队列的方式临时存入一个进程发给另一个进程的信息,这段内核空间完全由操作系统管理和维护,应用程序只能通过系统调用访问。
无名管道和普通文件:无名管道的内核资源在进程退出时自动释放;不能像普通文件一样存储大量常规信息;编程应用中和普通文件有一样的特点,可以read/write。
1、 pipe
——创建无名管道,单向传输
(1) 函数原型
#include <unistd.h>
int pipe(int pipes[2]);
(2) 函数参数
pipes:管道的读端和写端【pipes[0]——读出,pipes[1]——写入】
(3) 返回值
执行成功,存储两个文件描述符于pipes中,返回0,调用失败返回-1
2、 两进程通信
父进程调用pipe函数开辟管道,得到两个文件描述符指向管道的两端;父进程调用fork函数创建子进程,那么子进程也有两个文件描述符指向管道两端;父进程关闭管道读端,子进程关闭管道写端,父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
#include<stdlib.h>
#include<unistd.h>#defineMAXLINE 80in tmain(void)
{int n;int fd[2];pid_t pid;char line[MAXLINE];if (pipe(fd) < 0) // 开辟管道{perror("pipe");exit(1);}if ((pid = fork()) < 0) // 创建子进程{perror("fork");exit(1);}if (pid > 0) /* parent */{close(fd[0]);write(fd[1], "hello world\n", 12);wait(NULL);}else /* child */{close(fd[1]);n = read(fd[0], line, MAXLINE);write(STDOUT_FILENO, line, n);}return 0;
}
3、 注意4种特殊情况(都是阻塞I/O操作,没有设置O_NONBLOCK标志)
(1) 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程 从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件 末尾一样。
(2) 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的 进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读 取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
(3) 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
(4) 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。