Linux进程之进程间通信

一 进程间通信概述

一个大型的应用软件往往需要众多进程协作,所以进程间通信(IPC)的重要性显而易见。Linux系统下的进程间通信机制基本上是从UNIX平台上的进程间通信机制移植而来的。

Linux环境下,各个进程的地址空间相互独立,每个进程各自拥有不同的地址空间。任何一个进程的全局变量在另一个进程中都是不可见的,所以进程和进程之间不能相互访问,要想交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

在进程间完成数据交换需要借助操作系统提供的特殊方式。主要的进程间通信机制有以下几种。

  • 管道(Pipe):也称为匿名管道,是一种半双工的通信方式,数据只能单向流动,而且只能在具有父子关系的进程间通信。管道一般用于两个父子进程间的通信,当一个进程创建一个管道,并调用fork()系统调用创建一个子进程后,父进程关闭读端,子进程关闭写端,就形成了两个进程之间数据流动的一种方式。
  • 命名管道(Named Pipe):或称为先进先出队列(FIFO)。命名管道也是一种半双工的通信方式,它允许无父子关系的两个进程进行通信。命名管道类似于一种先进先出的队列(FIFO)。
  • 信号(Signal):进程间高级的通信方式,用于通知其他进程有何种事件发生。信号可以在任意时刻发给某一进程,而无需知道该进程的状态。进程可以向自身发送信号,还可以获得Linux内核发出的信号。Linux支持UNIX系统早期信号处理函数signal(),并从BSD引入了信号处理函数 sigaction()。sigaction()函数不仅提供了更为便利的通信机制,并保持了接口的统一,已替代 signal()函数。
  • 消息队列(Message Queue):消息队列是消息的链表,存储在内核中,由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点。具有写权限的进程可以按照一定的规则向消息队列中添加新数据;对消息队列有读权限的进程则可以从消息队列中读取数据。消息队列是UNIX/Linux下不同进程之间可实现共享资源的一种机制,允许进程将格式化的数据流以消息队列的形式发送给其他任意进程。UNIX/Linux下使用msgget() 来完成对消息队列的操作控制。目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列。
  • 信号量(semaphore):信号量是一个计数器,用于为多个进程/线程提供对共享数据的访问。它不是用于交换大批数据,而用于多线程之间的同步,它常作为一个锁机制,防止某进程在访问资源时其他进程访问该共享资源,因此,主要作为进程间以及同一个进程内不同线程之间的同步手段。信号量的实现有:System V信号量、POSIX信号量。
  • 共享内存(share memory):共享内存就是映射一段能被多个进程都能访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。由于数据不需要在进程之间复制,因此它是最快的IPC方式,它往往需要与其他进程间通信机制配合使用,如信号量,来实现进程间的同步和通信。当多个进程同步访问一个共享存储区时,若服务器进程正在将数据放入存储区,则在它做完这一操作之前,客户进程不应当去取这些数据。通常,信号量用于同步共享内存的访问。需要注意的是,使用共享内存作为进程间通信方式时,需要考虑同步问题。共享内存的实现有:POSIX共享内存、System V共享内存。
  • 套接字(socket):套接字也是一种进程间的通信机制,与其他进程间通信机制不同的是,它既可以用于同一机器上的进程间通信,也可以跨网络通信,也就说它可以用于不同机器上的进程间通信。

二 7种IPC的特点及使用场景

 2.1 管道(匿名管道)

概念:在内核中申请一块固定大小的缓冲区,进程拥有写入和读取的权限,一般使用fork()系统调用实现父子进程间的通信。

管道的特点:

(1)管道是半双工通信,即数据只能单向流动。但是可以通过同时使用2个管道实现双向通信。

(2)管道的生命周期随进程,进程消亡,管道也就消失。

(3)管道的通信是面向字节流的,即传送的是无格式的数据。这就要求管道的读取方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令,或记录)等。

(4)管道自带同步机制,保证读写顺序一致。

管道的局限性:管道的局限性正体现在它的特点上,只支持单向数据流;只能用于具有血缘关系的进程之间;没有名字;管道的缓冲区大小是固定的;管道传送的是无格式字节流。

匿名管道阻塞问题:无名管道无需显式打开,创建时直接返回文件描述符,在读写时需要确定对方进程的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程存在。如果写入无名管道的数据量超过其最大值,写操作将阻塞;如果无名管道中没有数据,读操作将阻塞,如果无名管道发现另一端已断开,将自动退出。

