进程间通信方式

进程间通信方式

       管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常指父子进程关系。

       命名管道(FIFO):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

       消息队列(message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递的信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

       共享内存(share memory):共享内存就是映射一段能被其它进程所访问的内存,这段内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。

       信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

       套接字(socket):是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

       信号(signal):是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

1.1 信号

       信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程,而无需知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给它为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

       Linux提供了几十种信号,分别代表着不同的意义。信号之间依靠它们的值来区分,但是通常在程序中使用信号的名字来表示一个信号。在Linux系统中,这些信号和以它们的名称命名的常量被定义在/usr/include/bitssignum.h文件中。通常程序中直接包含<signal.h>就好。

       信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以在用户空间进程和内核之间交互。内核也可以利用信号来通知用户空间的进程来通知用户空间发生了哪些系统事件。信号事件有两个来源:

  1. 硬件来源,例如按下了ctrl+C,通常产生中断信号SIGINT
  2. 软件来源,例如使用系统调用或者命令发出信号。最常用的发送信号的系统函数是kill,raise,setitimer,sigation,sigqueue函数。软件来源还包括一些非法运算等操作。

一旦有信号产生,用户进程对信号产生的响应有三种方式:

  1. 执行默认操作,Linux对每种信号都规定了默认操作。
  2. 捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
  3. 忽略信号,当不希望接收到的信号对进程的执行产生影响,而让进程继续执行时,可以忽略该信号,即不对信号进程作任何处理。

有两个信号是应用进程无法捕捉和忽略的,即SIGKILL和SIGSTOP,这是为了使系统管理员能在任何时候中断或结束某一特定的进程。

 

1.2 管道

       管道允许在进程之间按先进先出的方式传送数据,是进程间通信的一种常见方式。管道是Linux支持的最初Unix IPC形式之一,具有如下特点:

  1. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  2. 匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  3. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。

管道分为pipe(无名管道)和FIFO(命名管道)两种,除了建立、打开、删除的方式不同外,这两种管道几乎是一样的。它们都是通过内核缓冲区实现数据传输。

pipe用于相关进程之间的通信,例如父进程和子进程,它通过pipe()系统调用来创建并打开,当最后一个使用它的进程关闭对它的引用时,pipe将自动撤销。

       FIFO即命名管道,在磁盘上有对应的节点,但没有数据块。换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建立,任何进程都可以通过文件将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

       管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后在缓冲区就不复存在了。当缓冲区空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或满的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。

 

1.2.1 pipe(匿名管道)

 

1.2.2 FIFO(命名管道)

       和pipe的主要区别在于,FIFO有一个名字,FIFO的名字对应于一个磁盘索引节点,有了这个文件名,任何进程有相应的权限都可以对它进行访问。而pipe却不同,进程只能访问自己或祖先创建的管道,而不能任意访问已经存在的管道---因为没有名字。

       Linux中通过系统调用mknod()或makefifo()来创建一个FIFO。最简单的方式是通过直接使用shell:

       mkfifio myfifo等价于manod myfifo p

       以上命令在当前目录下创建了一个名为myfifo的命名管道。用ls –p命令查看文件的类型时,可以看到命名管道对应的文件名后有一条竖线“|”,表示该文件不是普通文件而是命名管道。

       使用open()函数通过文件名可以打开已经创建的命名管道,而无名管道不能由open来打开。当一个命名管道不再被任何进程打开时,它没有消失,还可以再次被打开,就像打开一个磁盘文件一样。可以用删除普通文件的方法将其删除,实际删除的是磁盘上对应的节点信息。

 

 

 

 

1.3 消息队列

       消息队列,就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。

       进程间通过消息队列通信,主要是:创建或打开消息队列,添加消息和控制消息队列。

 

1.4 共享内存

       共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取操作读出,从而实现了进程间的通信。

       采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。

 

       一般而言,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时在重新建立共享内存区域;而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件,因此,采用共享内存的通信方式效率非常高。

 

       共享内存有两种实现方式:内存映射、共享内存机制。

1 内存映射

       内存映射memory map机制使进程之间通过映射同一个普通文件实现共享内存,通过mmap()系统调用实现。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read/write等文件操作函数。

 

 

2 UNIX System V共享内存机制

       IPC的共享内存指的是把所有的共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。

       和前面的mmap系统调用通过映射一个普通文件实现共享内存不同,UNIX system V共享内存是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值