前言

在上一篇文章中,我们探讨了进程间通信的三种常见机制:管道、消息队列和共享内存。我们了解到,这些机制各有其特点和适用场景,可以根据实际需求选择合适的机制进行进程间通信。然而,进程间通信并不仅限于这三种方式。

在本文中,我们将继续探索进程间通信的知识点,重点关注信号量、信号和套接字。信号量是一种用于进程同步的机制,它可以用于控制对共享资源的访问。信号是一种用于进程间通知的机制,可以用于处理异步事件。而套接字则是一种用于网络通信的接口,它可以实现不同主机之间的进程间通信。

信号量

共享内存通信方式虽然提供了高效的数据交换,但也引发了新的问题。如果多个进程同时修改同一个共享内存区域,很可能会导致数据冲突。举个例子,如果两个进程同时写入同一个地址,先写入的进程可能会发现自己的内容被后写入的进程覆盖。

在进程间共享资源时,使用信号量可以避免多个进程同时访问共享资源而导致数据冲突的问题。信号量是一个整型计数器,用来表示资源的可用数量。通过P操作和V操作来控制信号量的值。

  • P操作会将信号量减1,如果结果小于0,则表示资源已被占用,进程需要阻塞等待。如果结果大于等于0,则表示资源仍然可用,进程可以继续执行。
  • V操作会将信号量加1,如果结果小于等于0,则表示有其他进程正在等待资源,需要唤醒其中一个进程。如果结果大于0,则表示没有进程在等待资源。

通过使用P操作和V操作,可以实现对共享资源的互斥访问和同步执行。例如,可以初始化一个信号量为1,使得只有一个进程可以访问共享资源,从而避免数据错乱。另外,可以初始化一个信号量为0,使得进程按照特定的顺序执行,实现多进程的同步。
接下来,我们先看下互斥访问,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为 1。

具体的过程如下:

  • 进程 A 在访问共享内存之前,先执行了 P 操作。由于信号量的初始值为 1,所以进程 A 执行 P 操作后,信号量减为 0,表示共享资源可用,进程 A 可以访问共享内存。
  • 如果此时进程 B 也想访问共享内存,它执行了 P 操作。结果信号量变为 -1,表示临界资源已被占用,因此进程 B 被阻塞。
  • 直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0。接着,进程 A 唤醒被阻塞的进程 B,使其可以访问共享内存。
  • 最后,进程 B 完成共享内存的访问后,执行 V 操作,将信号量恢复到初始值 1。

将信号量初始化为 1,代表着它是一个互斥信号量。这种设置可以确保在任何时刻只有一个进程可以访问共享内存,从而有效保护了共享内存的完整性。有人可能会发现如果多线程都来访问资源全部阻塞了唤醒谁呢?这不就是我们之前讲到的进程调度算法了吗?进程阻塞后会进入阻塞队列,而唤醒哪个进程则由系统的调度算法决定。

在多进程环境中,每个进程并不一定按照顺序执行,它们以各自独立且不可预测的速度向前推进。然而,在某些情况下,我们希望多个进程能够密切合作,以实现一个共同的任务。

比如生产者消费者模式,假设进程A负责生产数据,而进程B负责读取数据,这两个进程是相互合作、相互依赖的。进程A必须先生产数据,然后进程B才能读取到数据,因此它们之间存在执行顺序。

接下来,我们来讨论同步执行。我们可以通过初始化信号量为0来实现。

具体过程如下:

  • 如果进程B比进程A先执行,那么当它执行P操作时,由于信号量的初始值为0,所以信号量会变为-1,表示进程A还没有生产数据,进程B会被阻塞等待。
  • 接着,当进程A生产完数据后,执行V操作,信号量会变为0,这会唤醒被阻塞在P操作的进程B。
  • 最后,进程B被唤醒后,意味着进程A已经生产了数据,进程B就可以正常读取数据了。

可以看出,将信号量初始化为0,代表着这是一个同步信号量,它可以保证进程A在进程B之前执行。

信号

信号是一种在异常情况下进行进程间通信的机制,它是一种异步通信方式,其数据结构一般为一个数字。

在Linux操作系统中,为了响应各种事件,提供了几十种信号,每个信号代表着不同的含义。可以通过运行”kill -l”命令来查看所有的信号列表。

对于在Shell终端中运行的进程,我们可以通过键盘输入某些组合键向进程发送信号。例如,按下Ctrl+C会产生SIGINT信号,表示终止该进程;按下Ctrl+Z会产生SIGTSTP信号,表示暂停该进程,但进程并未结束。需要注意的是,Ctrl+Z命令在某些情况下可能会导致内存飙升等问题(比如你看一个全量服务器日志),因此需要谨慎使用。

如果进程在后台运行,可以使用kill命令向进程发送信号,但前提是需要知道正在运行的进程的PID(进程ID)。例如,执行”kill -9 ###”命令会向PID为”###”的进程发送SIGKILL信号,用于立即终止该进程。

因此,信号的事件来源主要有硬件来源(如键盘的Ctrl+C)和软件来源(如kill命令)。信号是进程间通信机制中唯一的异步通信机制,进程需要为信号设置相应的监听处理程序,当收到特定信号时,执行相应的操作,类似于其他编程语言中的通知机制。

Socket

Socket通信是一种常用的进程间通信机制,可以用于跨网络与不同主机上的进程之间通信,也可以在同一台主机上的进程之间进行通信。

Socket通信是通过网络协议进行数据传输的一种方式。在使用Socket通信时,一个进程可以作为服务器端创建一个Socket,并指定一个IP地址和端口号来监听连接请求;另一个进程则可以作为客户端创建一个Socket,指定服务器的IP地址和端口号来发起连接。一旦连接建立,服务器和客户端就可以通过Socket进行数据的发送和接收。

在同一台主机上,进程可以使用特殊的IP地址(如本地回环地址127.0.0.1)和不同的端口号来建立Socket连接,实现进程间的通信。这种方式被称为本地回环通信,可以用于进程之间的协作和数据交换。

后期我将详细讲解计算机基础的网络篇,敬请期待!

总结

IPC 机制数据抽象参与者方向内核实现
管道字节流两个进程单向通常以 FIFO 的缓冲区来管理数据。
有匿名管道和命名管道两类主要实现
消息队列消息多进程单向
双向
队列的组织方式。
通过文件的权限来管理对队列的访间
信号量计数器多进程单向
双向
内核馀护共享计数器。
通过文件的权限来管理刘计数器的访问
共享内存内存区问多进程单向
双向
内核维护共享的内存区可。
通过文件的权限来管理对共享内存的访间
信号事件编号多进程单向为线程/进程维护信号等待队列。
通过用户了组等的权限来管理信号的操作
套接字数据报文两个进程单向
双向
有基于IP/端口和基于文件路轻的寻址方式。
利用网络栈来管理通信

进程间通信是操作系统中的重要概念,它允许不同的进程之间进行数据交换、消息传递和协作。在Linux系统中,提供了多种进程间通信的机制,包括管道、消息队列、共享内存、信号量、信号和套接字。每种通信机制都有不同的特点和适用场景。需要根据具体需求选择合适的方式。进程间通信涉及到资源的共享和竞争,需要合理地使用同步和互斥机制来保证数据的一致性和正确性。同时,进程的唤醒顺序也会受到系统的调度算法的影响。