了解内存映射
在系统上处理应用程序指令的速度与为获取程序可寻址内存之外的数据所需的访问操作的数量成正比。
系统提供了两种方法来减少与这些读写操作相关的处理开销。 可将文件数据映射到进程的地址空间中。 也可将进程映射到可能由协作进程共享的匿名内存区域中。
通过将文件数据合并入进程地址空间,内存映射文件提供了一种进程存取文件的机制。 使用映射文件可显著减少 I/O 数据移动,因为文件数据不必如 read 和 write 子例程所做的那样被复制到进程数据缓冲区中。 当超过一个进程映射相同的文件时,其内容将被这些进程共享,这提供了一个低开销的进程同步与通信机制。
已映射的内存内存区域,也称为共享内存区域,可作为在进程间交换数据的大池。 可用的子例程不提供进程间的锁定或访问控制。 因此,使用共享内存区域的进程必须设置信号控制方法以防止访问冲突,并确保进程不更改其他进程正在使用的数据。 在进程间的数据交换量太大以至于无法传送消息,或进程维护一共用的大型数据库时,共享内存区域带来的益处最大。
系统提供了两个映射文件和匿名内存区域的方法。 以下子例程都被认为是 shmat 服务,它们特别用于创建并使用来自程序的共享内存段:
| 子例程 | 定义 |
|---|---|
| shmctl | 控制共享内存操作 |
| shmget | 获取或创建共享内存段 |
| shmat | 连接来自进程的共享内存段。 不允许映射块设备。 |
| shmdt | 拆离来自进程的共享内存段 |
| mprotect | 修改对共享内存段中指定的地址范围的访问保护。 |
| 声明 | 除去来自共享内存段内指定的地址范围的映射 |
ftok 子例程提供 shmget 子例程用于创建共享段的键
第二个服务组都被认为是 mmap 服务,特别用于映射文件,虽然它还可能被用于创建共享内存段。
所有对由文件的 mmap() 产生的内存的有效操作对由块设备的 mmap() 产生的内存的也有效。 块设备是一个特殊文件,它提供对给出块接口的设备驱动程序的访问。 设备驱动程序的块接口需要大小固定的块中的数据访问。 该接口特别用于接口数据。
mmap 服务包括以下子例程:
| 子例程 | 定义 |
|---|---|
| 建议 | 将进程的预期调页行为通知系统 |
| mincore | 确定内存页的驻留 |
| mmap | 将对象文件映射到虚拟内存中。 允许一次一个进程地映射块设备。 |
| mprotect | 修改内存映射的访问保护 |
| msync | 使映射文件与其底层的存储设备同步 |
| munmap | 对已映射的内存区域取消映射 |
msem_init, msem_lock, msem_unlock, msem_remove, msleep, mwakeup 子例程为使用 mmap 服务映射的进程提供访问控制。
请参阅以下章节以了解更多有关内存映射的信息。
将 mmap 与 shmat 相比较
与和使用 shmat 服务一样,可用于使用 mmap 服务映射文件的进程地址空间部分与该进程是 32 位进程还是 64 位进程有关。 对于 32 位进程,可用于映射的地址空间部分由以下范围内的地址组成:0x30000000-0xCFFFFFFF,总共为 2.5G 字节的地址空间。 可用于映射文件的地址空间部分由范围内的地址组成0x30000000-0xCFFFFFFF和0xE0000000-0xEFFFFFFF总共 2.75G 字节的地址空间。 在AIX® 5.2及更高版本中,使用超大地址空间模型运行的 32 位进程可用于映射的范围为0x30000000-0xFFFFFFFF,地址空间总计高达3.25GB。
32 为进程地址空间内的所有可用范围均可用于固定位置和可变位置的映射。 在应用程序指定映射所在的地址空间的固定位置时,发生固定位置映射。 在应用程序指定由系统确定映射应该所在的位置时,发生可变位置映射。
对于 64 位过程,有两个进程地址空间的地址范围组可用于 mmap 或 shmat 映射。 第一个,由单个范围组成0x07000000_00000000-0x07FFFFFF_FFFFFFFF,可用于固定位置和可变位置映射。 第二组地址范围仅可用于固定位置映射,并由范围组成0x30000000-0xCFFFFFFF,0xE0000000-0xEFFFFFFF和0x10_00000000-0x06FFFFFF_FFFFFFFF. 此集合的最后一个范围,由以下各项组成:0x10_00000000-0x06FFFFFF_FFFFFFFF也可用于系统装入器以保存程序文本,数据和堆,因此只有范围中未使用的部分可用于固定位置映射。
mmap 和 shmat 服务均提供了将多个进程映射到同一对象区域的能力,这样它们就可共享对该对象的寻址能力。 然而,通过允许建立相对无限的映射数量,mmap 子例程将其能力延伸到 shmat 子例程所提供的能力之外。 在该能力提高每个文件对象或内存段所支持的映射数量的同时,它被证明对某些应用程序无效,这种应用程序中有许多进程将相同的文件数据映射到地址空间中。
mmap 子例程为每个映射到对象的进程提供了一个唯一的对象地址。 这是该软件通过为每个进程提供一个唯一的虚拟地址(即通常所说的别名)来完成的。 shmat 子例程允许进程共享已映射对象的地址。
因为任何时候对象中的给定页的现存别名里只有一个拥有实地址转换,所以只有一个 mmap 映射可在不导致缺页故障的情况下引用该页。 任何通过不同的映射(即不同的别名)对页的引用会导致缺页故障,该故障会造成该页的现存实地址转换无效。 结果,必须为其在不同的别名下建立新的转换。 进程通过在这些不同的转换中移动它们来共享页。
对于其中许多进程将相同的文件数据映射到其地址空间中的应用程序,该切换进程可能对性能有负面影响。 在这些情况下,shmat 子例程可能提供更有效的文件映射能力。
在以下情况下,请使用 shmat 服务:
- 对于 32 位应用程序,同时映射了 11 个或更少的文件,且每个小于 256 MB。
- 映射大于 256 MB 的文件时。
- 映射需要在无关(没有父子关系)进程间共享的共享内存区域时。
- 映射整个文件时。
在以下情况下,请使用 mmap:
- 要考虑应用程序的可移植性。
- 同时映射许多文件。
- 只需要映射文件的一部分。
- 需要在映射时设置页级别的保护。
- 需要专用映射。
“扩展的 shmat”能力可用于地址空间有限的 32 位应用程序。 如果定义了环境变量 EXTSHM=ON,那么在那个环境中执行的进程可以创建并连接多于 11 个的共享内存段。 进程可将这些段连接到地址空间中,以获得段的大小。 其他段可在相同的 256 MB 区域中被连接到第一个段的末尾。 进程可在其上连接的地址位于页边界,它是 SHMLBA_EXTSHM 字节的倍数。
对于使用 shmat 功能部件有一些限制。 这些共享内存区域不能被作为 I/O 缓冲区(其中缓冲区固定的取消发生在中断处理程序内)使用。 使用扩展的 shmat I/O 缓冲区的限制与 mmap 缓冲区的相同。
该环境变量为应用程序执行选项提供了能在 EXTSHM=ON 时连接超过 11 个段的附加功能,或提供在没有设置该环境变量时对 11 个或更少的段的性能更高的访问。 此外,“扩展的 shmat”能力只能应用于 32 位进程。
mmap 兼容性注意事项
mmap 服务由不同的标准指定,且通常用作其他操作系统实现中选项的文件映射接口。 然而,mmap 子例程的系统实现可能与其他实现不同。 mmap 子例程包括以下改进:
- 不支持映射到进程的专用区域。
- 映射不被隐式取消。 如果映射已经在所指定的范围中存在,指定 MAP_FIXED 的 mmap 操作将失败。
- 对于专用映射,写入时复制语义在第一次写入引用上创建页的复制。
- 不支持 I/O 或设备内存的映射。
- 不支持字符设备或将 mmap 区域用作对字符设备进行读写操作的缓冲区的映射。
- madvise 子例程只是为了兼容性而提供的。 系统不按照该所指定的建议采取任何操作。
- mprotect 子例程允许指定的区域包含未映射的页。 在操作中,未映射的页被简单地跳过。
- 不支持缺省精确映射和 MAP_INHERIT、MAP_HASSEMAPHORE 以及 MAP_UNALIGNED 标记的特定于 OSF/AES 的选项。
使用信号量子例程
msem_init, msem_lock, msem_unlock, msem_remove msleep 和 mwakeup 子例程符合 OSF Application Environment 规范。 它们提供了 IPC 接口 (例如 semget 和 se拖把 子例程) 的替代方法。 使用该信号的好处包括高效的串行化方法和由于不必在没有信号争用的情况下建立系统调用而减少了的开销。
信号可在共享内存区域中找到。 信号由 msemaphore 结构指定。 msemaphore 结构中的所有值应该由 msem_init 子例程调用得出。 跟在该调用后可能是 msem_lock 子例程或 msem_unlock 子例程的调用序列,也可能不是。 如果 msemaphore 结构值以其他方式产生,信号子例程的结果不确定。
msemaphore 结构的地址为有效数字。 应该注意不要修改该结构的地址。 如果该结构包含复制自其他地址上的 msemaphore 结构的值,该信号子例程的结果将是不确定的。
在信号结构存在于由 mmap 创建的匿名内存存储器中时,可能会证实信号子例程没有那么高效,特别是在许多进程引用相同的信号时。 在这种情况下,应该在由 shmget 和 shmat 子例程创建的共享内存区域以外分配信号结构。
使用 shmat 子例程来映射文件
映射可用于减少涉及文件内容读写的开销。 一旦文件内容被映射到用户内存区域,可像内存中的数据一样使用该数据的指针而不是输入/输出调用操作该文件。 磁盘上该文件的副本也可用作该文件的页面调度区域来保存调页空间。
程序可将任何常规文件用作映射数据文件。 还可将映射数据文件的功能扩展到包含已编译和可执行对象代码的文件。 因为访问映射文件比访问常规文件更快,所以如果程序的可执行对象文件已映射到一个文件,系统就可以更快地装入该程序。
要创建作为已映射可执行文件的程序,请使用 cc 或 ld 命令的 -K 标记编译并链接该程序。 -K 标记通知链接程序创建页对齐格式的对象文件。 就是说,对象文件的每个部件在页界(可被 2 KB 整除的地址)启动。 该选项会导致对象文件中某些空的空间,但允许可执行文件被映射到内存。 系统将对象文件映射到内存时,对文本和数据部分进行不同的处理。
写时复制映射文件
要阻止对映射文件的更改立即出现在磁盘上的文件中,请将文件映射为写入时复制文件。 该选项创建更改保存在系统页空间中,而不是磁盘上的文件副本上的映射文件。 必须选择将这些更改写入磁盘上的副本以保存这些更改。 否则在关闭文件时会丢失这些更改。
因为更改不是立即反映在其他用户可能访问的文件的副本中,使用写入时复制映射文件。
系统不检测用 shmat 子例程映射的文件末尾。 因此,如果程序通过存储入相应的内存段(映射文件的地方)以在写入时复制映射文件中写入超出文件当前的末尾,那么用由零组成的块来扩展磁盘上的实际文件,以此为新的数据好准备。 如果程序在关闭文件之前不使用 fsync 子例程,那么在文件的前一个末尾以外写入的数据不会写入磁盘。 文件看起来更大了,但只包含所添加的零。 因此,在关闭写入时复制映射文件以保留任何添加或更改的数据之前,请总是使用 fsync 子例程。
使用 shmat 子例程来映射共享内存段
系统使用与其创建和使用文件相似的方法来使用共享内存段。 以更熟悉的文件系统术语来定义用于共享内存的术语对了解共享内存是非常关键的。 共享内存术语的定义列表如下:
| 术语 | 定义 |
|---|---|
| key | 特定的共享段的唯一标识。 只要该共享段存在它就与该共享段相关联。 在这点上,它与文件的文件名相似。 |
| shmid | 为了在特定进程中使用而分配给共享段的标识。 它在使用上与文件的文件描述符相似。 |
| 连接 | 指定了进程必须连接一个共享段以使用它。 连接共享段与打开文件相似。 |
| detach | 指定了一旦进程完成了对共享段的使用,必须将其拆离。 拆离共享段与关闭文件相似。 |