news 2026/9/11 18:45:32

Linux No-MMU 内存映射支持完全指南:uClinux 环境下的 mmap 行为、约束与驱动实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux No-MMU 内存映射支持完全指南:uClinux 环境下的 mmap 行为、约束与驱动实现

Linux No-MMU 内存映射支持完全指南:uClinux 环境下的 mmap 行为、约束与驱动实现

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

在缺少 MMU(Memory Management Unit)的嵌入式环境中(即 uClinux 所代表的系统),Linux 内核只能提供"受限"的内存映射支持。本文以内核文档 Documentation/admin-guide/mm/nommu-mmap.rst 为骨架,结合内核源码逐项剖析 no-MMU 下 mmap()、shmat()、execve()、fork()/clone() 的行为差异,详解各类映射(匿名映射、文件映射、共享映射)在 MMU 与 no-MMU 两种模式下的异同,并给出为字符设备、内存后端文件、块设备提供可共享映射支持的驱动实现路径,以及vm.nr_trim_pages页修剪行为的调优方法。读完本文,你将掌握在 no-MMU 内核上正确使用与实现内存映射的完整知识体系。


1. 背景:no-MMU 环境下的内存映射全景

内核在 no-MMU 条件下(如 uClinux 环境)对内存映射只提供有限支持。从用户空间角度看,内存映射主要与三类系统调用相关:

  • mmap():创建内存映射;
  • shmat():SYSV 共享内存附着;
  • execve():加载可执行程序。

从内核角度看,execve() 的映射实际由 binfmt(二进制格式)驱动完成,这些驱动会回调 mmap() 例程来执行实际工作。

内存映射行为还牵涉 fork()、vfork()、clone() 与 ptrace() 的工作方式。在 uClinux 下没有 fork(),clone() 必须携带CLONE_VM标志才能工作——因为 no-MMU 下无法通过页表复制实现地址空间隔离。

MMU 与 no-MMU 的映射行为"相似但不相同",且后者受到的约束严格得多。下文逐一对比各类映射在两种模式下的表现。


2. 各类映射在 MMU 与 no-MMU 下的行为对比

2.1 匿名映射,MAP_PRIVATE

模式行为
MMUVM 区域由任意页(virtual pages)支撑,fork 时写时复制(copy-on-write)
no-MMUVM 区域由任意一段连续的物理页支撑(contiguous run of pages)

这是 no-MMU 最本质的差异:由于没有页表做虚拟到物理的间接映射,匿名私有映射必须一次性分配一整段物理连续的内存。

2.2 匿名映射,MAP_SHARED

在 MMU 模式下,共享匿名映射的行为非常类似私有映射,区别在于 fork() 或未带 CLONE_VM 的 clone() 之后它是进程间共享的。而 no-MMU不支持共享匿名映射,因此其行为与 MAP_PRIVATE 完全相同。

2.3 文件映射,MAP_PRIVATE,PROT_READ / PROT_EXEC,不带 PROT_WRITE

MMU 模式:VM 区域由从文件读入的页支撑;底层文件的修改会反映到映射中;fork 时复制。

no-MMU 模式(按优先级依次尝试):

  1. 复用已有映射:如果存在对同一文件同一段的已有映射,且权限兼容,内核会直接复用——即使该映射是由另一个进程创建的;
  2. 直接映射到后备设备:如果后备设备具有NOMMU_MAP_DIRECT能力且具备相应的映射保护能力,文件映射将直接建立在后备设备上。ramfs、romfs、cramfs 和 mtd 都可能允许这种直接映射;
  3. 复制到内存:如果后备设备不允许直接共享,但具有NOMMU_MAP_COPY能力,内核会把文件相应部分读入一段连续内存,并将 EOF 之外的额外空间清零。

无论哪种路径,对文件的写操作不会影响映射;对映射的写操作(虽然不应该发生)会因 no-MMU 缺乏保护而可见于其他进程

