不连续页分配器

不连续页分配器

在设备长时间运行后,内存碎片话,连续的物理页比较稀缺;伙伴分配器和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区分配内存;能分配大块内存;

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值