[Linux]学习笔记系列 -- [kernel]iomem
本文介绍了Linux内核中include/linux/ioport.h文件定义的两组重要宏:IORES_DESC枚举和IORESOURCE标志位。IORES_DESC为不同类型的物理内存区域提供标准分类标识,在STM32等嵌入式系统中主要用于管理预留内存区域。IORESOURCE标志位则用于描述硬件资源类型、属性和状态,在STM32平台上通过设备树配置,对内存保护单元(MPU)的设置和资源管理至关
title: iomem
categories:
- linux
- kernel
tags: - linux
- kernel
abbrlink: 5ac3df00
date: 2025-10-03 09:01:49
https://github.com/wdfk-prog/linux-study
include/linux/ioport.h
IORES_DESC: I/O 内存资源描述符枚举
此代码片段定义了一个名为 IORES_DESC 的枚举类型。它的核心作用是为内核中不同类型的物理内存区域 (I/O Memory Resource) 提供一个标准化的、唯一的分类标识符 (即描述符)。这些描述符使得内核代码 (例如 region_intersects() 函数) 不仅可以按地址范围搜索内存区域, 还可以按其特定用途进行搜索和识别。
- 在单核无MMU的STM32H750平台上的原理与作用
在STM32H750这样的嵌入式系统中, 物理内存映射相对简单, 但内存区域的划分和用途管理依然至关重要。虽然该枚举中定义的许多描述符与PC架构相关 (如ACPI, CXL), 在STM32平台上不会被使用, 但其中有几个对嵌入式系统是核心且非常重要的:
-
IORES_DESC_RESERVED和IORES_DESC_SOFT_RESERVED: 这两个描述符在基于设备树的嵌入式系统中至关重要。当在设备树 (.dts文件) 中定义了reserved-memory(预留内存) 区域时, 内核会使用这些描述符来标记这些内存。例如, 为DMA控制器预留的物理连续内存池、为GPU或显示控制器预留的帧缓冲区 (framebuffer) 等, 都会被标记为IORES_DESC_RESERVED。这可以防止内核的通用内存分配器 (buddy system) 将这些专用内存分配给其他不相关的任务, 从而保证了关键驱动程序的内存可用性。 -
IORES_DESC_NONE: 这是默认值, 用于标记那些没有特定分类的通用内存区域, 例如主系统RAM中未被特殊指定的部分。
总而言之, 即使在简单的STM32平台上, 这个枚举也为内核提供了一个统一的框架来理解和区分物理内存的不同用途, 特别是保护那些通过设备树预留的专用内存区域。
/*
* I/O 资源描述符
*
* 描述符被 walk_iomem_res_desc() 和 region_intersects() 函数用于
* 在 iomem 表中搜索特定的资源范围。当一个资源范围支持搜索接口时,
* 应为其分配一个新的描述符。否则, resource.desc 必须设置为 IORES_DESC_NONE (0)。
*/
enum {
/*
* 描述符: 无特定类型.
* 这是资源的默认值, 表示它没有被赋予任何特殊的分类.
*/
IORES_DESC_NONE = 0,
/*
* 描述符: 用于崩溃转储(crash dump)的内核内存.
* 标记为kdump保留的内存区域. 在资源受限的STM32上通常不使用.
*/
IORES_DESC_CRASH_KERNEL = 1,
/*
* 描述符: ACPI 表.
* 标记包含ACPI固件表的内存区域. ACPI是PC架构的技术, 与STM32无关.
*/
IORES_DESC_ACPI_TABLES = 2,
/*
* 描述符: ACPI 非易失性存储.
* 标记被ACPI用于非易失性存储的内存区域. 与STM32无关.
*/
IORES_DESC_ACPI_NV_STORAGE = 3,
/*
* 描述符: 持久性内存.
* 标记可被用作持久性内存(如NVDIMM)的区域. 在STM32上可能用于映射片上非易失性存储.
*/
IORES_DESC_PERSISTENT_MEMORY = 4,
/*
* 描述符: 传统的持久性内存.
* 标记由传统PC BIOS方法(如e820)报告的持久性内存区域. 与STM32无关.
*/
IORES_DESC_PERSISTENT_MEMORY_LEGACY = 5,
/*
* 描述符: 设备私有内存.
* 标记只能由特定设备访问的内存区域, 例如某些PCI设备的总线地址空间.
*/
IORES_DESC_DEVICE_PRIVATE_MEMORY = 6,
/*
* 描述符: (硬)预留内存.
* 标记被固件或设备树明确预留, 完全不能被内核通用分配器使用的内存区域.
* 这在STM32上极为重要, 用于保护DMA池、帧缓冲区等.
*/
IORES_DESC_RESERVED = 7,
/*
* 描述符: 软预留内存.
* 标记那些被内核预留但可以被释放并回收的内存区域 (如果设置了 "reusable" 属性).
* 这在STM32上也常用于可回收的预留内存.
*/
IORES_DESC_SOFT_RESERVED = 8,
/*
* 描述符: CXL 内存.
* 标记由Compute Express Link (CXL)技术提供的内存区域. 这是服务器技术, 与STM32无关.
*/
IORES_DESC_CXL = 9,
};
IORESOURCE: 内核资源描述标志位
此代码片段定义了一系列宏, 它们是作为位掩码 (bitmask) 使用的标志位。这些标志位被设置在 struct resource 结构体的 flags 成员中, 用来详细、标准地描述一个硬件资源 (如一段内存地址、一个中断请求或一个DMA通道) 的类型、属性和状态。
- 在单核无MMU的STM32H750平台上的原理与作用
在像STM32H750这样的嵌入式系统中, 所有硬件资源都是在设计时就固定的, 并通过设备树 (.dts 文件) 告知内核。当Linux内核解析设备树时, 它会为每个设备声明的资源 (例如, reg 属性定义的寄存器地址范围, interrupts 属性定义的中断号) 创建一个 struct resource 实例, 并使用本文件中定义的这些 IORESOURCE_* 宏来正确地填充其 flags 字段。
这套标志系统对STM32平台至关重要, 因为:
- 它提供了统一的资源分类: 内核通过
IORESOURCE_MEM,IORESOURCE_IRQ等标志来区分不同类型的资源, 使得驱动程序可以用标准的方式 (如platform_get_resource()) 来请求和识别它们。 - 它描述了内存的关键属性: 标志如
IORESOURCE_CACHEABLE与MPU (内存保护单元) 的配置紧密相关, 决定了CPU访问特定内存区域 (如SRAM或外设寄存器) 时的缓存策略, 这对性能和数据一致性至关重要。 - 它管理资源的所有权:
IORESOURCE_BUSY和IORESOURCE_EXCLUSIVE等标志是内核资源管理的核心, 它们可以防止两个驱动程序意外地同时使用同一个硬件资源, 从而避免冲突和系统崩溃。
虽然此文件中包含大量与PC架构 (如PCI, PnP) 相关的标志, 但在STM32的内核配置中, 这些不相关的部分会被忽略。起作用的是那些描述内存、中断和状态的基础标志。
/*
* IO 资源具有这些已定义的标志.
*
* PCI设备在"resource" sysfs文件中向用户空间暴露这些标志,
* 所以不要移动它们的位置.
*/
/* 定义了总线特定比特位所占用的范围. 这8位通常由特定总线(如PnP)来定义更细致的类型. */
#define IORESOURCE_BITS 0x000000ff /* 总线特定的比特位 */
/* 定义了资源主类型所占用的比特位范围. */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* 资源类型比特位 */
/* 资源类型: I/O端口. 在ARM架构中很少使用, 因为ARM主要使用内存映射I/O (MMIO). */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O 端口 */
/* 资源类型: 内存区域. 在STM32上, 这是最核心的资源类型, 所有外设寄存器都通过内存映射访问. */
#define IORESOURCE_MEM 0x00000200
/* 资源类型: 寄存器. 通常作为 IORESOURCE_MEM 的一种细化, 明确指出这是一块寄存器区域. */
#define IORESOURCE_REG 0x00000300 /* 寄存器偏移量 */
/* 资源类型: 中断请求(IRQ). 在STM32上, 用于描述一个中断源, 例如GPIO或定时器的中断号. */
#define IORESOURCE_IRQ 0x00000400
/* 资源类型: DMA通道. 在STM32上, 用于描述一个DMA控制器通道. */
#define IORESOURCE_DMA 0x00000800
/* 资源类型: 总线. 用于描述一个总线编号. */
#define IORESOURCE_BUS 0x00001000
/* 内存属性: 可预取. 表示读取此区域没有副作用, 系统可以安全地预取数据以提高性能. */
#define IORESOURCE_PREFETCH 0x00002000 /* 无副作用 */
/* 内存属性: 只读. */
#define IORESOURCE_READONLY 0x00004000
/* 内存属性: 可缓存. 在STM32H750上, 这个标志非常重要, 它会影响MPU对该内存区域的配置, 决定数据是否经过Cache. */
#define IORESOURCE_CACHEABLE 0x00008000
/* 内存属性: 范围长度. 表示资源的end字段存储的是范围的长度, 而不是结束地址. */
#define IORESOURCE_RANGELENGTH 0x00010000
/* 内存属性: 可映像. 表示此内存(通常是ROM)可以被映射到RAM中以加速访问. */
#define IORESOURCE_SHADOWABLE 0x00020000
/* 对齐属性: size字段表示对齐要求, 而非大小. */
#define IORESOURCE_SIZEALIGN 0x00040000 /* size 指示对齐值 */
/* 对齐属性: start字段表示对齐要求, 而非起始地址. */
#define IORESOURCE_STARTALIGN 0x00080000 /* start 字段是对齐值 */
/* 内存属性: 64位内存. 表示这是一个64位的内存地址空间. 对32位的STM32H750不适用. */
#define IORESOURCE_MEM_64 0x00100000
/* 桥接属性: 表示该资源是由桥接器转发的. */
#define IORESOURCE_WINDOW 0x00200000 /* 由桥接器转发 */
/* 复用属性: 表示该资源是在软件层面被复用的. */
#define IORESOURCE_MUXED 0x00400000 /* 资源是软件复用的 */
/* 定义了资源扩展类型所占用的比特位. */
#define IORESOURCE_EXT_TYPE_BITS 0x01000000 /* 资源扩展类型 */
/* 扩展类型: 系统RAM. 这是一个修饰符, 与IORESOURCE_MEM组合使用, 用于明确标记一块内存是系统主RAM, 而非外设MMIO. */
#define IORESOURCE_SYSRAM 0x01000000 /* 系统RAM (修饰符) */
/* IORESOURCE_SYSRAM 特定的比特位. */
/* 系统RAM属性: 由驱动管理. 表示这块RAM总是通过一个驱动来发现和管理的. */
#define IORESOURCE_SYSRAM_DRIVER_MANAGED 0x02000000 /* 总是通过驱动检测. */
/* 系统RAM属性: 可合并. 表示这块资源可以和相邻的同类资源合并成一个更大的区域. */
#define IORESOURCE_SYSRAM_MERGEABLE 0x04000000 /* 资源可被合并. */
/* 访问属性: 独占. 表示用户空间程序不被允许映射此资源. 这对于保护关键的硬件寄存器很重要. */
#define IORESOURCE_EXCLUSIVE 0x08000000 /* 用户空间不能映射此资源 */
/* 状态: 禁用. 表示此资源当前是禁用的. */
#define IORESOURCE_DISABLED 0x10000000
/* 状态: 未设置. 表示还未给该资源分配一个确切的地址 (在动态分配时使用). */
#define IORESOURCE_UNSET 0x20000000 /* 尚未分配地址 */
/* 状态: 自动配置. */
#define IORESOURCE_AUTO 0x40000000
/* 状态: 忙. 表示已有驱动程序申请并正在使用此资源. 这是内核资源管理的核心标志. */
#define IORESOURCE_BUSY 0x80000000 /* 驱动已将此资源标记为忙 */
/* I/O 资源扩展类型组合宏 */
/* 一个完整的系统RAM资源类型, 是内存类型和系统RAM扩展类型的组合. */
#define IORESOURCE_SYSTEM_RAM (IORESOURCE_MEM|IORESOURCE_SYSRAM)
/* PnP(即插即用) IRQ 特定比特位 (使用 IORESOURCE_BITS). 这些对基于设备树的STM32系统不适用. */
#define IORESOURCE_IRQ_HIGHEDGE (1<<0)
#define IORESOURCE_IRQ_LOWEDGE (1<<1)
#define IORESOURCE_IRQ_HIGHLEVEL (1<<2)
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4) /* 表示中断线可被多个设备共享 */
#define IORESOURCE_IRQ_OPTIONAL (1<<5)
#define IORESOURCE_IRQ_WAKECAPABLE (1<<6) /* 表示此中断可唤醒系统 */
/* PnP DMA 特定比特位 (使用 IORESOURCE_BITS). 这些对STM32不适用. */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
#define IORESOURCE_DMA_8AND16BIT (1<<0)
#define IORESOURCE_DMA_16BIT (2<<0)
#define IORESOURCE_DMA_MASTER (1<<2)
#define IORESOURCE_DMA_BYTE (1<<3)
#define IORESOURCE_DMA_WORD (1<<4)
#define IORESOURCE_DMA_SPEED_MASK (3<<6)
#define IORESOURCE_DMA_COMPATIBLE (0<<6)
#define IORESOURCE_DMA_TYPEA (1<<6)
#define IORESOURCE_DMA_TYPEB (2<<6)
#define IORESOURCE_DMA_TYPEF (3<<6)
/* PnP 内存I/O特定比特位 (使用 IORESOURCE_BITS). 这些对STM32不适用. */
#define IORESOURCE_MEM_WRITEABLE (1<<0) /* IORESOURCE_READONLY 的复制 */
#define IORESOURCE_MEM_CACHEABLE (1<<1) /* IORESOURCE_CACHEABLE 的复制 */
#define IORESOURCE_MEM_RANGELENGTH (1<<2) /* IORESOURCE_RANGELENGTH 的复制 */
#define IORESOURCE_MEM_TYPE_MASK (3<<3)
#define IORESOURCE_MEM_8BIT (0<<3)
#define IORESOURCE_MEM_16BIT (1<<3)
#define IORESOURCE_MEM_8AND16BIT (2<<3)
#define IORESOURCE_MEM_32BIT (3<<3)
#define IORESOURCE_MEM_SHADOWABLE (1<<5) /* IORESOURCE_SHADOWABLE 的复制 */
#define IORESOURCE_MEM_EXPANSIONROM (1<<6)
#define IORESOURCE_MEM_NONPOSTED (1<<7)
/* PnP I/O特定比特位 (使用 IORESOURCE_BITS). 这些对STM32不适用. */
#define IORESOURCE_IO_16BIT_ADDR (1<<0)
#define IORESOURCE_IO_FIXED (1<<1)
#define IORESOURCE_IO_SPARSE (1<<2)
/* PCI ROM 控制比特位 (使用 IORESOURCE_BITS). 这些对没有PCI总线的STM32不适用. */
#define IORESOURCE_ROM_ENABLE (1<<0)
#define IORESOURCE_ROM_SHADOW (1<<1)
/* PCI 控制比特位. 与上面的PCI ROM共享 IORESOURCE_BITS. 这些对STM32不适用. */
#define IORESOURCE_PCI_FIXED (1<<4)
#define IORESOURCE_PCI_EA_BEI (1<<5)
kernel/iomem.c I/O内存资源管理器(I/O Memory Resource Manager) 内核资源树的守护者
历史与背景
这项技术是为了解决什么特定问题而诞生的?
kernel/iomem.c 及其相关文件是为了解决现代计算机系统中一个根本性的硬件资源管理问题:如何防止不同的设备驱动程序访问和控制同一块物理地址空间,从而避免系统冲突和崩溃。
在计算机体系结构中,CPU与外围设备(如网卡、显卡、磁盘控制器)的通信主要通过两种方式:
- I/O端口(I/O Ports):在x86架构中常见,是一块独立的、较小的地址空间。
- 内存映射I/O(Memory-Mapped I/O, MMIO):将设备的控制寄存器、内部RAM等映射到主内存的物理地址空间中。CPU访问这些地址就如同访问普通内存,但实际上是在与设备硬件交互。
这些I/O地址空间是独占性的物理资源。如果两个驱动程序都认为它们可以控制从地址0xFEBC0000开始的4KB空间,并同时对其进行读写,结果将是灾难性的数据损坏和系统不稳定。
iomem.c通过建立一个中央注册和仲裁机制来解决这个问题。它维护一个权威的“地籍图”,记录了系统中所有物理I/O内存区域的分配和使用情况。任何驱动程序在使用一块I/O地址之前,都必须先向这个管理器申请(request),使用完毕后再释放(release)。
它的发展经历了哪些重要的里程碑或版本迭代?
- 早期的简单列表:最初的资源管理可能只是一个简单的链表或数组,用于跟踪已分配的区域。
- 资源树的引入:最重要的里程碑是引入了**分层的资源树(Resource Tree)**模型。这完美地映射了硬件的物理拓扑。例如,一个PCI总线本身会占用一个大的地址窗口,而挂载在该总线下的各个PCI设备则从这个窗口中“申请”更小的区域。资源树模型使得这种父子、兄弟关系的管理变得清晰高效。
/proc/iomem和/proc/ioports:将内核中维护的资源树以一种人类可读的方式导出到/proc文件系统,这是一个巨大的进步。它为系统管理员和内核开发者提供了一个极其强大的工具,用于诊断资源冲突、查看硬件布局。- 与热插拔和动态设备集成:随着USB、PCI Hotplug等技术的发展,资源管理系统需要变得更加动态。
iomem.c的框架需要支持在系统运行时动态地添加和移除资源节点。
目前该技术的社区活跃度和主流应用情况如何?
iomem.c是Linux内核的基石之一,其代码非常成熟、稳定。它不属于经常会添加新潮功能的领域,但由于其在系统稳定性中的核心地位,任何相关的修改都会受到极其严格的审查。
它的应用无处不在:任何一个需要通过MMIO或I/O端口与硬件交互的设备驱动,都必须使用这个框架。从启动的那一刻起,内核就在使用它来构建系统物理资源的版图。
核心原理与设计
它的核心工作原理是什么?
iomem.c的核心是维护一个或多个资源树,树的节点是struct resource。
核心流程:
-
树的初始化(Population):
- 在系统启动的早期阶段,内核会根据硬件描述(如x86上的ACPI表、ARM等平台上的设备树Device Tree)来初始化资源树的根节点和顶级分支。
- 例如,它会创建一个名为“System RAM”的资源来标记物理内存的范围,创建“PCI I/O”或“PCI Memory”等资源来标记PCI总线的地址窗口。这些顶层资源构成了“可用土地”的总和。
-
资源的申请(Request):
- 当一个设备驱动(如
e1000e网卡驱动)被探测到时,它知道自己的硬件需要使用一段MMIO空间(信息来自PCI配置空间)。 - 驱动会调用
request_mem_region(start, size, name)或request_ioport_region()。 - 这个API函数的核心逻辑是冲突检测。它会遍历资源树,检查所申请的
[start, start + size - 1]范围是否与任何已经存在的资源区域(除了作为其父节点的容器区域)发生重叠。 - 如果重叠,说明该资源已被占用,申请失败,驱动探测也随之失败,从而防止了冲突。
- 如果不重叠,
iomem.c会创建一个新的struct resource节点,描述这次申请(记录起始地址、大小、申请者名字),并将其插入到资源树中,作为相应父资源(如“PCI Memory”)的一个子节点。
- 当一个设备驱动(如
-
资源的释放(Release):
- 当驱动被卸载时,它必须调用
release_mem_region()或release_ioport_region()。 - 这个函数会在资源树中找到对应的节点,并将其移除,表示这块“土地”现在又可以被其他驱动申请了。
- 当驱动被卸载时,它必须调用
-
信息展示:
- 当用户读取
/proc/iomem时,内核会遍历iomem_resource树,并将每个节点的范围、名称和状态格式化输出,为用户提供一个实时的系统物理地址空间使用图。
- 当用户读取
它的主要优势体现在哪些方面?
- 冲突避免:这是其最核心的优势,通过强制的申请/释放协议,从根本上防止了驱动间的资源冲突。
- 系统内省(Introspection):通过
/proc/iomem,开发者可以清晰地看到系统的物理资源是如何被划分和使用的,是调试硬件相关问题的利器。 - 动态管理:支持在运行时动态地分配和回收资源,适应了现代热插拔设备的需求。
- 分层管理:资源树的结构清晰地反映了硬件的层级关系,管理高效。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 合作式机制:
iomem.c是一个合作式(Cooperative)的管理器。它依赖于所有驱动都“遵守规则”。一个恶意的或编写有误的驱动仍然可以绕过这个机制,直接通过ioremap等方式访问它不拥有的物理地址。它负责管理,但不负责强制隔离(强制隔离是IOMMU的工作)。 - 范围有限:它只管理基于地址的资源(I/O内存和I/O端口)。它不管理中断号(由
kernel/irq管理)或DMA通道(由kernel/dma管理)等其他类型的共享硬件资源。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
它是Linux内核中管理I/O地址空间的唯一且强制的解决方案。
- PCI/PCIe设备驱动:任何PCIe驱动在初始化时,都必须解析其BAR(Base Address Registers),然后调用
pci_request_regions()(该函数内部会调用request_mem_region)来申请其MMIO空间。 - 平台设备驱动(Platform Device):在嵌入式系统中,很多SoC内部的外设(如UART, I2C, SPI控制器)地址是固定的。它们的驱动会从设备树中获取地址范围,并调用
devm_request_mem_region()来声明对这些寄存器区域的所有权。 - 老旧的ISA设备驱动:对于使用I/O端口的传统设备,驱动必须使用
request_ioport_region()来申请其端口范围。
是否有不推荐使用该技术的场景?为什么?
不适用场景在于资源类型不匹配。
- 不用于普通内存分配:当驱动需要一块内存来存储软件数据结构时,应该使用
kmalloc()或vmalloc()。iomem.c管理的内存是与硬件设备直接关联的特殊内存,不是通用的RAM。 - 不用于中断号或DMA通道申请:这些有专门的子系统和API(如
request_irq(),dma_request_channel())。
对比分析
请将其 与 其他内核资源管理器进行详细对比。
将iomem.c与内核中其他资源管理器对比,可以更好地理解其独特职责。
| 特性 | I/O内存管理器 (iomem.c) |
内核内存分配器 (mm/slab.c, page_alloc.c) |
中断号管理器 (kernel/irq) |
|---|---|---|---|
| 管理对象 | 物理地址空间 (MMIO, I/O Ports)。资源是固定的、硬件定义的地址范围。 | 可互换的RAM页。资源是匿名的、可随意替换的物理内存。 | 中断号 (IRQ numbers)。资源是有限的、代表硬件信号线的整数。 |
| 资源特性 | 位置敏感。地址0x1000和0x2000是完全不同的资源。 |
位置无关。驱动不关心kmalloc返回的虚拟地址背后的物理地址是什么。 |
逻辑标识符。数字本身没有物理意义,只是一个ID。 |
| 核心操作 | request_mem_region(start, size, ...) |
kmalloc(size, ...) |
request_irq(irq_num, ...) |
| 主要解决的问题 | 地址冲突。 | 动态内存需求。 | 中断信号分发与共享。 |
| 用户可见性 | /proc/iomem, /proc/ioports |
/proc/slabinfo, /proc/meminfo |
/proc/interrupts |
| 与硬件关系 | 直接映射。管理的地址就是硬件寄存器的物理地址。 | 间接。管理的是通用RAM,用于运行软件。 | 直接映射。管理的数字对应到中断控制器上的物理线路。 |
memremap: 为物理内存区域创建带缓存属性的CPU访问映射
此函数的主要作用是为一段指定的物理内存区域 (offset, size) 创建一个CPU可以直接访问的指针, 并根据调用者请求的标志 (flags) 为这个内存区域设置特定的缓存属性 (如写回 Write-Back、写通 Write-Through 或写合并 Write-Combining)。它类似于 ioremap, 但专门用于那些已知没有I/O副作用的内存区域, 如显存或用作DMA缓冲区的RAM。
- 在单核无MMU的STM32H750平台上的原理与作用
在STM32H750这类没有内存管理单元(MMU)但通常配备有内存保护单元(MPU)的单核微控制器上, memremap 的行为和意义有其特殊性:
-
"映射"的含义: 由于没有MMU, 内核虚拟地址与物理地址是直接对应的 (线性映射或等同)。因此,
memremap的核心目的不是创建虚拟地址到物理地址的翻译表项, 而是:- 提供一个类型正确的
void *指针, 其值就等于传入的物理地址offset, 使得内核代码可以通过这个指针合法地访问该内存。 - 配置内存属性: 这是在MPU系统上最关键的功能。
memremap会调用底层的ioremap_wc或ioremap_wt等函数, 这些函数最终会配置STM32H750的MPU, 为指定的物理地址范围设置一个区域 (region), 并将该区域的内存类型和缓存策略配置为调用者所请求的模式 (例如, 将某块SRAM配置为支持写合并, 以优化DMA传输)。
- 提供一个类型正确的
-
RAM区域检测: 函数首先会判断请求的内存区域是否属于内核所管理的主系统RAM (
IORESOURCE_SYSTEM_RAM)。- 如果请求映射的是主系统RAM, 并且标志为
MEMREMAP_WB(写回, 这是RAM的默认属性),memremap会认为这块内存已经是可访问且具有正确属性的, 通常会直接返回物理地址对应的指针, 避免重复设置。 - 如果试图用
MEMREMAP_WC或MEMREMAP_WT等非默认属性去映射主系统RAM, 函数会认为这是一个错误操作并返回失败。这是因为改变主RAM的缓存策略可能会破坏系统稳定性。
- 如果请求映射的是主系统RAM, 并且标志为
-
IO内存区域映射: 如果请求的内存区域是外设的寄存器地址或是一块专门用于DMA的、不属于主系统RAM的SRAM,
memremap会通过ioremap_*函数来配置MPU, 以确保CPU以正确的缓存策略访问这块区域, 从而保证数据一致性和性能。
/*
* memremap() - 将一个iomem资源重新映射为可缓存的内存
*
* @offset: iomem资源的起始物理地址. resource_size_t 是一个用于表示物理地址或资源大小的类型.
* @size: 需要重新映射的区域的大小, 单位为字节.
* @flags: 一个或多个标志的组合, 用于指定映射的缓存类型, 如 MEMREMAP_WB, MEMREMAP_WT, MEMREMAP_WC 等.
*
* memremap() 是 "ioremap" 的一种变体, 专门用于那些已知没有I/O副作用 (即读写这块内存不会触发硬件操作)
* 且不适用 __iomem 标注的内存资源. 当指定多个标志时, 函数会按照下面列出的顺序尝试不同的映射类型,
* 直到其中一种成功为止.
*
* MEMREMAP_WB - (写回) 匹配架构上系统RAM的默认映射方式. 这通常是读分配、写回的缓存策略.
* 此外, 如果指定了 MEMREMAP_WB 并且请求的区域本身就是RAM, memremap() 会绕过创建新映射的步骤,
* 而是直接返回一个指向该地址的指针 (因为在无MMU系统中它已经是直接可访问的).
*
* MEMREMAP_WT - (写通) 建立一个写操作或者绕过缓存, 或者直接写穿到内存的映射, 从程序的角度看,
* 数据永远不会处于 "脏" 的状态. 尝试用此类型映射系统RAM会失败. 在STM32上, 这通过MPU实现.
*
* MEMREMAP_WC - (写合并) 建立一个写合并的映射. 写操作可能会被合并(例如在CPU的写缓冲器中),
* 但除此之外是不被缓存的. 尝试用此类型映射系统RAM会失败. 在STM32上, 这通过MPU实现.
*/
/* 这里都是直接获取的内存地址 */
void *memremap(resource_size_t offset, size_t size, unsigned long flags)
{
/*
* 定义一个整型变量 is_ram.
* 用于存储对目标区域与系统RAM区域相交情况的检查结果.
* 调用 region_intersects 函数来判断 [offset, offset+size) 这段物理地址区间是否与内核管理的系统主RAM区域相交.
* IORESOURCE_SYSTEM_RAM: 表示要检查的资源类型是系统RAM.
* IORES_DESC_NONE: 表示不关心描述符的特定类型.
* 返回值可能是: REGION_INTERSECTS (相交), REGION_MIXED (部分相交), 或 REGION_OK (不相交).
*/
int is_ram = region_intersects(offset, size,
IORESOURCE_SYSTEM_RAM, IORES_DESC_NONE);
/*
* 定义一个 void 指针 addr, 并初始化为 NULL.
* 这个指针将用于存储最终映射成功的地址.
*/
void *addr = NULL;
/*
* 如果调用者没有提供任何标志, 这是一个无效的请求.
*/
if (!flags)
/*
* 直接返回 NULL, 表示失败.
*/
return NULL;
/*
* 检查区域是否为 "混合" 类型, 即一部分在系统RAM内, 另一部分在RAM外.
* 这是一种不支持的复杂情况.
*/
if (is_ram == REGION_MIXED) {
/*
* 调用 WARN_ONCE 宏, 打印一条警告信息. 这个警告只会在第一次发生时打印.
* @ 1: 条件为真, 触发警告.
* @ "memremap attempted...": 警告信息字符串.
* @ &offset: 要打印的物理地址. %pa 是其格式说明符.
* @ (unsigned long) size: 要打印的大小.
*/
WARN_ONCE(1, "memremap attempted on mixed range %pa size: %#lx\n",
&offset, (unsigned long) size);
/*
* 返回 NULL, 表示失败.
*/
return NULL;
}
/* 按顺序尝试所有请求的映射类型, 直到有一个成功返回非NULL地址. */
/*
* 检查调用者是否请求了 MEMREMAP_WB (写回) 标志.
*/
if (flags & MEMREMAP_WB) {
/*
* MEMREMAP_WB 是特殊的, 因为对系统RAM的WB映射可以直接满足 (无需创建新映射).
* 某些架构依赖于 memremap() 能够自动检测请求的范围是否在系统RAM中.
*/
if (is_ram == REGION_INTERSECTS)
/*
* 如果确认区域在系统RAM内, 调用 try_ram_remap.
* 在无MMU系统中, 如果标志只是WB, 此函数很可能只是进行检查后直接返回 offset 强制转换的指针.
*/
addr = try_ram_remap(offset, size, flags);
/*
* 如果 try_ram_remap 失败 (返回NULL), 或者区域不在系统RAM内,
* 则调用特定于架构的 arch_memremap_wb 函数.
* 这个函数会尝试为非RAM区域建立一个写回缓存的映射 (例如, 通过MPU).
*/
if (!addr)
addr = arch_memremap_wb(offset, size, flags);
}
/*
* 如果到目前为止我们还没有获得一个有效的映射 (addr 为 NULL),
* 并且还请求了其他类型的标志 (WT 或 WC),
* 那么我们将尝试建立一个新的 "虚拟" 地址映射 (在STM32上是配置MPU).
* 在这种情况下, 我们强制要求这个映射不能与系统RAM重叠.
*/
if (!addr && is_ram == REGION_INTERSECTS && flags != MEMREMAP_WB) {
/*
* 如果试图用非WB属性 (如WC或WT) 来映射系统RAM, 这是一个错误.
* 打印一次警告信息.
*/
WARN_ONCE(1, "memremap attempted on ram %pa size: %#lx\n",
&offset, (unsigned long) size);
/*
* 返回 NULL, 表示失败.
*/
return NULL;
}
/*
* 如果 addr 仍为 NULL, 且请求了 MEMREMAP_WT (写通) 标志,
* 调用 ioremap_wt 函数. 在ARMv7M上, 这个函数会配置MPU来创建一个写通属性的内存区域.
*/
if (!addr && (flags & MEMREMAP_WT))
addr = ioremap_wt(offset, size);
/*
* 如果 addr 仍为 NULL, 且请求了 MEMREMAP_WC (写合并) 标志,
* 调用 ioremap_wc 函数. 在ARMv7M上, 这个函数会配置MPU来创建一个写合并属性的内存区域.
*/
if (!addr && (flags & MEMREMAP_WC))
addr = ioremap_wc(offset, size);
/*
* 返回最终获取到的地址. 如果所有尝试都失败了, 它将是 NULL.
*/
return addr;
}
/*
* EXPORT_SYMBOL 将 memremap 函数的符号导出到内核符号表中.
* 这使得可加载的内核模块 (例如设备驱动) 可以在运行时调用这个核心内核函数.
*/
EXPORT_SYMBOL(memremap);
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)