2.4 文件映射,MAP_PRIVATE,PROT_READ / PROT_EXEC 加 PROT_WRITE

MMU 模式:与非 PROT_WRITE 情形类似,但相关页在真正写入前会先被复制(copy-before-write)。此后,该页之下的底层文件修改不再反映到映射的支撑页中,该页改由 swap 支撑。

no-MMU 模式:工作方式与非 PROT_WRITE 情形大体相同,但总是复制、绝不共享(a copy is always taken and never shared)。

2.5 普通文件 / 块设备,MAP_SHARED,PROT_READ / PROT_EXEC / PROT_WRITE

  • MMU 模式:VM 区域由从文件读入的页支撑;对页的修改回写到文件;对文件的修改反映到映射页;fork 后共享。
  • no-MMU 模式不支持(not supported)。

也就是说,在 no-MMU 下,对普通文件和块设备发起可写共享映射会直接失败。

2.6 内存后端文件(memory-backed regular file),MAP_SHARED,可读写执行

MMU 模式:与普通文件一致。

no-MMU 模式:提供内存后端文件的文件系统(如 ramfs 或 tmpfs)可以选择通过 open → truncate → mmap 序列提供一段连续页用于映射。此时共享可写映射成为可能,行为与 MMU 情形一致;若文件系统不提供此类支持,映射请求将被拒绝

这一机制正是 POSIX 共享内存的实现基础(详见第 5 节)。

2.7 内存后端块设备,MAP_SHARED,可读写执行

MMU 模式:与普通文件一致。

no-MMU 模式:与内存后端文件类似,但块设备必须能在不调用 truncate 的情况下直接提供一段连续页。例如 ramdisk 驱动若在初始化时一次性把所有内存分配为连续数组,即可满足此要求。

2.8 内存后端字符设备,MAP_SHARED,可读写执行

MMU 模式:与普通文件一致。

no-MMU 模式:字符设备驱动可以选择通过 mmap() 提供对底层设备的直接访问,前提是设备具有可被直接访问的内存或类内存区域。典型例子是帧缓冲(frame buffers)和闪存设备(flash devices)。若驱动不提供此类支持,映射请求将被拒绝。


3. 进一步说明:no-MMU mmap 的特殊细节

3.1 页对齐规则

  • 私有文件映射可能返回非页对齐的缓冲区:因为可能发生 XIP(execute-in-place,就地执行),后备存储中的数据本身可能不是页对齐的;
  • 匿名映射则始终页对齐。如果可能,请求大小应为 2 的幂次方,否则会浪费部分空间——内核必须按 2 的幂次粒度分配,而多余部分只有在配置了修剪时才被回收(见第 8 节)。

3.2 匿名映射的清零行为

根据 Linux man pages(2.22 版及以后)的规范,匿名映射返回给用户前必须清零

  • MMU 情形:虚拟页支撑区域,只有在对特定页发生写入时才映射到已清零的物理页(此前读操作实际命中全局零页)。这种方式把页内容初始化的开销摊薄到了映射的写入过程,性能较好;
  • no-MMU 情形:匿名映射由物理页支撑,整个映射在分配时一次性清零。这会给用户空间的 malloc() 带来显著延迟——C 库执行匿名映射后,内核会对整个映射做 memset。

对于无需预清零的内存(如 malloc() 返回的内存),mmap() 可携带MAP_UNINITIALIZED标志告知内核不必在返回前清零。但注意:必须启用CONFIG_MMAP_ALLOW_UNINITIALIZED配置,否则该标志会被忽略。该配置项定义于 mm/Kconfig,依赖EXPERT && !MMU,默认关闭(default n),并明确警示其存在明显安全风险,应谨慎使用。

从源码 mm/nommu.c 可以看到实际清零逻辑:

/* clear anonymous mappings that don't ask for uninitialized data */ if (!vma->vm_file && (!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) || !(flags & MAP_UNINITIALIZED))) memset((void *)region->vm_start, 0, region->vm_end - region->vm_start);

