进程间通信的方式

一、常见的通信方式

  1. 管道 pipe:是一个环形缓冲区,允许两个进程以生产者/消费者的模型进行通信。管道是一种半双工的通信方式,数据只能单向流动。它分为两类:命名管道和匿名管道。
  2. FIFO特别文件:它允许非父子关系的进程也可以交换数据。
  3. 消息队列 Message Queue:半双工的先进先出通信通道,是由离散的消息组成的队列。消息队列实际上是内核地址空间的内部链表。
  4. 信号 Sinal:用于向一个进程通知发生异步事件的机制。它类似与硬件中断,但没有优先级,即内核公平地对待所有信号。
  5. 信号量 Semaphore:用于进程间传递信号的一个整数值。在信号量上只可进行三个操作:初始化、增加、递减。递减操作作用于阻塞一个进程,递增操作作用于解除一个进程的阻塞。
  6. 共享内存 Shared Memory:虚存中由多个进程共享的一个公共内存块,希望通信的进程通过一块具有唯一标识的共享存储连接在一起,并可以用与正常存储访问一样的方法访问这块共享存储区,从而交换数据。

其中,管道、消息队列和共享内存提供了进程间传递数据的方法,信号和信号量则用于触发其他进程的行为。


二、详解

(一) 管道

管道允许在进程之间按先进先出的方式传送数据,它提供进程之间单向通信的方法,即管道是连接一个进程的输出至另一个进程的输入的方法。

管道最常见的是在命令行中:

$cat file | grep "pipe" | more 
  • " | " 表示使用了管道。在上面命令行中,cat、grep和more三个命令之间使用了两个管道,分别使得cat的标准输出成为grep的标准输入,grep的标准输出成为more的标准输入。
  1. 管道特点

  • 管道是半双工的,数据只能向一个方向流动;如果需要用管道进行两个方向的通信,则需要同时创建两个管道,一个用于接收数据,一个用于发送数据。
  • 匿名管道只能用于父子进程或者兄弟进程之间(具有父子关系的进程) 。
  • 管道没有名字,它是为了一次使用而创建的。
  • 管道的两个描述字是同时打开的
  1. 创建管道

创建管道需调用pipe()函数。pipe()的唯一参数是一个由两个整数组成的数组,该数组在pipe()调用成功后将含有作为管道使用的两个文件描述字,其中一个作为管道的输入,一个作为管道的输出。

#include<unistd.h>
int pipe(int fdes[2]);
  • 当进程调用pipe()成功后,内核在系统内部创建一条管道,并设置由该管道使用的两个已打开文件描述字于数组fdes。
  • fdes[0]为读而打开,是与输入端相连的文件描述字。
  • fdes[1]为写而打开,是与输出端相连的文件描述字。
  • pipe()调用成功后返回0,否则返回-1并置errno错误条件。
  1. 父子进程间的管道通信

  • 当进程调用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特别文件类似于管道,它也是半双工方式,且数据也按先进先出顺序传送。它允许非父子关系的进程交换数据。

  1. FIFO特别文件与管道的不同:

  • FIFO作为特别文件存在于文件系统中。
  • 不同祖先的进程可以通过FIFO特别文件共享数据。
  • 当共享进程完成了所有I/O操作后,除非用unlink()删除它,否则FIFO特别文件将保存在文件系统中,并且可以留待下一次使用。
  1. 创建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,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值