7. 内存管理与 I/O 访问
基于 Linux 内核源码分析 | 硬件平台:瑞芯微 RK3588 (ARM64)
目录
7.1 ioremap / iounmap
7.1.1 概述
在 Linux 驱动开发中,硬件设备的寄存器通常位于物理地址空间。内核代码使用的是虚拟地址,因此需要一种机制将物理地址映射到内核虚拟地址空间。ioremap 就是为此而设计的接口。
核心源码位置:
- ARM64 架构实现:kernel/arch/arm64/mm/ioremap.c
- 通用页表映射:kernel/mm/ioremap.c
- 接口定义:kernel/arch/arm64/include/asm/io.h
7.1.2 ioremap 映射流程
7.1.3 核心实现分析
__ioremap_caller 函数
源码位置: kernel/arch/arm64/mm/ioremap.c:21-63
static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,
pgprot_t prot, void *caller)
{
unsigned long last_addr;
unsigned long offset = phys_addr & ~PAGE_MASK;
int err;
unsigned long addr;
struct vm_struct *area;
/* 1. 页对齐处理 */
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(size + offset);
/* 2. 检查地址范围有效性 */
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr || (last_addr & ~PHYS_MASK))
return NULL;
/* 3. 禁止映射 RAM 区域 */
if (WARN_ON(pfn_valid(__phys_to_pfn(phys_addr))))
return NULL;
/* 4. 分配内核虚拟地址空间 */
area = get_vm_area_caller(size, VM_IOREMAP, caller);
if (!area)
return NULL;
addr = (unsigned long)area->addr;
area->phys_addr = phys_addr;
/* 5. 建立页表映射 */
err = ioremap_page_range(addr, addr + size, phys_addr, prot);
if (err) {
vunmap((void *)addr);
return NULL;
}
/* 6. 返回带偏移的虚拟地址 */
return (void __iomem *)(offset + addr);
}
ioremap_page_range 页表建立
源码位置: kernel/mm/ioremap.c:222-250
页表层级遍历:
PGD (Page Global Directory)
↓
P4D (Page 4th Directory)
↓
PUD (Page Upper Directory)
↓
PMD (Page Middle Directory)
↓
PTE (Page Table Entry) → 物理页面
支持巨页映射优化:
| 页表级别 | 映射粒度 | 条件 |
|---|---|---|
| P4D 级别 | 512GB | 地址和大小都要 512GB 对齐 |
| PUD 级别 | 1GB | 地址和大小都要 1GB 对齐 |
| PMD 级别 | 2MB | 地址和大小都要 2MB 对齐 |
| PTE 级别 | 4KB | 常规页面映射 |
7.1.4 内存类型属性
源码位置: kernel/arch/arm64/include/asm/pgtable-prot.h:55-60
ARM64 定义了多种设备内存属性:
/* 设备内存属性 - 不可缓存 */
#define PROT_DEVICE_nGnRnE /* non-Gathering, non-Reordering, No Early acknowledge */
#define PROT_DEVICE_nGnRE /* non-Gathering, non-Reordering, Early acknowledge */
/* 正常内存属性 */
#define PROT_NORMAL_NC /* Normal Non-Cacheable */
#define PROT_NORMAL /* Normal Cacheable */
| 属性缩写 | 含义 | 说明 |
|---|---|---|
| nG | non-Gathering | 读写操作不会合并 |
| nR | non-Reordering | 读写操作不会重排序 |
| RnE | Read No Early | 读操作不提前返回 |
| RE | Early | 写操作可提前返回确认 |
| NC | Non-Cacheable | 不使用缓存 |
常用 ioremap 变体:
/* 标准 ioremap - 设备内存,不可缓存 */
void __iomem *ioremap(phys_addr_t addr, size_t size);
/* 写合并模式 - 正常非缓存内存 */
void __iomem *ioremap_wc(phys_addr_t addr, size_t size);
/* 可缓存模式 - 用于正常 RAM 区域 */
void __iomem *ioremap_cache(phys_addr_t addr, size_t size);
/* PCI 配置空间 - nGnRnE 属性 */
void __iomem *pci_remap_cfgspace(phys_addr_t addr, size_t size);
7.1.5 iounmap 解除映射
源码位置: kernel/arch/arm64/mm/ioremap.c:72-83
void iounmap(volatile void __iomem *io_addr)
{
unsigned long addr = (unsigned long)io_addr & PAGE_MASK;
/*
* 某些情况下(如 ioremap_cache 复用 RAM 映射),
* 地址可能不在 vmalloc 范围内,需要检查。
*/
if (is_vmalloc_addr((void *)addr))
vunmap((void *)addr);
}
7.1.6 使用示例
/* 假设 RK3588 GPIO 控制器物理基址为 0xFD820000 */
#define GPIO_BASE_PHYS 0xFD820000
void __iomem *gpio_base;
/* 1. 映射设备寄存器 */
gpio_base = ioremap(GPIO_BASE_PHYS, 0x1000);
if (!gpio_base) {
pr_err("Failed to ioremap GPIO registers\n");
return -ENOMEM;
}
/* 2. 访问寄存器 */
u32 value =


4345

被折叠的 条评论
为什么被折叠?



