无MMU内存映射支持

在无MMU条件下(例如uClinux环境中),内核对内存映射的支持是有限的。从用户空间的角度来看,内存映射与mmap()系统调用、shmat()调用和execve()系统调用一起使用。从内核的角度来看,execve()映射实际上由binfmt驱动程序执行,这些驱动程序会回调mmap()例程来完成实际工作。

内存映射行为还涉及fork()、vfork()、clone()和ptrace()的工作方式。在uClinux下没有fork(),而clone()必须提供CLONE_VM标志。

MMU和无MMU情况下的行为相似,但并不相同;在后者的情况下,其限制也更多

  1. 匿名映射,MAP_PRIVATE

    在MMU情况下:由任意页面支持的VM区域;跨fork进行写时复制。

    在无MMU情况下:由任意连续页面运行支持的VM区域。

  2. 匿名映射,MAP_SHARED

    它们与私有映射的行为非常相似,不同之处在于它们在MMU情况下跨fork()或clone()(不带CLONE_VM)共享。由于无MMU情况不支持这些,因此其行为与MAP_PRIVATE相同。

  3. 文件,MAP_PRIVATE,PROT_READ / PROT_EXEC,!PROT_WRITE

    在MMU情况下:由从文件读取的页面支持的VM区域;对底层文件的更改反映在映射中;跨fork复制。

    在无MMU情况下

    • 如果存在,内核将重用对同一文件的同一段的现有映射(如果其具有兼容的权限),即使此映射是由另一个进程创建的。

    • 如果可能,文件映射将直接在支持设备上进行,如果支持设备具有NOMMU_MAP_DIRECT功能和适当的映射保护功能。Ramfs、romfs、cramfs和mtd都可能允许这样做。

    • 如果支持设备不能或不允许直接共享,但确实具有NOMMU_MAP_COPY功能,那么文件的相应部分将被读取到一段连续的内存中,并且超出EOF的任何多余空间将被清除

    • 对文件的写入不影响映射;对映射的写入在其他进程中可见(没有MMU保护),但不应该发生。

  4. 文件,MAP_PRIVATE,PROT_READ / PROT_EXEC,PROT_WRITE

    在MMU情况下:类似于非PROT_WRITE情况,不同之处在于相关页面在实际写入发生之前被复制。从那时起,对该页面底层文件的写入不再反映到映射的后端页面中。该页面则由交换空间支持。

    在无MMU情况下:与非PROT_WRITE情况非常相似,但始终进行复制且从不共享。

  5. 常规文件/块设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE

    在MMU情况下:由从文件读取的页面支持的VM区域;对页面的更改写回文件;对文件的写入反映到支持映射的页面中;跨fork共享。

    在无MMU情况下:不支持。

  6. 内存支持的常规文件,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE

    在MMU情况下:与普通常规文件相同。

    在无MMU情况下:提供内存支持文件的文件系统(如ramfs或tmpfs)可能会选择通过提供连续的页面序列进行映射来遵守开放、截断、mmap序列。在这种情况下,共享可写内存映射将成为可能。其工作方式与MMU情况相同。如果文件系统不提供任何此类支持,则映射请求将被拒绝。

  7. 内存支持的块设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE

    在MMU情况下:与普通常规文件相同。

    在无MMU情况下:与内存支持的常规文件相同,但块设备必须能够提供连续的页面运行,而无需调用截断。如果ramdisk驱动程序预先分配所有内存作为连续数组,则可以实现这一点。

  8. 内存支持的字符设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE

    在MMU情况下:与普通常规文件相同。

    在无MMU情况下:如果字符设备驱动程序提供可以直接访问的内存或准内存,则可以选择通过提供对底层设备的直接访问来遵守mmap()。此类示例包括帧缓冲器和闪存设备。如果驱动程序不提供任何此类支持,则映射请求将被拒绝。

无MMU MMAP的进一步说明

  1. 对文件的私有映射请求可能会返回一个非页面对齐的缓冲区。这是因为可能发生XIP,并且数据在后端存储中可能未进行分页对齐。

  2. 匿名映射的请求将始终是页面对齐的。如果可能,请求的大小应该是2的幂,否则一些空间可能会被浪费,因为内核必须分配2的幂的粒度,但只有在适当配置时才会丢弃多余的部分,因为这会影响碎片化。

  3. 根据Linux手册页(2.22版或更高版本),匿名映射请求分配的内存通常会在返回之前由内核清除。

    在MMU情况下,这可以以合理的性能实现,因为区域由虚拟页面支持,内容仅在特定页面发生写入时才映射到已清除的物理页面(在此之前,页面实际上映射到全局零页面,可以从中读取)。这分散了初始化页面内容所需的时间 - 取决于映射的写入使用情况。

    然而,在无MMU情况下,匿名映射由物理页面支持,并且整个映射在分配时就被清除。这可能在用户空间malloc()期间导致显著延迟,因为C库执行匿名映射,然后内核对整个映射进行memset操作。

    但是,对于不需要预清除的内存(例如malloc()返回的内存),mmap()可以接受MAP_UNINITIALIZED标志,以指示内核在返回内存之前不需要清除它。请注意,必须启用CONFIG_MMAP_ALLOW_UNINITIALIZED才能允许这样做,否则该标志将被忽略。

    uClibc使用此功能来加速malloc(),而ELF-FDPIC binfmt使用此功能来分配brk和栈区域。

  4. 在无MMU模式下,可以通过/proc/maps查看系统上所有私有复制和匿名映射的列表。

  5. 在无MMU模式下,可以通过/proc/<pid>/maps查看进程使用的所有映射的列表。

  6. 提供MAP_FIXED或请求特定映射地址将导致错误。

  7. 私有映射的文件通常必须由驱动程序或文件系统提供读取方法,以便如果mmap()选择不直接映射后端设备,则可以将内容读取到分配的内存中。如果它们不提供,则将导致错误。这种情况最常出现在字符设备文件、管道、FIFO和套接字中。

