进程之间的通信方式

进程间通信(IPC InterProcesss Communication)是指在不同进程之间传播或交换信息

IPC对象持续性分为三种:随进程,随内核,随文件系统

  • 随进程程序的IPC对象:一直存在到打开着IPC对象的最后一个进程关闭该对象为止,管道,FIFO进程
  • 随内核持续的IPC对象:一直存在到内核重新自举或者显示删除IPC对象为止,Posix新消息队列随内核
  • 随文件系统:一直存在到显示删除IPC对象为止

1、单工(Simplex)
单工通信只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。

2、半双工(Half Duplex)
半双工通信允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输。半双工通信实际上是一种可切换方向的单工通信。

3、全双工(Full Duplex)
全双工通信允许数据同时在两个方向上传输,即有两个信道,因此允许同时进行双向传输。全双工通信是两个单工通信方式的结合,要求收发双方都有独立的接收和发送能力。

进程之间的通信方式

管道pipe

PIPE

管道分为PIPE(无名管道)和FIFO(命名管道)俩种,除了简历打开,删除的方式不同外,俩种管道几乎是一样的。他们都是通过内核缓冲区实现数据传输

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

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

FIFO

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

  1. 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信
  2. FIFO可以在无关的进程之间交换数据,与无名管道不同

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

消息队列 message queue

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

  1. 消息队列是面向记录的,其中消息具有特定的格式以及特定的优先级
  2. 消息队列独立于发送与接受进程。进程终止时,消息队列以及内容并不会被删除
  3. 消息队列可以实现消息的随机查询,消息队列不一定要以先进先出的次序存取,也可以按照信息的类型读取

消息队列与管道通信相比,其优势是每个消息指定特定的消息类型。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息

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

共享存储 Shared Memory

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

  1. 贡献该内存是最快的一种IPC,因为进程是直接对内存机械能存取
  2. 因为多个进程可以同时操作,所以需要进行同步
  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问

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

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

共享内存由俩种实现方式

内存映射

mmap就是把内核空间和用户空间的地址映射到同一个物理地址,从而减少拷贝的次数

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

例子:创建子线程,父子进程通过匿名映射实现共享内存

分析:主程序中先调用mmap映射内存,然后调用fork函数创建进程。那么调用fork函数之后,子进程集成父类进程匿名映射后的地址空间,同样也继承mmap函数的返回地址

共享内存机制

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

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

例子:设计俩个程序,通过unix system v共享内存机制,一个程序写入共享区域,另一个程序读取共享区域

分析:一个程序调用fotk函数产生的标准key,接着调用shmget函数,获取共享内存区域的id,调用shmat函数,映射内存,循环计算年龄,另一个程序读取共享内存

信号量Semaphore

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

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
  2. 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
  3. 每次对信号量的PV操作不仅限于对信号量加1或减1,而且可以加减任意正整数
  4. 支持信号量组

套接字socket

socket,即套接字是一种通信机制,拼接这种机制,客户、服务端(即要通信的线程)系统的开发工作既可以在本地单机上运行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确低将客户端和服务器区分开来

套接字特性由三个属性确定,它们分别是:域,类型和协议

套接字的域

它指定套接字通信中使用的网络介质,最常见的套接字域是AF_INET,它指的是internet网络。当客户使用套接字进行跨网络的连接时,它就需要用服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用socket作为通信的终点,服务器应用程序必须在开始通信之间绑定一个端口,服务器在指定的端口等待客户端的连接,另一个域AF_UNUIX表示表示文件系统,它就是文件输入、输出,而它的地址就是文件名

套接字的类型

因特网提供了俩种通信机制流(stream)和数据报(datagram),因而套接字的类型也就分为流套接字和数据报套接字。这里主要讲流套接字

流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。

流套接字提供的是一个有序,可靠,双向字节流的连接,是因此发送的数据可以确保不会丢失,重复或者乱序到达,而且还有一定的出错后重新发送机制。

与流套接字相对的是由类型SOCK_DGRAM指定的数据报套接字,它不需要简历连接和维持一个连接,它们在AF_INET通常是通过UDP、IP协议实现的。它可以发送的数据长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失,复制,或错乱到达,UDP是一个不可靠的协议,但它速度比较高,因为它

套接字协议

只要底层的传输机制允许不止一个协议来听要求的套接字类型,我们就可以为套接字选择一个特定的协议。通常只需要使用默认值

信号sinal

信号是linux中向进程发送的消息,接收到该信息的进程会响应地采取一些行动,即通过软中断的方式来响应这个信号,出发一些实现指定或也定的事件,进程之间可以互相通过系统调用kill发送信号,内核也可以因为内部事件而给进程发送信号,通知进程发生了某件事件

信号产生

  1. 由硬件产生,如从键盘输入ctrl+C可以终止当前进程
  2. 由其他进程发送,如在shell进程下,使用命令 kill -信号标号 PID, 向指定进程发送信号
  3. 异常,进程异常时会发送信号

信号的处理

  1. 信号是由操作系统来处理的,说明信号的处理在内核态中
  2. 信号不一定会立即被处理,此时会存储在信号的信号表中

信号有三种处理方式

  1. 忽略
  2. 默认处理方式
  3. 自定义信号处理方式

Java如何支持进程间通信。我们把Java进程理解为JVM进程。很明显,传统的这些大部分技术是无法被我们的应用程序利用了(这些进程间通信都是靠系统调用来实现的)。但是Java也有很多方法可以进行进程间通信的。
除了上面提到的Socket之外,当然首选的IPC可以使用Rmi,或者Corba也可以。另外Java nio的MappedByteBuffer也可以通过内存映射文件来实现进程间通信(共享内存)。

Last modification:January 6, 2023
如果觉得我的文章对你有用,请随意赞赏