不连续页分配器

news/2024/7/20 14:06:20 标签: linux kernel, 内存管理

不连续页分配器

在设备长时间运行后,内存碎片话,连续的物理页比较稀缺;伙伴分配器和slab块分配器,分配的内存物理上是连续的;在这种情况下,如果需要分配长度超过一页的内存块,可以使用不连续页分配器,分配虚拟地址连续但物理地址不连续的内存块;

编程接口

不连续页分配器提供以下编程接口

  1. vmalloc

分配不连续的物理页并且把物理页映射到连续的虚拟地址空间;

/**
 *	vmalloc  -  allocate virtually contiguous memory
 *	@size:		allocation size
 *	Allocate enough pages to cover @size from the page level
 *	allocator and map them into contiguous kernel virtual space.
 *
 *	For tight control over page level allocator and protection flags
 *	use __vmalloc() instead.
 */
void *vmalloc(unsigned long size);
  1. vfree

释放vmalloc分配的物理页和虚拟地址空间;

/**
 *	vfree  -  release memory allocated by vmalloc()
 *	@addr:		memory base address
 *
 *	Free the virtually continuous memory area starting at @addr, as
 *	obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is
 *	NULL, no operation is performed.
 *
 *	Must not be called in NMI context (strictly speaking, only if we don't
 *	have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling
 *	conventions for vfree() arch-depenedent would be a really bad idea)
 *
 *	NOTE: assumes that the object at @addr has a size >= sizeof(llist_node)
 */
void vfree(const void *addr);
  1. vmap

把已经分配的不连续物理页映射到连续的虚拟地址空间;

/**
 *	vmap  -  map an array of pages into virtually contiguous space
 *	@pages:		array of page pointers
 *	@count:		number of pages to map
 *	@flags:		vm_area->flags
 *	@prot:		page protection for the mapping
 *
 *	Maps @count pages from @pages into contiguous kernel virtual
 *	space.
 */
void *vmap(struct page **pages, unsigned int count,
			unsigned long flags, pgprot_t prot);
  1. vunmap

释放vamp分配的虚拟地址空间;

/**
 *	vunmap  -  release virtual mapping obtained by vmap()
 *	@addr:		memory base address
 *
 *	Free the virtually contiguous memory area starting at @addr,
 *	which was created from the page array passed to vmap().
 *
 *	Must not be called in interrupt context.
 */
void vunmap(const void *addr);

有了不连续页分配器,在连续物理内存不足的情况下,就可以使用虚拟地址连续、物理地址不连续的内存;所以内核还提供了以下接口:

  1. kvmalloc

先尝试使用kmalloc分配内存块,如果失败,则使用vmalloc分配不连续的物理页;

/*attempt to allocate physically contiguous memory, but upon
 * failure, fall back to non-contiguous (vmalloc) allocation.
*/
void *kvmalloc(size_t size, gfp_t flags);
  1. kvfree

检查内存块是否是通过vmalloc分配的;如果是则使用vfree释放,否则使用kfree;

void kvfree(const void *addr)
{
	if (is_vmalloc_addr(addr))
		vfree(addr);
	else
		kfree(addr);
}

数据结构

不连续页分配器结构如下图:
image.png
每个虚拟内存区域对应一个vmap_area;每个vmap_area关联一个vm_struct;

struct vmap_area

vmap_area和vm_area_struct有什么区别?
vmap_area是内核虚拟地址空间,vm_area_struct是用户空间虚拟地址;

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;
};
  • va_start,起始虚拟地址;
  • va_end,结束虚拟地址;
  • flags,如果flags设置了标志位VM_VM_AREA,表示成员vm指向一个vm_struct实例,即vm_area关联一个vm_struct实例;
  • rb_node,红黑树节点,用来把vmap_area实例加入到根节点为vmap_area_root的红黑树中,虚拟地址作为key,提高查找效率;
  • list,链表节点,用来把vmap_area实例加入到头节点为vmap_area_list的链表中,该链表按虚拟地址从小到大排序;

sturct vm_struct

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;
};

addr,起始虚拟地址;
size,虚拟内存区域的长度;
flags,如果设置了VM_ALLOC,表示虚拟内存区域是使用vmalloc分配的;
pages,表示page指针数组;
nr_pages,页数;
next,指向下一个vm_struct实例;仅在不连续页分配器初始化以前使用;
phys_addr,起始物理地址;仅在不连续页分配器初始化以前使用;

如果虚拟内存区域是使用vmap分配的,vm_struct实例的差别是:成员flags没有设置VM_ALLOC,成员pages是NULL,成员nr_pages是0;
image.png

技术原理

vmalloc虚拟地址空间范围

vmalloc虚拟地址空间的范围是[VMALLOC_START,VMALLOC_END),每种处理器架构都需要定义这两个宏,例如ARM64架构定义如下:

/*
 * VMALLOC range.
 *
 * VMALLOC_START: beginning of the kernel vmalloc space
 * VMALLOC_END: extends to the available space below vmmemmap, PCI I/O space
 *	and fixed mappings
 */
#define VMALLOC_START		(MODULES_END)
#define VMALLOC_END		(PAGE_OFFSET - PUD_SIZE - VMEMMAP_SIZE - SZ_64K)

