进程间为什么需要数据通信?
- 数据传输:一个进程需要将它的数据传送给另外一个进程。
- 资源共享:多个进程之间共享相同的资源。
- 通知事件:一个进程需要向另外一组进程发送消息,通知它们发生了某种事件。
- 进程控制:有些进程需要完全控制另一个进程的执行,该控制进程希望能够拦截另外一个进程的所有操作,并且能够及时知道它的状态改变。
主要有管道、消息队列、共享内存、信号量、信号、Socket等六种方式。
管道
管道的本质是内核中的一个缓存,当内存创建一个管道后,Linux会返回两个文件描述符号,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道里写入或者读取数据。如果想要实现两个进程通过管道来通信,则需要让创建管道的进程fork子进程,这样子进程就拥有了父进程的文件描述符,这样子进程之间可以对同一管道的操作。管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。
消息队列
消息队列的本质就是存放在内存中的消息链表,而消息本质上是用户自定义的数据结构。如果进程在消息队列种读取了某个消息,这个消息就会被从消息队列种删除。
- 消息队列允许一个或者多个进程向他写入或者读取数据。
- 消息队列可以不遵循FIFO,进行消息随机查询。
- 消息队列的生命周期与内核相同。
- 消息队列对于交换较少数量的数据很有用,因为无需避免冲突。因为用户进程往消息队列种读写数据,需要进行系统调用。如果数据量大会造成频繁的系统调用,因此需要更多的时间以便于内核介入。
共享内存
共享内存就是允许不相干的进程将同一段物理内存连接到它们各自的地址空间中,使得这些内存可以访问同一个物理空间,这个物理内存就成为共享内存。
- 两个不同进程的逻辑地址通过页表映射到物理空间的同一区域,它们所共同指向的这块区域就是共享内存。
- 消息队列无需避免冲突,而共享内存机制可能会发生冲突。
信号量
在多道批处理系统中,多个进程是可以并发执行的,但由于系统的资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进(异步性)。但有时候我们又希望多个进程能密切合作,按照某个特定的顺序依次执行,以实现一个共同的任务。
为了解决上述这两个问题,保证共享内存在任何时刻只有一个进程在访问(互斥),并且使得进程们能够按照某个特定顺序访问共享内存(同步),我们就可以使用进程的同步与互斥机制,常见的比如信号量与 PV 操作。进程的同步与互斥其实是一种对进程通信的保护机制,并不是用来传输进程之间真正通信的内容的,但是由于它们会传输信号量,所以也被纳入进程通信的范畴,称为低级通信。
- 信号量:信号量其实就是一个变量 ,我们可以用一个信号量来表示系统中某种资源的数量。例如系统连接的打印机资源的数量,可以设置一个初值为1的信号量。
- 原语:操作系统提供的一对原语来对信号量进行操作。
- P操作:
信号量--
,表示申请占用一个资源。如果申请结果小于0,则无资源可用,P操作的进程被阻塞。 - V操作:
信号量++
,表示释放一个资源。如果信号量+1<0
,表明有进程处于阻塞中等待资源,因此可以唤醒一个等待中的进程,使其运行下去。 - 可以这么理解,当信号量的值为 2 的时候,表示有 2 个资源可以使用,当信号量的值为 -2 的时候,表示有两个进程正在等待使用这个资源。
- P操作:
信号
通过发送指定信号来通知某个异步事件的发送,以迫使进程进行信号处理程序。信号处理完毕后,被中断的进程将恢复执行。
- 信号和信号量是完全不同的概念。
- 信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。
- 信号事件的来源主要有硬件来源和软件来源。
- 硬件来源,例如组合键Ctrl+C 产生 SIGINT 信号,表示终止该进程。
- 软件来源,通过 kill 系列的命令给进程发送信号,比如 kill -9 1111,给PID 1111进程发送SIGKILL信号。
Socket
通过Socket相关协议,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据,当然也能完成同主机上的进程通信。
- Socket本质上是一个API,是应用层与TCP/IP协议的中间软件抽象层,对TCP/IP进行了封装。