2.2 命名管道(FIFO)

概念:在内核中申请一块固定大小的缓冲区,进程拥有写入和读取的权限,没有血缘关系的进程也可以实现进程间的通信。有名管道的出现主要是为了克服匿名管道只能用于亲缘关系的进程间通信的缺点。

区别:有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信。因此,通过有名管道不相关的进程也能交换数据。有名管道的名字存在于文件系统中,数据存放在内存中。

特点

(1)和匿名管道一样,FIFO仅提供半双工的数据通信,即只支持单向的数据流;

(2)和管道不同的是,FIFO可以支持任意两个进程间的通信。

有名管道的使用场景:shell命令使用FIFO将数据从一条管道传送到另一条管道时,无须创建中间的临时文件;客户进程-服务器进程应用程序中,FIFO作为汇聚点,在客户进程和服务器进程之间传递数据。

2.3 信号(signal)

概念:信号是UNIX/Linux系统中用于进程间通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。

如果进程并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给它为止。如果一个信号被进程设置为阻塞,则该信号的传递将被延迟,直到其阻塞被取消时才被传递给进程。

Linux系统提供了几十种信号,每一种信号分别代表不同的含义。可以使用 kill -l 命令查看Linux下所有的信号种类。每一个信号都用一个值来表示,但是通常在程序中是使用信号的宏名来表示一个信号。

信号机制是通过软中断的方式实现的。

信号使用场景:在IPC中,用于某个事件发生时对进程进行通知的场景。

2.4 消息队列

概念:消息队列,是在内核中创建的一个消息的链表,队列中每一个元素就是一个数据块,每个消息队列由消息队列标识符标识。用户进程可以向消息队列添加消息,也可以从消息队列读取消息。

特点

(1)消息队列是消息的链表,具有特定的数据格式,存放在内存中并由消息队列标识符标识。

(2)消息队列是全双工通信,即可以双向通信。

(3)消息队列允许一个或多个进程写入或者读取消息。

(4)消息队列的生命周期随内核。

(5)内置同步与互斥机制。

消息队列与管道的异同

1、相同点:管道和消息队列的通信方式,数据都是先进先出(FIFO)的原则。

2、不同点:

(1)当进程往一个消息队列写入消息之前,并不需要另外某个进程在该消息队列上等待消息的到达。

(2)与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即重启操作系统)或者显式地删除一个消息队列时,该消息队列才会真正的被删除。简单点说,就是管道的生命周期随进程,而消息队列的生命周期随内核。

(3)消息队列可以实现多对多,而管道只能实现一对一。

消息队列的优势

(1)消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

(2)每个消息可以指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。

(3)消息队列允许任意进程通过共享队列来进行进程间通信,并由系统调用函数来实现消息发送和接收之间的同步,从而使得用户在使用消息队列进行通信时不需要考虑同步问题,使用相对方便。

消息队列使用场景:在多对多进程间通信和需要全双工通信的情形下使用。需要注意的是,消息队列中数据的复制需要耗费CPU时间,不适宜数据量大或频繁操作的场合。

2.5 信号量

概念:信号量是一个计数器,一般用来控制多个进程/线程对共享资源的访问。信号量的使用主要是用来保护共享资源,使得共享资源在某个时刻只有一个进程(线程)所拥有。

为了获得共享资源,进程需要执行下列操作:

(0)创建一个信号量:这要求调用者指定初始值,对于二值信号量来说,它通常是1,也可是0;

(1)测试控制某个临界资源的信号量的值;

(2)若此信号量的值为正,则进程可以使用该资源。这时,进程会将信号量值减1,表示它使用了一个资源单位;

(3)若信号量的值为零,则进程进入睡眠状态,直到信号量的值大于0,睡眠状态下的进程被唤醒,返回到步骤(1)。

信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和 V(sv),sv为信号量,它们的行为如下:

P(sv):如果sv的值大于0,就对其减1;如果它的值为0,就挂起该进程的执行。

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行;如果没有进程因等待sv而被挂起,就给它加1。

举例说明:两个进程A和B共享信号量sv,一旦进程A执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1;而进程B将被阻止进入临界区,因为当它试图执行P(sv)时,sv这时候为0,它就会被挂起直到进程A离开临界区并执行V(sv)释放信号量后,这时进程B会被唤醒,并恢复执行状态。

