深入Linux内存管理:从ioremap源码看VMALLOC区页表构建全过程

深入Linux内存管理:从ioremap源码看VMALLOC区页表构建全过程

最近在调试一块新的ARM64开发板时,遇到了一个奇怪的问题:驱动里用ioremap映射的寄存器地址,在某个特定配置下访问会触发数据异常。排查了半天,最后发现是页表属性设置的问题。这件事让我重新审视了ioremap这个看似简单的接口——它背后隐藏着Linux内核虚拟内存管理中最精妙的设计之一。今天,我们就抛开表面的API调用,直接钻进内核源码,看看当你在驱动里写下ioremap(0x48000000, 0x1000)时,内核到底在背后做了什么。

这篇文章适合那些已经熟悉Linux驱动开发,但对内存子系统工作原理感到好奇的内核开发者。我们会聚焦在ARM64架构上,从虚拟地址空间的分配到页表项的填充,完整走一遍VMALLOC区的映射流程。你会发现,ioremap不仅仅是“获取一个虚拟地址”那么简单,它涉及虚拟内存区域的管理、红黑树的查找、页表层级的遍历,以及内存属性的精确控制。

1. VMALLOC区:内核的“动态映射”空间

在深入ioremap之前,我们需要先理解它操作的舞台——VMALLOC区。内核的虚拟地址空间被划分成几个主要区域,每个区域都有特定的用途。对于ARM64架构,典型的布局如下:

0xffff000000000000 - 0xffff7fffffffffff : vmalloc区域
0xffff800000000000 - 0xffffffffffffffff : 内核镜像和线性映射区域

VMALLOC区位于内核空间的高端,它的核心特点是虚拟地址连续,但背后的物理页面可以不连续。这与kmalloc使用的线性映射区域形成鲜明对比——线性映射区域的虚拟地址和物理地址保持着固定的偏移关系。

那么内核为什么需要VMALLOC区?主要有几个场景:

  • 大块非连续物理内存的映射:比如DMA缓冲区,物理上可能是分散的,但驱动希望用连续的虚拟地址访问。
  • 外设寄存器的映射:这正是ioremap的主要用途,将物理地址空间中的设备寄存器映射到内核可以访问的虚拟地址。
  • 内核模块的加载:模块的代码和数据需要被映射到内核空间,但物理位置不确定。
  • vmalloc()函数:为用户态malloc的内核版本,用于分配大块虚拟内存。

VMALLOC区的管理核心是两个数据结构:struct vm_structstruct vmap_area。它们的关系有点像进程地址空间中的struct vm_area_struct和实际物理页面的关系——一个管“视图”,一个管“分配”。

// 简化的结构示意
struct vm_struct {
    void            *addr;           // 虚拟地址起始
    unsigned long   size;            // 区域大小
    const void      *caller;         // 调用者信息
    struct vmap_area *va;            // 指向底层的管理单元
};

struct vmap_area {
    unsigned long va_start;          // 区域起始地址
    unsigned long va_end;            // 区域结束地址
    struct rb_node rb_node;          // 红黑树节点,用于地址查找
    struct list_head list;           // 链表节点,用于顺序遍历
    struct vm_struct *vm;            // 指向上层的vm_struct
};

vmap_area是VMALLOC区的“账本”,记录着哪些虚拟地址范围已经被占用。内核用红黑树来快速查找地址,用链表来维护地址的顺序。当你调用ioremap时,内核首先要做的就是在这个“账本”上找到一段空闲的虚拟地址范围。

2. 虚拟地址的寻址:get_vm_area_caller如何找到“空洞”

ioremap的第一步是调用get_vm_area_caller在VMALLOC区分配一段虚拟地址范围。这个过程的核心算法可以概括为:在已分配的地址区间中寻找足够大的“空洞”

想象一下VMALLOC区就像一条很长的停车带,已经停了一些车(已分配的区域),我们需要找到一段足够长的空位来停新车。内核的查找策略相当聪明:

  1. 缓存优先:首先检查free_vmap_cache。这是一个最近成功分配时记录的vmap_area节点,它的va_end地址之后很可能还有空间。如果缓存有效且满足要求,就直接从那里开始。
  2. 红黑树搜索:如果缓存不适用,就从红黑树的根节点开始,找到第一个结束地址大于等于目标地址的vmap_area节点。
  3. 链表遍历:从这个节点开始,沿着链表向后遍历,检查每个已分配区域之间的“空洞”是否足够大。
  4. 地址对齐:找到的空洞地址需要按照请求的对齐要求进行调整(通常是页对齐)。

这里有个实际的查找示例。假设VMALLOC区当前的状态如下表所示:

已分配区域 起始地址 结束地址 大小
区域A 0xffff00000000 0xffff00001000 4KB
区域B 0xffff00002000 0xffff00003000 4KB
区域C 0xffff00005000 0xffff00006000 4KB

现在请求分配一个8KB的区域。查找过程会是:

  • 从区域A之后开始(0xffff00001000),但到区域B之前只有4KB空间,不够。
  • 从区域B之后开始(0xffff00003000),到区域C之前有8
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值