进程间共享内存

SYSV IPC SHM 共享内存和 POSIX 共享内存均在 NOMMU 模式下受支持。前者通过常规机制,后者通过在 ramfs 或 tmpfs 挂载点上创建文件。

Futexes

如果架构支持,Futexes 在 NOMMU 模式下受支持。如果传递给 Futex 系统调用的地址超出了进程创建的映射范围,或者地址所在的映射不支持 Futexes(例如 I/O 字符设备映射),则会报错。

无MMU mremap

mremap()函数部分受支持。它可能会改变映射的大小,并且如果指定了MREMAP_MAYMOVE,并且映射的新大小超过了当前被映射所引用内存占用的slab对象的大小,或者可以使用更小的slab对象,则可能会移动它[1]

MREMAP_FIXED不受支持,但如果地址没有改变且对象不需要移动,则会忽略它。

共享映射不能移动。可共享映射也不能移动,即使它们当前未共享。

mremap()函数必须提供与先前映射对象的基地址和大小完全匹配的参数。它不能用于在现有映射中创建空洞、移动现有映射的一部分或调整映射的一部分大小。它必须作用于完整的映射。

提供可共享字符设备支持

为了提供可共享的字符设备支持,驱动程序必须提供一个file->f_op->get_unmapped_area()操作。mmap()例程将调用此操作以获取映射的建议地址。如果它不想遵守映射(因为映射太长、偏移量异常、标志组合不受支持等等),它可能会返回错误。

驱动程序还应提供带有功能集的后端设备信息,以指示此类设备上允许的映射类型。默认假设为可读写、不可执行,并且只能直接共享(不能复制)。

file->f_op->mmap()操作将被调用以实际启动映射。此时可以拒绝它。如果指定了NOMMU_MAP_COPY,返回ENOSYS错误将导致映射被复制。

当字符设备上的最后一个映射被移除时,将调用vm_ops->close()例程。现有映射将被共享,无论部分还是全部,如果可能且无需通知驱动程序。

file->f_op->get_unmapped_area()操作也允许返回-ENOSYS。这意味着此操作只是不想处理它,尽管它有一个操作。例如,它可能会尝试将调用定向到辅助驱动程序,而该驱动程序未能实现它。帧缓冲驱动程序的情况就是如此,它尝试将调用定向到特定于设备的驱动程序。在这种情况下,如果未指定NOMMU_MAP_COPY,映射请求将被拒绝,否则将映射一份副本。

重要

某些类型的设备在特定模式下可能会呈现不同的外观。闪存芯片就可能如此;例如,如果它们处于编程或擦除模式,您可能会在映射中看到状态而非数据。

在这种情况下,必须小心,以免在驱动程序忙于控制设备时,用户空间看到显示此类信息的共享或私有映射。尤其要记住:在某些情况下,私有可执行映射可能仍然直接从设备映射!

提供可共享内存支持的文件支持

在内存支持的文件上提供共享映射类似于提供对共享映射字符设备的支持。主要区别在于,提供服务的 文件系统 可能会分配一组连续的页面,并允许在其上进行映射。

建议对这类文件应用截断操作(如果文件为空且增加文件大小),应视为请求收集足够的页面来支持映射。这是支持 POSIX 共享内存所必需的。

内存支持的设备通过映射的后端设备信息中设置 memory_backed 标志来指示。

提供可共享块设备支持

在块设备文件上提供共享映射与字符设备完全相同。如果底层没有实际设备,则驱动程序应分配足够的连续内存来支持任何受支持的映射。

调整页面修剪行为

NOMMU mmap在执行分配时会自动向上舍入到最接近的2的幂次的页数。这可能会对内存碎片化产生不利影响,因此,它被配置为可调节的。默认行为是积极地修剪分配并将任何多余的页面返回给页面分配器。为了更好地控制碎片化,此行为可以完全禁用,或者提高到更高的页面水印以开始修剪。

页面修剪行为可通过sysctl vm.nr_trim_pages 进行配置。