PV操作用于同一进程,实现互斥;PV操作用于不同进程,实现同步。

为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。

信号量与普通整型变量的区别

(1) 信号量是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap),signal(semap) 来进行访问;

(2)操作也被成为PV原语(P来源于荷兰语proberen “测试”,V来源于荷兰语verhogen “增加”,P表示通过的意思,V表示释放的意思),而普通整型变量则可以在任何语句块中被访问。

信号量与互斥量之间的区别

(1) 互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一临界资源同时只允许一个访问者对其进行访问,具有唯一性和排它性,但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对共享资源的有序访问。

在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问共享资源。

(2) 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

(3) 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

信号量使用场景:多进程/多线程对临界资源的访问。它本身不具备数据交换的功能,而是通过保护临界资源来实现进程间通信。信号量在此过程中负责数据操作的互斥、同步功能。

几个重要的概念的初步认识:

临界资源:一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

临界区:临界区内的数据一次只能同时被一个进程使用,当一个进程使用临界区内的数据时,其他需要使用临界区数据的进程进入挂起状态。

互斥:指某一资源同时只允许一个访问者对其进行访问。

原子性:一个事务包含多个操作,这些操作要么全部执行,要么都不执行。

同步:基本都是以互斥为条件,让不同的进程访问临界资源,以某种特定的顺序去访问。

2.6 共享内存

概念:允许两个或多个进程共享物理内存的同一块区域(通常被称为段,即共享内存段)。由于多个进程可以直接读写这个共享内存段,不需要借助内核缓冲区进行数据复制过程,因此这种IPC方式是速度最快的。另一方面,由于多个进程共享同一块内存区域,因此需要依靠某种同步机制来达到进程间的同步及互斥。例如,两个进程同时执行写操作或者一个进程在从共享内存中读数据的同时另一个进程正在进行写操作,这显然是不能允许的。一般使用信号量机制来同步多个进程对共享内存的访问。如,System V信号量、POSIX信号量。

共享内存通信原理

在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Address Space),并且都有一个与之对应的页表,负责将进程的虚拟地址(也叫逻辑地址)与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理内存的同一块区域,它们所指向的这块物理内存区域就是共享内存区域。

共享内存的通信原理示意图:

共享内存通信原理示意图
共享内存通信示意图

分析如下:

1、进程A和B都有一个属于自己的进程控制块和虚拟地址空间,PCB是存放在进程地址空间中的。将进程的虚拟地址空间通过页表映射到物理内存中,也就是进程的物理地址空间,图中红色连线部分。

2、在物理内存上开辟了一块内存空间,称为共享内存。

3、进程A和B通过返回一个虚拟地址将这块共享内存映射到自己的地址空间中。

4、进程A和B以各自地址空间的虚拟地址通过页表找到共享内存,然后向共享内存中读写数据实现进程间的通信。

共享内存的特点

1、共享内存无须进行复制操作,是所有IPC中速度最快的一种方式。

2、共享内存没有自带同步或互斥机制,需要用户自己来提供同步机制,这也是其缺点。

3、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

共享内存适用场景:当两个进程间需要进行大量数据的交换时,适合使用共享内存方式。

2.7 网络IPC:Socket

概念:socket也是一种IPC机制。它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的进程之间交换数据。

套接字对的组成=(本地IP地址:本地端口号)+(远程IP地址:远程端口号)

socket使用场景:主要应用于计算机网络通信领域。例如,客户端/服务器场景中,应用程序使用socket进行通信,以TCP网络通信架构为例来说明:

TCP网络通信模型
Socket是应用层与传输层之间的桥梁

分析说明:

1、TCP客户端和TCP服务器端假设是两个不同主机上的应用程序,它们各自创建一个socket对象,每一个socket对象在其本地都有一个唯一的socket号,由操作系统分配。

2、当TCP连接建立成功后,两个应用程序就可以通过读写操作来交换数据,准确地说,应该是两个进程之间进行数据交换。

3、当客户端与服务器端的数据交互完毕后,通过系统调用close()关闭socket连接,释放socket对象,通信结束。

参考

《Unix环境高级编程(第3版)》

《Linux_Unix系统编程手册(下)》

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值