不连续页分配器补充

vmalloc流程

1. 背景:vmalloc() 要解决的问题

  • kmalloc() 要求 虚拟地址连续,物理页也连续。大块内存分配可能失败。
  • vmalloc() 只保证 虚拟地址连续,物理内存可以由很多不连续的页拼接。

实现的关键就是:

  1. vmalloc 区域 找一块空闲的虚拟地址。
  2. 分配若干物理页(可能不连续)。
  3. 建立虚拟地址 → 物理页的映射。

这三个步骤里,数据结构的角色就是:

  • **vmap_area**:负责管理 vmalloc 区域里的虚拟地址范围。
  • **vm_struct**:描述一个具体的 vmalloc 内存块(和用户返回的 addr 对应)。

2. 关键数据结构解析

struct vmap_area

表示 vmalloc 区域中的一个虚拟地址段

struct vmap_area {unsigned long va_start;unsigned long va_end;unsigned long flags;struct rb_node rb_node;         /* address sorted rbtree */struct list_head list;          /* address sorted list */struct llist_node purge_list;    /* "lazy purge" list */struct vm_struct *vm;struct rcu_head rcu_head;
};
  • 内核全局维护一棵红黑树和链表来管理所有的 vmap_area,保证虚拟地址分配不冲突。
  • 每次 vmalloc() 会新建一个 vmap_area,挂到这棵树里。

struct vm_struct

表示 一个具体的 vmalloc 块,用户代码拿到的就是 vm_struct->addr

struct vm_struct {struct vm_struct	*next;void			*addr;unsigned long		size;unsigned long		flags;struct page		**pages;unsigned int		nr_pages;phys_addr_t		phys_addr;const void		*caller;
};
  • **pages[]**** 是核心**:记录了 vmalloc 这片区域实际映射到哪些物理页。
  • addrvmap_area->va_start,两者一一对应。
  • vm_struct 通过 vmap_area->vm 与虚拟地址区间关联。

3. vmalloc() 的流程

vmalloc(size) 为例,流程大致是:

(1) 计算所需页数

nr_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;

(2) 在 vmalloc 区域找虚拟地址

  • 调用 alloc_vmap_area()
    • 通过 红黑树,在vmalloc区域中查找一块足够大的空闲虚拟地址区间;
    • 建立一个新的 struct vmap_area,填好 va_start/va_end
    • 挂到全局红黑树/链表里。

这一步解决:虚拟地址空间的分配。


(3) 分配物理页

  • 调用 alloc_page()(实际走伙伴系统),分配 nr_pages 个物理页。
  • 这些页可能离散。
  • 把它们存进 vm_struct->pages[]

这一步解决:物理内存的获取。


(4) 建立映射

  • 调用 map_vm_area() 或更底层的 vmap_page_range()
    • 遍历 pages[]
    • 在页表里把 va_start ~ va_end 的虚拟页,依次映射到对应的物理页。

这样,就实现了 虚拟地址连续 → 物理页不连续 的映射。

如何找到内核线程的页表?后面解释


(5) 返回给用户

  • vm_struct->addr = (void *)vmap_area->va_start
  • 返回给调用者。

调用者得到的是一段看起来“连续”的内存。


4. vmalloc() 与 vmap() 的关系

  • vmalloc() = 自动分配物理页 + 申请虚拟地址 + 调用 vmap 建立映射
  • vmap(pages[], nr_pages, ...) = 自己提供物理页数组,直接建立虚拟映射。

所以:

  • **vmalloc()**** 面向使用者**(只要给我一段内存);
  • **vmap()**** 面向更底层**(我已有页,帮我拼接)。

5. 小结

vmalloc() 的机制可以归纳为三步:

  1. 地址管理
    vmap_area 负责在 vmalloc 区域找一段空闲虚拟地址,并放到全局红黑树。
  2. 块描述
    vm_struct 保存这段虚拟内存的元数据(起始地址、大小、物理页数组)。
  3. 页表映射
    把虚拟地址区间映射到 vm_struct->pages[] 里记录的实际物理页。

vfree释放过程

vfree() 被调用时:

  1. 根据 addr 找到对应的 vmap_area
  2. 从红黑树和链表删除。
  3. 把物理页释放回伙伴系统。
  4. 延迟释放 vmap_area(放到 purge_list,用 RCU 机制安全回收)。

linux中常用内存分配函数

用户态 vs 内核态

  • 用户态 API
    malloc(), brk(), mmap()
    这是 C 库(glibc)或系统调用提供的接口,进程使用。
    本质上是通过 VMA 管理 + 缺页时分配物理页
  • 内核态 API
    alloc_pages(), kmalloc(), vmalloc()
    这是 Linux 内核给自己用的内存分配器接口,驱动/内核子系统用。
    本质上是 直接操作伙伴系统/SLAB/vmalloc 子系统

