通过共享内存进行 IPC

2025 年 7 月 18 日 | 9 分钟阅读

共享内存是两个或多个进程之间共享的内存。每个进程都有自己的地址空间;如果任何进程想通过其自己的地址空间与其它进程通信,那么只能通过 IPC(进程间通信)技术来实现。

共享内存是最快的进程间通信机制。操作系统将一个内存段映射到多个进程的地址空间,以便在不调用 操作系统 函数的情况下读取和写入该内存段。

IPC through Shared Memory

对于需要交换大量数据的应用程序,共享内存远优于消息传递技术,如消息队列,后者需要为每次数据交换调用系统调用。要使用共享内存,我们必须执行两个基本步骤:

  1. 向操作系统请求一个可以共享的内存段。
  2. 将该内存的一部分或全部与调用进程的地址空间相关联。

共享内存段是多个进程共享的物理内存的一部分。在此区域,进程可以设置结构,其他进程可以读取/写入它们。当在两个或多个进程中建立共享内存区域时,不能保证区域会放置在相同的基地址。如果需要同步,可以使用信号量。

IPC through Shared Memory

例如,一个进程的共享区域可能从地址 0x60000 开始,而另一个进程使用 0x70000。理解这两者指的是完全相同的数据至关重要。因此,在第一个进程的地址 0x60000 中存储数字 1 意味着第二个进程在 0x70000 处的 [值] 为 1。这两个不同的地址指向完全相同的位置。

为什么选择通过共享内存进行 IPC

通常,相关进程间的通信是通过管道或命名管道进行的。而无关进程间的通信可以通过命名管道或通过共享内存和消息队列等流行的 IPC 技术进行。

但是,管道、FIFO 和消息队列的问题在于,两个进程之间的信息交换要通过内核,并且工作方式如下:

  • 服务器从输入文件中读取。
  • 服务器使用管道、FIFO 或消息队列以消息形式写入此数据。
  • 客户端从 IPC 通道读取数据,这再次需要将数据从内核的 IPC 缓冲区复制到客户端的缓冲区。
  • 最后,数据从客户端缓冲区复制。

总共需要四次数据复制(2 次读,2 次写)。因此,共享内存通过让两个或多个进程共享一个内存段来提供一种方式。使用共享内存,数据只复制两次,即从输入文件复制到共享内存,以及从共享内存复制到输出文件。

共享内存 IPC 的功能

使用共享内存进行 IPC 需要两个函数:shmget()shmat()。shmget() 函数用于创建共享内存段,而 shmat() 函数用于将共享段附加到进程的地址空间。

1. shmget() 函数

第一个参数指定标识共享段的唯一数字(称为键)。第二个参数是共享段的大小,例如 1024 字节或 2048 字节。第三个参数指定共享段的权限。成功时,shmget() 函数返回一个有效的标识符;失败时,则返回 -1。

语法

2. shmat() 函数

shmat() 函数用于将由 shmid 指定的共享内存标识符所关联的已创建的共享内存段附加到调用进程的地址空间。这里的第一个参数是 shmget() 函数成功时返回的标识符。第二个参数是要将其附加到调用进程的地址。第二个参数为 NULL 表示系统将自动选择一个合适的地址。第三个参数为 '0',如果第二个参数为 NULL。否则,该值为 SHM_RND 指定。

语法

共享内存 IPC 如何工作?

一个进程使用 shmget() 创建一个共享内存段。共享内存段的原始所有者可以使用 shmctl() 将所有权分配给另一个用户。它也可以撤销此分配。其他具有适当权限的进程可以使用 shmctl() 对共享内存段执行各种控制功能。

创建后,可以使用 shmat() 将共享段附加到进程地址空间。可以使用 shmdt() 将其分离。附加进程必须具有 shmat() 的适当权限。附加后,进程可以读取或写入该段,具体取决于附加操作中请求的权限。同一进程可以多次附加共享段。

共享内存段由一个具有唯一 ID 的控制结构描述,该 ID 指向物理内存的区域。段的标识符称为 shmid。共享内存段控制结构和原型定义可以在 <sys/shm.h> 中找到。

示例

我们将以两个程序为例,演示如何使用共享内存进行 IPC。程序 1 将创建共享段,将其附加,然后写入一些内容。然后程序 2 将把自己附加到共享段并读取程序 1 写入的值。

程序 1: 此程序创建一个共享内存段,将自己附加到它,然后将一些内容写入共享内存段。

输出

Key of shared memory is 0
Process attached at 0x7ffe040fb000
Enter some data to write to shared memory 
Hello World
You wrote: Hello World 

它是如何工作的?

在上面的程序中,shmget() 函数以键 2345、大小 1024 字节创建了一个段,并为所有用户提供了读写权限。它返回段的标识符,该标识符存储在 shmid 中。此标识符用于 shmat() 将共享段附加到进程的地址空间。

shmat() 中的 NULL 表示操作系统将自行在当前进程的合适地址处附加共享段。然后使用 read() 系统调用从用户读取一些数据,并最终使用 strcpy() 函数将其写入共享段。