即:匿名映射且未请求未初始化数据时,对整段区域执行 memset 清零。

该特性的两个典型受益者是:

  • uClibc:用它加速 malloc();
  • ELF-FDPIC binfmt:用它分配 brk 与栈区域。

3.3 映射可见性:/proc 接口

  • 系统上所有私有复制与匿名映射的清单可通过/proc/maps查看(no-MMU 模式特有);
  • 某进程使用的全部映射清单可通过/proc/<pid>/maps查看。

3.4 不支持的标志

  • 提供MAP_FIXED或请求特定映射地址将直接返回错误。no-MMU 无法实现固定地址映射,因为映射位置由物理连续内存的可用性决定。

3.5 对文件 read 方法的要求

被私有映射的文件通常必须由驱动或文件系统提供read 方法,以便在 mmap() 选择不直接映射后备设备时,能把文件内容读入已分配的内存。若缺失 read 方法则产生错误。这最常发生在字符设备文件、管道(pipes)、FIFO 和 socket 上。


4. Futex 支持

若架构支持,no-MMU 模式同样支持 futex。以下情况会返回错误:

  • 传入 futex 系统调用的地址位于进程映射之外;
  • 该地址所在的映射不支持 futex(例如 I/O 字符设备映射)。

原因在于 futex 依赖物理内存上的原子操作与等待队列,而 no-MMU 无法对 I/O 映射或映射外地址提供此类保证。


5. 进程间共享内存(IPC SHM / POSIX SHM)

SYSV IPC SHM 共享内存与 POSIX 共享内存在 no-MMU 模式下都受支持

  • SYSV SHM 通过常规机制(shmat 等)提供;
  • POSIX 共享内存通过创建在ramfs 或 tmpfs 挂载点上的文件提供——即借助第 2.6 节描述的内存后端文件共享映射机制。

内核文档 Documentation/admin-guide/mm/nommu-mmap.rst 同时建议:对这类空文件执行增大文件大小的 truncate 操作,应视为收集足够页以兑现映射的请求——这是支持 POSIX 共享内存的必备前提。相应的,此类"内存后端"设备由其 backing device info 中的memory_backed标志标识。


6. No-MMU 下的 mremap 语义