各方法机制对比

接口使用场景内核实现方式地址连续性使用者
malloc()用户程序最常用的内存申请glibc 封装,底层调用 brk()
mmap()
扩展堆/映射匿名页
用户虚拟地址连续(物理不一定连续)用户空间
brk()扩展/收缩 heap(sbrk
系统调用)
修改进程的堆 VMA 边界,缺页时由 alloc_pages()
分配物理页
用户虚拟地址连续(物理不一定连续)用户空间
mmap()大块内存/文件映射/共享内存创建新的 VMA,缺页时用 alloc_pages()
或从文件读取到物理页
用户虚拟地址连续(物理不一定连续)用户空间
alloc_pages()分配页粒度内存伙伴系统分配 struct page物理连续,内核虚拟地址也连续(线性映射区)内核
kmalloc()内核小块内存(字节/KB 级)SLAB/SLUB 分配器,底层基于 alloc_pages()物理连续 + 内核虚拟连续内核
vmalloc()内核大块内存(MB 级)从 vmalloc 区找虚拟地址区间,分配不连续物理页(底层基于alloc_pages()),建立页表映射虚拟地址连续,物理地址不连续内核

关系梳理

  1. 用户空间
    • malloc() → 封装,可能走 brk()mmap()
    • brk()/mmap() → 修改 mm_struct 和 VMA;
    • 缺页时 → 最终用 alloc_pages() 分配物理页。
  2. 内核空间
    • alloc_pages() → 最底层接口,直接伙伴系统;
    • kmalloc() → 面向小对象,使用slab分配器,底层用 alloc_pages()
    • vmalloc() → 面向大块虚拟地址空间,物理页不连续。底层用 alloc_pages()

总结

  • 用户态用 malloc()(底层 brk/mmap),本质是修改虚拟内存布局,缺页时通过 **伙伴系统 ****alloc_pages()** 分配物理页;
  • 内核态直接用 alloc_pages()kmalloc()(小块)、vmalloc()(大块,物理不连续)。

如何找到内核线程的页表?

“内核线程没有用户空间”就会怀疑:那页表怎么办?是不是有个“内核专用页表”?
其实 Linux 内核线程并不是共享一个“内核页表”,而是借用普通进程的页表


1. 页表的基本事实

  • x86/ARM 等架构上,CPU 访问内存都要走页表转换。页表的基地址存放在控制寄存器(x86 的 CR3,ARM64 的 TTBR0/TTBR1)。
  • Linux 设计:所有进程的页表都包含了同一份内核态映射(高地址部分的 linear mapping、vmalloc 等)
    • 换句话说,每个进程的 mm_struct->pgd 不同,但其中“内核地址区”是一致的。
    • 所以,只要有一份用户进程的页表,就能保证内核地址区始终可用。

2. 普通进程 vs 内核线程

普通用户进程

  • 每个进程有自己的 mm_struct,里面有独立的 pgd(页全局目录)。
  • 切换进程时,调度器会把 mm->pgd 加载到 CR3
  • 这样用户态地址空间不同,但内核态地址映射相同。

内核线程

  • task_struct->mm = NULL,说明它没有独立的 mm_structpgd
  • 调度器在切换到内核线程时:
    • 如果发现 mm == NULL,会把 prev->active_mm 借给内核线程,保存到 next->active_mm
    • 并且在切换时 不会切换 CR3,继续使用原进程的页表。
  • 内核线程只在内核态执行,不会访问用户空间地址,所以根本不在意用户空间页表部分。

3. 也就是说:

  • 每个内核线程并没有单独的页表
  • 它们 借用上一个普通进程的页表,只是用其中的内核映射部分。
  • 这就是 task_struct->active_mm 的意义。

4. “内核页表”的保存与使用

  • 并不存在一个独立的“全局内核页表”。
  • 取而代之:每个进程的页表都自带了内核映射部分
  • 内核线程调度时,就继续使用借来的页表的内核部分。

总结

内核线程没有独立的页表,它们不会切换到某个“内核专用页表”。调度到内核线程时,Linux 内核会让它们 借用上一个进程的页表(通过 active_mm),只使用其中的内核地址映射部分。由于所有进程的内核区页表一致,内核线程就能安全运行。

测试验证

代码实现