MODULES_END,内核模块区域的结束地址;
PAGE_OFFSET,线性映射区域的起始地址;
PUD_SIZE,一个页上层目录表项映射的地址空间长度;在页表中进行介绍;
VMEMMAP_SIZE,vmemmap区域的长度;
vmalloc虚拟地址空间的起始地址等于内核模块区域的结束地址;
vmalloc虚拟地址空间的结束地址,等于(线性映射区域的起始地址-一个页上层目录表项映射的地址空间长度-vmemmap区域的长度-64KB);

vmalloc函数执行步骤

函数vmalloc的执行过程分为3步:

  1. 分配虚拟内存区域;

分配vmap_area实例和vm_struct实例;之后遍历vmap_area_list,在两个相邻vmap_area之间找到一个足够大的空洞,如果找到了,把起始虚拟地址和结束虚拟地址保存在新的vmap_area实例中,将新的vmap_area加入到vmap_area_list中;并把它加入到红黑树中;最后把新的vmap_area实例关联到vm_struct实例中;

  1. 分配物理页;

vm_struct实例的成员nr_pages存放页数n;分配page指针数组,数组的长度是n,vm_struct实例的成员pages指向page指针数组;之后连续执行n次如下操作:从页分配器(伙伴分配器)分配一个物理页,把物理页对应的page实例的地址存放到page指针数组中;

  1. 在内核的页表中把虚拟页映射到物理页;

内核的页表就是0号线程的页表;0号内核线程的进程描述符是全局变量init_task,成员active_mm指向全局变量init_mm,init_mm的成员pgd指向页全局目录swapper_pg_dir;

函数vmap和函数vmalloc的区别仅仅在于不需要分配物理页;

linux中常用内存分配函数

用户空间

malloc/calloc/realloc/free

glibc提供的标准接口;申请的内存,虚拟地址连续,物理地址不一定连续;从堆中申请内存,使用系统调用为brk/mmap;

mmap/munmap

场景:将文件利用虚拟内存技术映射到内存当中,直接操作内存,提供文件访问速度;

brk/sbrk

brk也用于内存映射,通常用于调整堆内存的大小;

内核空间

_get_free_page/_get_free_pages

伙伴系统,页分配器,分配的内存物理页是连续的;

kmalloc/kcalloc/kfree

slab,块分配器,基于伙伴系统管理小块内存,分配的内存物理页是连续的;分配小块内存,大小有限,不如vmalloc大;

vmalloc/vfree

不连续页分配器,分配的内存虚拟地址连续,物理地址不连续;从vmalloc区分配内存;能分配大块内存;


http://www.niftyadmin.cn/n/5471201.html

相关文章

Vue使用高德地图(快速上手)

1.在高德平台注册账号 2.我的 > 管理管理中添加Key 3.安装依赖 npm i amap/amap-jsapi-loader --save 或 yarn add amap/amap-jsapi-loader --save 4.导入 AMapLoade import AMapLoader from amap/amap-jsapi-loader; 5.直接上代码,做好了注释(初…

信创环境ES索引管理脚本:close, delete

背景 elastic-curator在信创环境无现成安装包,且现成一般无法联网,此时通过脚本管理es索引是最佳选择。 1, 脚本内容: es-close-del.sh [rootmyprojtest001 ]# cat es-close-del.sh #/bin/bash#elastic地址 ELASTIC_URL127.0.0.1:9200 #默认的删除时间…

重温OKHTTP源码

本文基于OkHttp4.12.0源码分析 官方地址 概括 本篇主要是对okhttp开源库的一个详细解析,包含详细的请求流程分析、各大拦截器的解读等。 使用方法 同步请求:创建一个OKHttpClient对象,一个Request对象,然后利用它们创建一个Ca…

creo扫描杯子学习笔记

creo扫描杯子学习笔记 扫描2要素: 轨迹, 截面。 多用于曲线扫描,区别于拉伸命令。 大小自定 旋转扫描 抽壳 草绘把手 扫描把手 复制曲面 实例化切除 成型

单例模式以及线程安全问题

单例模式的概念 单例模式是指的是整个系统生命周期内,保证一个类只能产生一个实例对象 保证类的唯一性 。 通过一些编码上的技巧,使编译器可以自动发现咱们的代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错。 …

C++--对象作为返回值-----拷贝构造函数不执行的问题解决方案

1.问题现象 本来func函数返回p1,实际上p1会先拷贝一份传递给test函数里面的p,这个时候会执行拷贝构造函数,但是实际上的输出并没有; 而且执行拷贝构造函数的时候,因为生成p1的副本,所以我们打印的地址应该不相同&…

Git安装教程(图文安装)

Git Bash是git(版本管理器)中提供的一个命令行工具,外观类似于Windows系统内置的cmd命令行工具。 可以将Git Bash看作是一个终端模拟器,它提供了类似于Linux和Unix系统下Bash Shell环境的功能。通过Git Bash,用户可以在Windows系统中运行基于…

C++语法代码总结

1.函数重载 2.引用 函数重载: 函数重载概念:是函数的一种特殊情况,C允许在同一作用域中声明几个功能类似的同名函数, 这些同名函数的形参列表(参数个数或类型或顺序不同),常用来处理类似数据…