mremap() 在 no-MMU 下部分支持

  • 可以改变映射大小,且在指定MREMAP_MAYMOVE且新大小超出当前 slab 对象容量(或更小的 slab 对象可被利用)时可以移动映射(注:移动能力当前未实现,见文档脚注[#]);
  • MREMAP_FIXED 不受支持,但若地址无变化且对象无需移动,则该标志会被忽略;
  • 共享映射不可移动;可共享映射即便当前未共享也不可移动;
  • mremap() 的基地址与大小必须与先前映射精确匹配;不允许在既有映射中打洞、移动部分映射或调整部分映射大小——它必须作用于完整的映射

7. 为设备提供可共享映射支持(驱动实现指南)

7.1 共享字符设备支持

要提供可共享的字符设备支持,驱动必须实现三个关键操作:

  1. file->f_op->get_unmapped_area():mmap() 例程调用它获取提议的映射地址。若映射过长、偏移怪异、标志组合不受支持等,它可以返回错误拒绝;
  2. backing device info 能力位:驱动应提供后备设备信息,设置能力位以指示该设备允许的映射类型。默认假定为可读可写、不可执行、仅可直接共享(不能被复制)
  3. file->f_op->mmap():实际启用映射时调用。此处仍可拒绝;返回ENOSYS错误会在指定了NOMMU_MAP_COPY时触发"改为复制映射"的降级路径。

此外:

  • vm_ops->close()例程会在 chardev 上最后一个映射被移除时调用;已有映射若可能则被共享(整体或部分共享),无需通知驱动;
  • get_unmapped_area() 也允许返回-ENOSYS,表示"本驱动不想处理此请求"。典型场景是 framebuffer 驱动把调用转发给设备特定驱动而后者未实现该操作。此时若未指定NOMMU_MAP_COPY,映射请求被拒绝;否则降级为复制映射。

能力位定义位于 include/linux/fs.h:

/* * NOMMU_MAP_COPY: Copy can be mapped (MAP_PRIVATE) * NOMMU_MAP_DIRECT: Can be mapped directly (MAP_SHARED) * NOMMU_MAP_READ: Can be mapped for reading * NOMMU_MAP_WRITE: Can be mapped for writing * NOMMU_MAP_EXEC: Can be mapped for execution */ #define NOMMU_MAP_COPY 0x00000001 #define NOMMU_MAP_DIRECT 0x00000008 #define NOMMU_MAP_READ VM_MAYREAD #define NOMMU_MAP_WRITE VM_MAYWRITE #define NOMMU_MAP_EXEC VM_MAYEXEC #define NOMMU_VMFLAGS \ (NOMMU_MAP_READ | NOMMU_MAP_WRITE | NOMMU_MAP_EXEC)

注意NOMMU_MAP_DIRECT的注释明确对应MAP_SHAREDNOMMU_MAP_COPY对应MAP_PRIVATE——这正是第 2.3 节中"直接映射 vs 复制映射"两条路径在内核里的落地实现。

重要安全提示(文档以.. important::标注):某些类型的设备在不同模式下会呈现不同外观。例如闪存芯片处于编程或擦除模式时,映射中看到的是状态信息而非数据。此时必须格外小心,避免驱动控制设备期间用户空间通过共享或私有映射看到此类信息。尤其要记住:私有可执行映射在某些情况下仍可能直接从设备映射(即 XIP),不能想当然认为"私有"就一定安全。

7.2 共享内存后端文件支持

提供内存后端文件的共享映射与共享字符设备类似,主要区别在于:提供该服务的文件系统通常会分配一段连续页并允许在此之上建立映射

推荐行为:对空文件执行增大文件大小的 truncate 操作时,视为"收集足够页以兑现映射"的请求——这是 POSIX 共享内存的必要条件(见第 5 节)。内存后端设备由其 backing device info 的memory_backed标志标识。

7.3 共享块设备支持

块设备文件上的共享映射支持与字符设备完全相同。若设备之下没有真实的物理设备,驱动应分配足够的连续内存以兑现任何受支持的映射。


8. 页修剪行为与 vm.nr_trim_pages 调优

8.1 问题根源:2 的幂次分配导致的浪费与碎片化

no-MMU mmap 分配内存时自动向上取整到最近的 2 的幂次页数。由于系统分配器只能按 2^N × PAGE_SIZE 的量交付内存块,实际分配往往大于所需,产生两种副作用:

  • 若不修剪:长期映射会浪费多余空间;
  • 若修剪:多余页被返还分配器,但在进程频繁创建/销毁(大量瞬时进程)时会造成额外碎片化

8.2 配置与 sysctl

修剪行为是可配置的,默认行为是激进修剪——把超出部分全部返还页分配器。为在碎片化上保留更细粒度的控制,可以:

  • 完全禁用修剪;
  • 或调高触发修剪的页数水位(watermark)。

运行时通过 sysctlvm.nr_trim_pages调节:其值表示触发修剪所需的最小多余页数,设为 0 表示不进行修剪。

内核实现位于 mm/nommu.c:

static int sysctl_nr_trim_pages = CONFIG_NOMMU_INITIAL_TRIM_EXCESS; static const struct ctl_table nommu_table[] = { { .procname = "nr_trim_pages", .data = &sysctl_nr_trim_pages, .maxlen = sizeof(sysctl_nr_trim_pages), .mode = 0644, .proc_handler = proc_dointvec_minmax, .extra1 = SYSCTL_ZERO, }, };

可见其默认值来自编译期配置CONFIG_NOMMU_INITIAL_TRIM_EXCESS(定义于 mm/Kconfig,默认1),且通过proc_dointvec_minmax校验最小值不得低于 0。

8.3 编译期配置 CONFIG_NOMMU_INITIAL_TRIM_EXCESS

该选项"在启动前设定 mmap() 多余空间修剪行为"(depends on !MMU,默认 1),在 mm/Kconfig 中详细描述了修剪的取舍:开启修剪可回收多余页但可能加剧碎片化;关闭修剪则长期映射会浪费空间。它给出了三种运行形态:

  • 开启修剪:多余空间被裁剪并返还系统分配器,瞬时进程多时可能引发碎片化;
  • 关闭修剪:多余空间保留但不用,对长期映射意味着空间浪费;
  • 动态调节:通过/proc/sys/vm/nr_trim_pages设定触发修剪的最小多余页数,0 表示不修剪。

实际修剪判定发生在 mm/nommu.c:当多余页数total - point达到sysctl_nr_trim_pages阈值时执行修剪,从而在"回收浪费"与"避免碎片化"之间取得平衡。


9. 总结:no-MMU 映射的实践要点

在 uClinux 等 no-MMU 系统上设计内存映射代码时,请牢记以下要点:

关注点no-MMU 结论
匿名映射必须物理连续;整段分配时清零(除非 MAP_UNINITIALIZED + CONFIG_MMAP_ALLOW_UNINITIALIZED)
文件私有映射复用已有映射 → 直接映射(NOMMU_MAP_DIRECT)→ 复制映射(NOMMU_MAP_COPY),且要求文件有 read 方法
文件共享映射普通文件/块设备不支持;仅内存后端文件(ramfs/tmpfs)、内存后端块设备、支持直接访问的字符设备可行
MAP_FIXED / 指定地址直接报错
进程模型无 fork();clone() 需 CLONE_VM
mremap部分支持,只能作用于完整映射,共享/可共享映射不可移动
futex架构支持即可用,但 I/O 映射与映射外地址会报错
碎片化控制编译期 CONFIG_NOMMU_INITIAL_TRIM_EXCESS + 运行期 vm.nr_trim_pages
驱动开发实现 get_unmapped_area()/mmap()/vm_ops->close() 并正确设置 NOMMU_MAP_* 能力位

深入源码读者可继续参阅 mm/nommu.c(no-MMU mmap 核心实现,含 sysctl 表与清零/修剪逻辑)、include/linux/fs.h(NOMMU_MAP_* 能力位定义)以及 mm/Kconfig(MMAP_ALLOW_UNINITIALIZED 与 NOMMU_INITIAL_TRIM_EXCESS 的完整语义)。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 18:40:29

基于PyTorch的遥感影像滑坡场景分类实战:从数据到模型

简介&#xff1a;面向毕业设计、课程设计与项目开发场景&#xff0c;基于Python实现遥感影像滑坡场景分类的完整代码与项目文档。项目采用SVM分类器&#xff0c;通过光谱特征与GLCM纹理特征提取、K-Means视觉词袋聚类、LDA主题抽象&#xff0c;最终利用Libsvm工具完成场景分类&…

作者头像 李华
网站建设 2026/9/11 18:38:15

定时任务学习

一.理论基础前置复习&#xff1a;什么是完全二叉树&#xff1a;除了最后一层外其他层都达到最大节点数&#xff0c;且最后一层节点都靠左排列1.小顶堆小顶堆(Min-Heap)是一种特殊的完全二叉树结构,核心原则只有一条:堆序性质:任意一个父节点的值,都小于或等于它的子节点的值。也…

作者头像 李华
网站建设 2026/9/11 18:37:10

小麦病害目标检测数据集 | 小麦病害 锈病检测 智慧农业9060期

小麦病害目标检测数据集 | 小麦病害 锈病检测 智慧农业9060期 数据集概述 本数据集专注于小麦常见病害的视觉检测与识别&#xff0c;服务于智慧农业、病害监测及精准植保。数据涵盖六类小麦健康状况&#xff0c;适配田间巡检、无人机病害普查及植保决策支持等应用。数据集核心…

作者头像 李华