实现一个最小可运行的 内核模块 示例,专门用来测试 vmalloc() 申请和释放内存。代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/vmalloc.h>   // vmalloc/vfree
#include <linux/kernel.h>#define VMALLOC_SIZE (1024 * 1024)  // 申请 1MBstatic void *vmalloc_area = NULL;static int __init vmalloc_test_init(void)
{pr_info("vmalloc_test: module loaded\n");// 使用 vmalloc 申请一块连续虚拟地址的内存vmalloc_area = vmalloc(VMALLOC_SIZE);if (!vmalloc_area) {pr_err("vmalloc_test: vmalloc failed!\n");return -ENOMEM;}pr_info("vmalloc_test: allocated %d bytes at %pK\n",VMALLOC_SIZE, vmalloc_area);// 写入测试数据memset(vmalloc_area, 0xAA, VMALLOC_SIZE);pr_info("vmalloc_test: memory initialized with 0xAA\n");return 0;
}static void __exit vmalloc_test_exit(void)
{if (vmalloc_area) {vfree(vmalloc_area);pr_info("vmalloc_test: freed memory at %pK\n", vmalloc_area);}pr_info("vmalloc_test: module unloaded\n");
}module_init(vmalloc_test_init);
module_exit(vmalloc_test_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("congchp");
MODULE_DESCRIPTION("Simple vmalloc test module");
obj-m += vmalloc_test.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

测试结果

dmesg结果:

/proc/vmallocinfo结果:

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921615.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bug | 事务粒度不能太大,含demo

刷到一个说法&#xff0c;建议不要使用transaction注解。这个说法不太准确&#xff0c;注解可以用&#xff0c;但标注的事务粒度不能太大&#xff0c;这样可能会引起数据库阻塞问题。以下介绍注解事务和编程式事务的两种用法。 关键字&#xff1a;声明式事务&#xff0c;编程式…

别再看人形机器人了!真正干活的机器人还有这些!

每次提起“机器人”&#xff0c;你脑海中是不是立刻浮现出双足行走、拟人微笑、还能陪你聊天的那种“人形机器人”&#xff1f;但真相是&#xff1a;人形机器人并非更实用&#xff0c;只是满足了我们对“人类替代品”的幻想。事实上&#xff0c;机器人的世界远比我们想象的更丰…

垃圾回收,几种GC算法及GC机制

1.什么是垃圾回收&#xff1f;如何触发垃圾回收&#xff1f; 垃圾回收(GC)是自动管理内存的一种机制&#xff0c;它负责自动释放不再被程序引用的对象所占用的内存&#xff0c;这种机制减少内存泄漏和内存管理错误的可能性。可以通过多种方式触发&#xff1a;内存不足时&#x…

更智能的零售终端设备管理:合规、安全与高效

目录 引言&#xff1a;为什么零售连锁和自助终端需要更智能的设备管理&#xff1f; 典型应用场景 1. 便利店连锁 2. 大型超市 3. 加油站 4. 自助终端 核心功能&#xff0c;驱动高效与安全 1. 批量配置 2. 定时策略同步 3. 设备状态监控 4. Kiosk 模式&#xff0c;保…

Elasticsearch:向量搜索过滤 - 保持相关性

作者&#xff1a;来自 Elastic Carlos Delgado 仅执行向量搜索以找到与查询最相似的结果是不够的。通常需要过滤来缩小搜索结果。本文解释了在 Elasticsearch 和 Apache Lucene 中向量搜索的过滤是如何工作的。 Elasticsearch 拥有丰富的新功能&#xff0c;帮助你为自己的用例构…

Linux 性能调优之 OOM Killer 的认知与观测

写在前面 博文内容涉及到OOM Killer机制,以及利用 Cgroup/dmesg/BPF 观测 OOM Killer 事件,包括云原生环境下的 OOM Killer 机制的简单介绍 这是内存调优的最后一篇,之后会分享一些网络调优相关内容 理解不足小伙伴帮忙指正 😃,生活加油 我不再将这个世界与我所期待的,塑…

webrtc之高通滤波——HighPassFilter源码及原理分析

文章目录前言一、导读二、高通滤波过程1.HighPassFilter的创建1&#xff09;HighPassFilter的作用2&#xff09;开启条件3&#xff09;开启配置2.高通滤波整体过程1&#xff09;触发时机2&#xff09;滤波器创建3&#xff09;高通滤波过程三、算法实现1.原理1&#xff09;滤波器…

《sklearn机器学习——聚类性能指数》同质性,完整性和 V-measure

函数&#xff1a;homogeneity_score 参数&#xff1a; labels_true: array-like, shape [n_samples] 样本的真实标签。 labels_pred: array-like, shape [n_samples] 样本的预测标签。返回值&#xff1a; h: float 同质性得分&#xff0c;在0到1之间&#xff0c;值越大表示聚…

HarmonyOS 应用开发新范式:深入剖析 Stage 模型与 ArkTS 状态管理

好的&#xff0c;请看这篇关于 HarmonyOS 应用开发中 Stage 模型与 ArkTS 状态管理的技术文章。 HarmonyOS 应用开发新范式&#xff1a;深入剖析 Stage 模型与 ArkTS 状态管理 引言 随着 HarmonyOS 4、5 的发布以及 API 12 的迭代&#xff0c;HarmonyOS 的应用开发范式已经全面…

一个Java的main方法在JVM中的执行流程

一个Java的main方法在JVM中的执行流程可以分为​​四大阶段​​&#xff1a;​​加载 -> 链接 -> 初始化 -> 执行​​。// HelloWorld.java public class HelloWorld {public static void main(String[] args) {String message "Hello, JVM!";System.out.p…

聚焦诊断管理(DM)的传输层设计、诊断服务器实现、事件与通信管理、生命周期与报告五大核心模块

聚焦诊断管理(DM)的传输层设计、诊断服务器实现、事件与通信管理、生命周期与报告五大核心模块,明确 UDS(ISO 14229-1)与 SOVD(ASAM 服务化诊断)的功能逻辑、交互流程及规范性要求(SWS_DM 系列)。 1 UDS 传输层(UDS Transport Layer) 作为 DM 与诊断客户端的 UDS …

关于npm的钩子函数

一、npm scripts 的生命周期钩子&#xff08;Lifecycle Scripts&#xff09; npm 提供了一些 ​​特殊的 script 名称​​&#xff0c;它们是 ​​生命周期钩子​​&#xff0c;会在特定时机 ​​自动执行​​。这些钩子包括&#xff1a; 1.prepublishOnly(在 npm publish之前执…

167.在Vue3中使用OpenLayers模仿共享单车,判断点是否放在规划的电子围栏内

一、前言大家好&#xff0c;这里分享一个 Vue3 OpenLayers 的小案例&#xff1a; 模仿共享单车的电子围栏功能&#xff0c;用户在地图上绘制停泊点时&#xff0c;系统会自动判断该点是否在规划好的电子围栏内&#xff08;多边形或圆形&#xff09;。这个功能在实际项目中有很大…

键盘上面有F3,四,R,F,V,按下没有反应,维修记录

打开游戏&#xff0c;按了好几遍F&#xff0c;结果都没反应&#xff0c;但是左右上下行走是没问题的。一脸懵逼&#xff1f;&#xff1f;&#xff1f;打开键盘测试网页&#xff0c;发现有一列没反应&#xff0c;F1不是&#xff0c;F1我定义了一个快捷键&#xff0c;跟测试冲突了…

8051单片机-成为点灯大师

第三章 成为点灯大师 1. 硬件设计 上一章说到&#xff0c;怎么点亮LED灯&#xff0c;很简单啊&#xff0c;就是把P2口设置成低电平就行了。接下来让我们更进一步&#xff0c;完成LED闪烁、流水灯实验2. 软件设计 2.1 LED闪烁实验 为了使LED闪烁&#xff0c;我们自然而然的想到要…

Rust 日志库完全指南:从入门到精通

GitHub 仓库: https://github.com/zhouByte-hub/rust-study ⭐ 如果这个项目对您有帮助&#xff0c;请给我一个 star&#xff01; 在 Rust 生态系统中&#xff0c;日志处理是一个至关重要的环节。无论是开发小型应用还是大型系统&#xff0c;良好的日志记录都能帮助我们追踪问题…

【科研绘图系列】R语言绘制论文合集图

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍 数据准备与过滤 统计分析 可视化绘图 抗药性分析 系统发育分析 加载R包 数据下载 Supp figure 1 Fig 1a Fig 1c Fig 1d Fig 1e Fig 1f Supp figure 3 Supp figure 4 Supp figure 5…

【c++】从三个类的设计看软件架构的哲学思考

从三个类的设计看软件架构的哲学思考 文章目录从三个类的设计看软件架构的哲学思考前言一、OP类&#xff1a;系统工程的安全守护者设计特点设计哲学适用场景现实类比二、VarReviser类&#xff1a;版本控制的严谨管理者设计特点设计哲学适用场景现实类比三、Model类&#xff1a;…

人工智能优化SEO关键词的实战策略

本文聚焦智能技术如何革新关键词优化实践&#xff0c;系统解析提升网站排名的核心路径。重点探讨语义分析如何精准匹配用户意图、长尾词智能挖掘怎样解锁高潜力流量&#xff0c;并详解工具筛选高转化关键词的五大实用策略。通过实战案例说明技术如何突破流量增长瓶颈&#xff0…

【c++】c++第一课:命名空间

文章目录1.C的第⼀个程序2.命名空间2.1 namespace的价值2.2 namespace的定义2.3 命名空间使⽤最新的c标准&#xff08;建议收藏&#xff09; 1.C的第⼀个程序 C兼容C语⾔绝⼤多数的语法&#xff0c;所以C语⾔实现的helloworld依旧可以运⾏&#xff0c;C中需要把定义⽂件代码后…