共享内存是多个处理器、线程或进程都能访问、用于交换数据的内存。在并行计算中,它指一种体系结构或编程模型,其中各执行主体使用共同的存储空间。在操作系统中,它也指将同一个内存对象映射到不同进程中的机制。这两种含义相互关联,但并不相同:共享存储空间使各方能够访问共同的数据,而同步则决定并发访问之间如何相互作用。(openmp.org)
体系结构与地址空间
共享内存计算机允许多个处理器访问共同的内存地址空间。底层存储不必位于同一物理位置。在非均匀内存访问(NUMA)系统中,内存按节点组织,访问开销取决于执行访问的处理器与数据所在节点之间的关系。本地访问通常无需节点间通信,而远程访问则需要经过系统互连。因此,逻辑上共享的内存仍可以在物理上分布于不同位置。(docs.kernel.org)
处理器通常会在CPU 缓存中保留数据副本。缓存一致性机制协调各个缓存中同一内存位置的副本。缓存一致性不应与内存一致性模型混淆,后者规定内存操作的顺序与可见性保证。共享内存编程模型可以允许每个线程维护一个临时的内存视图,而不要求每次访问都看到一个即时更新的全局状态。(cdn.kernel.org)
在进程间通信中,虚拟内存系统将一个共享对象映射到每个参与进程的地址空间。各映射可以位于不同的虚拟地址,同时指向同一个底层对象。因此,一个进程存储的指针在另一个进程中未必有意义。以相对于映射起始位置的偏移量为基础的数据格式,可以避免假定各进程使用相同的虚拟地址。(pubs.opengroup.org)
操作系统机制
共享内存是一种进程间通信(IPC)方式。一旦某个区域完成映射,应用程序就可以通过普通的内存操作访问其内容,而不必为每次传输单独调用通信接口。与交换离散消息的接口不同,内存映射直接提供对存储空间的访问;应用程序必须自行定义其中内容的布局、所有权和解释方式。(pubs.opengroup.org)
POSIX 接口将对象创建与映射分开。典型流程是使用 shm_open() 创建或打开一个具名对象,使用 ftruncate() 设置其大小,再使用带有 MAP_SHARED 标志的 mmap() 将其映射到地址空间。新创建的对象初始长度为零。能否读写取决于对象的权限和映射的保护设置。(pubs.opengroup.org)
对象的生命周期也独立于某个具体映射。移除 POSIX 共享内存对象的名称,并不会立即使已有引用失效;只有在对象的名称已解除关联且所有剩余引用都消失后,对象才会被销毁。这一区别使进程能够在名称移除后继续访问对象。规范并不保证对象在系统重启后仍然存在。(pubs.opengroup.org)
在 Microsoft Windows 中,进程可以通过具名文件映射对象,共享以分页文件为后备的存储空间。一个进程使用 CreateFileMapping() 创建对象,另一个进程使用 OpenFileMapping() 打开它。各进程都通过 MapViewOfFile() 获取本地视图。这与内存映射文件使用相同的映射抽象,但不要求共享数据存放在普通磁盘文件中。(learn.microsoft.com)
同步与正确性
共同访问并不意味着并发更新是安全的。当对同一存储位置的访问发生冲突、其中至少一次为写入,且这些访问缺少适用内存模型所要求的顺序约束时,就可能发生数据竞争。例如,两个工作线程可能读到相同的计数器值,各自递增后再写回,从而丢失一次更新。仅仅将计数器放入共享内存,并不能使这一操作序列成为不可分割的整体。(openmp.org)
同步机制用于协调访问并建立可见性保证。互斥锁提供对受保护区域的独占访问;信号量可以协调进程间的资源可用性或执行。原子操作使指定的访问或更新在其定义的作用范围内不可分割,而屏障则协调各参与工作线程或进程的执行进度。这些机制提供的保证各不相同,不能相互替代。POSIX 共享内存应用程序通常使用信号量来协调访问。(openmp.org)
OpenMP 为多种编程语言提供共享内存并行编程支持,包括 C、C++ 和 Fortran。其数据共享规则区分共享变量与私有副本。其同步构造和内存顺序规则决定线程如何通过共享变量通信;将变量声明为共享,本身并不能保护并发修改。(openmp.org)
性能特征
共享内存可以让多个参与者访问同一数据区域,而无需各自维护一份独立的有效载荷副本。不过,其性能取决于访问模式、同步方式和硬件局部性。在 NUMA 系统中,内存的放置位置以及工作线程或进程的迁移,会影响访问是在本地完成还是需要经过互连。因此,共同的地址空间并不意味着相同的访问开销。(pubs.opengroup.org)
当多个处理器访问逻辑上相互独立、却位于同一缓存行中的数据,并且至少有一个处理器执行写入时,就会发生伪共享。缓存一致性机制以缓存行为粒度工作,因此互不相关的字段也可能引发缓存失效和数据传输。所以,即使工作线程不修改同一个变量,数据结构的布局也会影响性能。将频繁修改的字段分开可以减少这种干扰,但填充也会增加存储需求。(cdn.kernel.org)
图形处理器上的共享内存
在图形处理器上,共享内存一词可以指一种作用范围更受限制的内存空间。在 CUDA 中,共享内存是由程序显式管理的片上暂存区,可供同一线程块内的线程访问。它比设备全局内存具有更低的延迟和更高的带宽,但容量有限。线程必须协调存在依赖关系的访问,通常通过 __syncthreads() 实现。这种面向线程块的资源不同于操作系统提供的进程间共享内存。(docs.nvidia.com)
参考来源
- Structure of the OpenMP Memory Modelopenmp.org
- What is NUMA? — The Linux Kernel documentationdocs.kernel.org
- False Sharing — The Linux Kernel documentationcdn.kernel.org
- mmappubs.opengroup.org
- shm_openpubs.opengroup.org
- Creating Named Shared Memory - Win32 appslearn.microsoft.com
- OpenMP Application Programming Interface Specification Version 6.0 November 2024openmp.org
- shm_overview(7) - Linux manual pagemichaelkerrisk.com
- Specifications - OpenMPopenmp.org
- OpenMP Examples: Memory Modelopenmp.org
- 3. Writing SIMT Kernels — CUDA Programming Guidedocs.nvidia.com
- 2. Programming Model — CUDA Programming Guidedocs.nvidia.com