无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¶
如果架构支持,则在 NOMMU 模式下支持 Futexes。如果传递给 futex 系统调用的地址位于进程进行的映射之外,或者地址所在的映射不支持 futex(例如 I/O 字符设备映射),则会报错。
No-MMU mremap¶
部分支持 mremap() 函数。它可以更改映射的大小,并且如果指定了 MREMAP_MAYMOVE,并且映射的新大小超过了映射所引用的内存当前占用的 slab 对象的大小,或者可以使用较小的 slab 对象,则可以移动它 [1]。
不支持 MREMAP_FIXED,但如果没有地址更改并且不需要移动对象,则会忽略它。
共享映射可能不会被移动。可共享的映射也可能不会被移动,即使它们目前没有被共享。
必须为 mremap() 函数提供与先前映射的对象的基地址和大小完全匹配的值。它不能用于在现有映射中创建空洞,移动现有映射的部分或调整映射部分的大小。它必须对完整的映射起作用。
调整页面修剪行为¶
NOMMU mmap 在执行分配时会自动向上舍入到最接近的 2 的幂的页面数。这可能会对内存碎片产生不利影响,因此将其设置为可配置。默认行为是积极修剪分配并将任何多余的页面丢弃回页面分配器。为了保持对碎片更精细的控制,可以完全禁用此行为,或者将其提升到开始修剪的更高页面水位线。
页面修剪行为可以通过 sysctl vm.nr_trim_pages
进行配置。