无MMU内存映射支持¶
在无MMU条件下(例如uClinux环境中),内核对内存映射的支持是有限的。从用户空间的角度来看,内存映射与mmap()系统调用、shmat()调用和execve()系统调用一起使用。从内核的角度来看,execve()映射实际上由binfmt驱动程序执行,这些驱动程序会回调mmap()例程来完成实际工作。
内存映射行为还涉及fork()、vfork()、clone()和ptrace()的工作方式。在uClinux下没有fork(),而clone()必须提供CLONE_VM标志。
MMU和无MMU情况下的行为相似,但并不相同;在后者的情况下,其限制也更多
匿名映射,MAP_PRIVATE
在MMU情况下:由任意页面支持的VM区域;跨fork进行写时复制。
在无MMU情况下:由任意连续页面运行支持的VM区域。
匿名映射,MAP_SHARED
它们与私有映射的行为非常相似,不同之处在于它们在MMU情况下跨fork()或clone()(不带CLONE_VM)共享。由于无MMU情况不支持这些,因此其行为与MAP_PRIVATE相同。
文件,MAP_PRIVATE,PROT_READ / PROT_EXEC,!PROT_WRITE
在MMU情况下:由从文件读取的页面支持的VM区域;对底层文件的更改反映在映射中;跨fork复制。
在无MMU情况下
如果存在,内核将重用对同一文件的同一段的现有映射(如果其具有兼容的权限),即使此映射是由另一个进程创建的。
如果可能,文件映射将直接在支持设备上进行,如果支持设备具有NOMMU_MAP_DIRECT功能和适当的映射保护功能。Ramfs、romfs、cramfs和mtd都可能允许这样做。
如果支持设备不能或不允许直接共享,但确实具有NOMMU_MAP_COPY功能,那么文件的相应部分将被读取到一段连续的内存中,并且超出EOF的任何多余空间将被清除
对文件的写入不影响映射;对映射的写入在其他进程中可见(没有MMU保护),但不应该发生。
文件,MAP_PRIVATE,PROT_READ / PROT_EXEC,PROT_WRITE
在MMU情况下:类似于非PROT_WRITE情况,不同之处在于相关页面在实际写入发生之前被复制。从那时起,对该页面底层文件的写入不再反映到映射的后端页面中。该页面则由交换空间支持。
在无MMU情况下:与非PROT_WRITE情况非常相似,但始终进行复制且从不共享。
常规文件/块设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE
在MMU情况下:由从文件读取的页面支持的VM区域;对页面的更改写回文件;对文件的写入反映到支持映射的页面中;跨fork共享。
在无MMU情况下:不支持。
内存支持的常规文件,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE
在MMU情况下:与普通常规文件相同。
在无MMU情况下:提供内存支持文件的文件系统(如ramfs或tmpfs)可能会选择通过提供连续的页面序列进行映射来遵守开放、截断、mmap序列。在这种情况下,共享可写内存映射将成为可能。其工作方式与MMU情况相同。如果文件系统不提供任何此类支持,则映射请求将被拒绝。
内存支持的块设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE
在MMU情况下:与普通常规文件相同。
在无MMU情况下:与内存支持的常规文件相同,但块设备必须能够提供连续的页面运行,而无需调用截断。如果ramdisk驱动程序预先分配所有内存作为连续数组,则可以实现这一点。
内存支持的字符设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE
在MMU情况下:与普通常规文件相同。
在无MMU情况下:如果字符设备驱动程序提供可以直接访问的内存或准内存,则可以选择通过提供对底层设备的直接访问来遵守mmap()。此类示例包括帧缓冲器和闪存设备。如果驱动程序不提供任何此类支持,则映射请求将被拒绝。
无MMU MMAP的进一步说明¶
对文件的私有映射请求可能会返回一个非页面对齐的缓冲区。这是因为可能发生XIP,并且数据在后端存储中可能未进行分页对齐。
匿名映射的请求将始终是页面对齐的。如果可能,请求的大小应该是2的幂,否则一些空间可能会被浪费,因为内核必须分配2的幂的粒度,但只有在适当配置时才会丢弃多余的部分,因为这会影响碎片化。
根据Linux手册页(2.22版或更高版本),匿名映射请求分配的内存通常会在返回之前由内核清除。
在MMU情况下,这可以以合理的性能实现,因为区域由虚拟页面支持,内容仅在特定页面发生写入时才映射到已清除的物理页面(在此之前,页面实际上映射到全局零页面,可以从中读取)。这分散了初始化页面内容所需的时间 - 取决于映射的写入使用情况。
然而,在无MMU情况下,匿名映射由物理页面支持,并且整个映射在分配时就被清除。这可能在用户空间malloc()期间导致显著延迟,因为C库执行匿名映射,然后内核对整个映射进行memset操作。
但是,对于不需要预清除的内存(例如malloc()返回的内存),mmap()可以接受MAP_UNINITIALIZED标志,以指示内核在返回内存之前不需要清除它。请注意,必须启用CONFIG_MMAP_ALLOW_UNINITIALIZED才能允许这样做,否则该标志将被忽略。
uClibc使用此功能来加速malloc(),而ELF-FDPIC binfmt使用此功能来分配brk和栈区域。
在无MMU模式下,可以通过/proc/maps查看系统上所有私有复制和匿名映射的列表。
在无MMU模式下,可以通过/proc/<pid>/maps查看进程使用的所有映射的列表。
提供MAP_FIXED或请求特定映射地址将导致错误。
私有映射的文件通常必须由驱动程序或文件系统提供读取方法,以便如果mmap()选择不直接映射后端设备,则可以将内容读取到分配的内存中。如果它们不提供,则将导致错误。这种情况最常出现在字符设备文件、管道、FIFO和套接字中。
Futexes¶
如果架构支持,Futexes 在 NOMMU 模式下受支持。如果传递给 Futex 系统调用的地址超出了进程创建的映射范围,或者地址所在的映射不支持 Futexes(例如 I/O 字符设备映射),则会报错。
无MMU mremap¶
mremap()函数部分受支持。它可能会改变映射的大小,并且如果指定了MREMAP_MAYMOVE,并且映射的新大小超过了当前被映射所引用内存占用的slab对象的大小,或者可以使用更小的slab对象,则可能会移动它[1]。
MREMAP_FIXED不受支持,但如果地址没有改变且对象不需要移动,则会忽略它。
共享映射不能移动。可共享映射也不能移动,即使它们当前未共享。
mremap()函数必须提供与先前映射对象的基地址和大小完全匹配的参数。它不能用于在现有映射中创建空洞、移动现有映射的一部分或调整映射的一部分大小。它必须作用于完整的映射。
调整页面修剪行为¶
NOMMU mmap在执行分配时会自动向上舍入到最接近的2的幂次的页数。这可能会对内存碎片化产生不利影响,因此,它被配置为可调节的。默认行为是积极地修剪分配并将任何多余的页面返回给页面分配器。为了更好地控制碎片化,此行为可以完全禁用,或者提高到更高的页面水印以开始修剪。
页面修剪行为可通过sysctl vm.nr_trim_pages
进行配置。