程序 2: 此程序将自己附加到程序 1 中创建的共享内存段,并读取共享内存的内容。

输出

Key of shared memory is 0
Process attached at 0x7f76b4292000
Data read from shared memory is: Hello World 

它是如何工作的?

在此程序中,shmget() 生成与程序 1 中创建的段相同的标识符。请记住使用相同的键值。唯一的区别是,由于共享内存段已创建,因此不要编写 IPC_CREAT。接下来,shmat() 将共享段附加到当前进程。

之后,将从共享段打印数据。在输出中,您将看到在执行程序 1 时写入的相同数据。

共享内存 IPC 的优点

  • 高速度和高性能:直接访问共享内存区域使共享内存成为最快的 IPC 机制之一。因为它消除了在进程之间复制数据的需要,所以与管道或消息队列相比,它提高了吞吐量并降低了 CPU 开销。
  • 适合大数据传输:此方法特别适合传输大数据块。例如,在需要快速处理视频或图像帧的多媒体应用程序中,共享内存可以大大减少模块间通信所需的时间。
  • 降低系统调用开销:它比消息队列或套接字等机制更有效,因为一旦共享内存段被创建并映射到进程地址空间,就可以在不进行额外系统调用的情况下执行频繁的内存操作,如读写。
  • 灵活的数据交换:除了基本消息外,进程还可以共享数组、对象和缓冲区等复杂数据结构。对于必须保持共享状态或实时数据同步的系统,由于其灵活性,共享内存是推荐的选择。

共享内存 IPC 的缺点

  • 复杂的同步:共享内存没有内部系统来协调访问。为了避免使开发更困难的竞态条件,开发人员必须使用额外的同步工具,如互斥锁或信号量。
  • 数据损坏风险:当多个进程在没有正确同步的情况下写入同一内存位置时,可能会导致崩溃或数据不一致。内存损坏和死锁只是由于微小的编码错误而可能产生的两个严重问题。
  • 安全和访问管理问题:任何被正确识别和授权的进程都可以访问共享内存的某些部分。未经授权的进程可能会访问或修改未充分保护的私人信息,从而导致安全漏洞。
  • 资源管理相关问题:如果进程在没有从共享内存中分离的情况下崩溃或突然终止,可能会发生内存泄漏。必须使用系统工具或基于代码的安全措施进行彻底清理。
  • 可移植性和平台问题:不同的操作系统以不同的方式实现共享内存及其 API。当应用程序在平台之间迁移时,代码的可移植性会降低,并且可能需要进行一些重大更改。

共享内存的典型应用

实时运行的系统

在工业自动化、机器人和无人驾驶汽车等实时应用中,共享内存允许控制单元和传感器快速通信。实时决策需要高数据访问速度和最小延迟。

多媒体处理

处理高分辨率图像、音频或视频的应用程序,如流媒体服务或视频编辑软件,使用共享内存来传输帧或样本(例如,渲染器和解码器之间的高效通信)。

数据库

大型数据库中的缓存表、缓冲区池和共享索引通常存储在共享内存中。它使多个工作进程能够访问和更新同一内存区域,而无需昂贵的 I/O 操作。

并行计算和科学

在高性能计算 (HPC) 环境中,共享内存用于协调在同一节点上运行的并行进程之间的任务。大规模计算、数值分析和模拟都从中受益。

操作系统内部

操作系统本身可能会使用共享内存来帮助内核模块或内核与用户空间进程(如设备驱动程序或性能监视工具)之间进行通信。

常见问题

为什么共享内存被认为比其他 IPC 机制更快?

因为一旦配置好,进程就可以直接从内存段读写数据,所以共享内存被认为是速度最快的 IPC 技术。这消除了每次交互都需要系统调用的需要。通过这样做,避免了套接字、管道和消息队列中常见的开销——即在用户空间和内核空间之间复制数据。因此,共享内存非常适合高性能应用程序。

访问共享内存的进程如何防止冲突?

应用程序必须使用信号量、互斥锁和自旋锁等同步技术来防止冲突。这些工具一次只允许一个进程更改内存区域。不正确的同步可能导致竞态条件,从而导致数据损坏或行为不可预测。

共享内存是无关进程之间通信的可行方式吗?

只要它们具有适当的权限并可以访问共享内存标识符(如键或名称),无关进程就可以使用共享内存。因此,在多个程序(不仅仅是父子进程)需要协同处理同一数据的情况下,可以使用它。

如果一个进程在使用共享内存时崩溃,会发生什么?

如果一个进程在未释放锁或分离共享内存的情况下崩溃或突然终止,该段可能会保留在内存中并导致泄漏。更糟糕的是,如果未释放锁,其他进程可能会停止。开发人员应确保共享内存得到清理,并使用 ipcrm(在 Unix 上)等工具或使用控制函数以编程方式实现适当的信号处理。

共享内存安全吗?

不能保证共享内存是安全的。如果访问权限未得到妥善管理,未经授权的进程可能会读取或替换私人信息。为防止安全风险,开发人员必须建立严格的访问控制,并确保共享段在使用后得到妥善擦除或保护。