一、常见的通信方式
- 管道 pipe:是一个环形缓冲区,允许两个进程以生产者/消费者的模型进行通信。管道是一种半双工的通信方式,数据只能单向流动。它分为两类:命名管道和匿名管道。
- FIFO特别文件:它允许非父子关系的进程也可以交换数据。
- 消息队列 Message Queue:半双工的先进先出通信通道,是由离散的消息组成的队列。消息队列实际上是内核地址空间的内部链表。
- 信号 Sinal:用于向一个进程通知发生异步事件的机制。它类似与硬件中断,但没有优先级,即内核公平地对待所有信号。
- 信号量 Semaphore:用于进程间传递信号的一个整数值。在信号量上只可进行三个操作:初始化、增加、递减。递减操作作用于阻塞一个进程,递增操作作用于解除一个进程的阻塞。
- 共享内存 Shared Memory:虚存中由多个进程共享的一个公共内存块,希望通信的进程通过一块具有唯一标识的共享存储连接在一起,并可以用与正常存储访问一样的方法访问这块共享存储区,从而交换数据。
其中,管道、消息队列和共享内存提供了进程间传递数据的方法,信号和信号量则用于触发其他进程的行为。
二、详解
(一) 管道
管道允许在进程之间按先进先出的方式传送数据,它提供进程之间单向通信的方法,即管道是连接一个进程的输出至另一个进程的输入的方法。
管道最常见的是在命令行中:
$cat file | grep "pipe" | more
- " | " 表示使用了管道。在上面命令行中,cat、grep和more三个命令之间使用了两个管道,分别使得cat的标准输出成为grep的标准输入,grep的标准输出成为more的标准输入。
- 管道是半双工的,数据只能向一个方向流动;如果需要用管道进行两个方向的通信,则需要同时创建两个管道,一个用于接收数据,一个用于发送数据。
- 匿名管道只能用于父子进程或者兄弟进程之间(具有父子关系的进程) 。
- 管道没有名字,它是为了一次使用而创建的。
- 管道的两个描述字是同时打开的
创建管道需调用pipe()函数。pipe()的唯一参数是一个由两个整数组成的数组,该数组在pipe()调用成功后将含有作为管道使用的两个文件描述字,其中一个作为管道的输入,一个作为管道的输出。
#include<unistd.h>
int pipe(int fdes[2]);
- 当进程调用pipe()成功后,内核在系统内部创建一条管道,并设置由该管道使用的两个已打开文件描述字于数组fdes。
- fdes[0]为读而打开,是与输入端相连的文件描述字。
- fdes[1]为写而打开,是与输出端相连的文件描述字。
- pipe()调用成功后返回0,否则返回-1并置errno错误条件。
- 当进程调用fork()派生一个子进程时,子进程将继承父进程所有打开的文件描述字。如果进程在调用fork()之间先调用pipe()创建一个管道,则在fork()调用之后,父、子进程都能访问构成管道的这两个文件描述字。此时可以利用管道在父子进程之间交换数据。
从上图可以看出管道有两种通信方向,但是这两种通信方向不能同时存在,否则会导致混乱。
因此要选择一个通信方向:如果选择从父进程往子进程发送数据,那么父进程要关闭它的fdes[0],子进程要关闭它的fdes[1]。 - 例:建立一个从父进程通往子进程的通道,子进程通过管道接收父进程发送的数据。
建立的管道如图所示:
相应代码:
#include "ch11.h"
int main(void)
{
pid_t pid;
int n, mypipe[2];
char buffer[BUFSIZ+1], some_data[]="Hello, world!";
/*创建管道*/
if (pipe (mypipe))
err_exit("Pipe failed.\n");
/*派生子进程*/
if ((pid = fork()) == (pid_t)0) {
/*子进程*/
close(mypipe([1]); /*子进程关闭管道输出端*/
n = read(mypipe[0], buffer, BUFSIZ);
printf("Child %d: read %d bytes: %s\n",getpid(),n,buffer);
}
else {
/*父进程*/
close(mypipe([0]); /*父进程关闭管道输入端*/
n = write(mypipe[1], some_data, strlen(some_data));
printf("Parent %d: write %d bytes: %s\n",getpid(),n,some_data);
}
exit(EXIT_SUCCESS);
输出结果:
$ a.out
parent 21009: write 13 bytes: Hello, world!
child 22162: read 13 bytes: Hello, world!
(二) FIFO特别文件
FIFO特别文件类似于管道,它也是半双工方式,且数据也按先进先出顺序传送。它允许非父子关系的进程交换数据。
- FIFO作为特别文件存在于文件系统中。
- 不同祖先的进程可以通过FIFO特别文件共享数据。
- 当共享进程完成了所有I/O操作后,除非用unlink()删除它,否则FIFO特别文件将保存在文件系统中,并且可以留待下一次使用。
- 方法一:用shell命令mknod或mkfifo
// 两条命令都建立一个名为myfifo的有名管道。
// mknod不仅可以创建FIFO,还可以创建其它类型的特别文件,因此用mknod创建FIFO时要通过参数"P"指明创建的是有名管道。
$ mknod myfifo p
// mkfifo是专门创建FIFO的命令,可以指定FIFO文件的访问方式。
// mknod不能像mkfifo一样指定FIFO文件的指定方式,由mknod创建的FIFO文件所在目录的读写方式如果需要改变,要用chmod命令来改变。
$ mkfifo a=rw myfifo
用ls命令可以查看文件系统中的FIFO特别文件,字母p开头的是FIFO特别文件。
$ ls -l myfifo
prw-r--r-- 1 zkj users 0 Jun 7 10:59 myfifo
- 方法二:在程序中调用函数mknod()或myfifo()。
#include <sys/types.h>
#include <sys/stat.h>
// mknod() 创建一个给定文件类型的新文件,该文件具有path指定的名字,文件类型由mode参数给出。
int mknod(const char *path,