linux vmalloc

参考:https://zhuanlan.zhihu.com/p/452710310
内核版本:4.4.0

1 主要接口

主要接口就2个, 1个是申请虚拟内存 vmalloc, 一个是释放虚拟内存 vfree

/*
 * 申请虚拟内存, 虚拟地址空间连续, 但是物理地址不连续
 * size: 大小
 * 成功返回地址, 失败返回NULL
 */
void *vmalloc(unsigned long size)
/*
 * 释放有vmalloc申请的虚拟内存
 * addr: 要释放的虚拟内存的地址
 */
void vfree(const void *addr)

2 主要结构体

申请虚拟内存涉及的结构体比较简单,主要就2个,因此可以先全部过一下

2.1 struct vm_struct

// 用于存储vmalloc区间
struct vm_struct {
	struct vm_struct	*next; // 下个vm_struct
	void			*addr; // 虚拟地址
	unsigned long		size; // 虚拟空间大小
	unsigned long		flags; // 标志位
	struct page		**pages; // 当指针数组用,存放物理页
	unsigned int		nr_pages; // 有多个物理页
	phys_addr_t		phys_addr; // 当使用ioremap 才会使用到
	const void		*caller; //  保存调用函数的地址
};

flags标志位的主要内容如下

/* bits in flags of vmalloc's vm_struct below */
#define VM_IOREMAP		0x00000001	/* ioremap() and friends */
#define VM_ALLOC		0x00000002	/* vmalloc() */
#define VM_MAP			0x00000004	/* vmap()ed pages */
#define VM_USERMAP		0x00000008	/* suitable for remap_vmalloc_range */
#define VM_VPAGES		0x00000010	/* buffer for pages was vmalloc'ed */
#define VM_UNINITIALIZED	0x00000020	/* vm_struct is not fully initialized */
#define VM_NO_GUARD		0x00000040      /* don't add guard page */
#define VM_KASAN		0x00000080      /* has allocated kasan shadow memory */
/* bits [20..32] reserved for arch specific ioremap internals */

2.2 struct vmap_area

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 */
	// 当要删除时链接到 purge_list
	struct list_head purge_list;    /* "lazy purge" list */
	struct vm_struct *vm; // 指向vm_struct
	struct rcu_head rcu_head;
};

2.3 数据结构之间的关系

  1. 有一个全局的链表vmap_area_list, 所有的struct vmap_area->list 都链接到这里链表上, 按照起始地址从小到大排列
    同时还有一个全局的红黑树根结点 vmap_area_root , 所有的struct vmap_area->rb_node 也都链接在这个红黑树上,同样按照起始地址来排列

  2. 这里之所有把每个struct vmap_area 同时链接到链表和红黑树的原因如下:
    2.1 红黑树方便找到某个地址是否存在,具体函数见find_vm_area()
    2.2 链表方便遍历所有的struct vmap_area

  3. 数据结构之间的关系如下图
    请添加图片描述

3 源码分析

3.1 初始化

  1. 初始化有2处地方,一个是vmalloc_init(),函数里面会遍历 vmlist, 把里面的虚拟空间调用 __insert_vmap_area 链接起来
void __init vmalloc_init(void)
{
	struct vmap_area *va;
	struct vm_struct *tmp;
	int i;

	for_each_possible_cpu(i) {
		struct vmap_block_queue *vbq;
		struct vfree_deferred *p;

		vbq = &per_cpu(vmap_block_queue, i);
		spin_lock_init(&vbq->lock);
		INIT_LIST_HEAD(&vbq->free);
		p = &per_cpu(vfree_deferred, i);
		init_llist_head(&p->list);
		INIT_WORK(&p->wq, free_work);
	}

	/* Import existing vmlist entries. */
	// 将依据存在的vmalloc空间进来
	for (tmp = vmlist; tmp; tmp = tmp->next) {
		va = kzalloc(sizeof(struct vmap_area), GFP_NOWAIT);
		va->flags = VM_VM_AREA;
		va->va_start = (unsigned long)tmp->addr;
		va->va_end = va->va_start + tmp->size;
		va->vm = tmp;
		__insert_vmap_area(va);
	}

	vmap_area_pcpu_hole = VMALLOC_END;

	vmap_initialized = true;
}

vmlist 是一个全局的指针, 通过函数vm_area_add_early() 把虚拟空间链接到上面

// 全局vmlist,早期初始化会将虚拟地址空间链接到这上面来
static struct vm_struct *vmlist __initdata;

// 链接到 vmlist的函数
void __init vm_area_add_early(struct vm_struct *vm)
{
	struct vm_struct *tmp, **p;

	BUG_ON(vmap_initialized);
	for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
		if (tmp->addr >= vm->addr) {
			BUG_ON(tmp->addr < vm->addr + vm->size);
			break;
		} else
			BUG_ON(tmp->addr + tmp->size > vm->addr);
	}
	vm->next = *p;
	*p = vm;
}

其中的一个调用流程如下
在这里插入图片描述

  1. 另一个是在模块初始化注册了一个/proc/vmallocinfo调试节点, 用于查看申请的虚拟内存空间
static const struct file_operations proc_vmalloc_operations = {
	.open		= vmalloc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release_private,
};

static int __init proc_vmalloc_init(void)
{
	proc_create("vmallocinfo", S_IRUSR, NULL, &proc_vmalloc_operations);
	return 0;
}
module_init(proc_vmalloc_init);

在这里插入图片描述

3.2 vmalloc

申请虚拟区间分为2大步骤

  1. 找到一块可用的虚拟区间
  2. 申请物理内存并建立映射

创建一个虚拟内存区间需要调用4次申请内存的函数
1)给 struct vm_struct 申请空间
2)给 struct vmap_area 申请空间
3)给 struct vmap_area->pages 申请空间用于存放物理页
4)申请物理页, 存放到 struct vmap_area->pages 中

下面开始源码分析

  1. 前面几个函都是简单的封装 vmalloc->__vmalloc_node_flags->__vmalloc_node->__vmalloc_node_range
void *vmalloc(unsigned long size)
{
	 // 没有指定node
	 // __GFP_HIGHMEM 可以走高端内存分配
	 return __vmalloc_node_flags(size, NUMA_NO_NODE,  GFP_KERNEL | __GFP_HIGHMEM);
}
static inline void *__vmalloc_node_flags(unsigned long size,
					int node, gfp_t flags)
{
	// align = 1
	// __builtin_return_address 是返回函数的调用地址
	return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
					node, __builtin_return_address(0));
}
void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
{
	return __vmalloc_node(size, 1, gfp_mask, prot, NUMA_NO_NODE,
				__builtin_return_address(0));
}

内核启动时专门分配了一块区间给vmalloc,这里的区间为0xf0800000~0xff800000, 因此下面的宏
VMALLOC_START = 0xf0800000
VMALLOC_END = 0xff800000
在这里插入图片描述

static void *__vmalloc_node(unsigned long size, unsigned long align,
			    gfp_t gfp_mask, pgprot_t prot,
			    int node, const void *caller)
{
	// VMALLOC_START = 0xf0800000
	// VMALLOC_END = 0xff800000
	return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
				gfp_mask, prot, 0, node, caller);
}
  1. __vmalloc_node_range函数就是申请虚拟内存的主要实现

注:后面代码都会删除了一些不用的注释和非关键函数,方便主要代码的阅读

/*
 * 传进来的参数如下
 * size: 需要申请的空间大小
 * align:为1
 * start: 传进来的宏 VMALLOC_START-0xf0800000
 * end: 传进来的宏 VMALLOC_END-0xff800000
 * gfp_mask:GFP_KERNEL | __GFP_HIGHMEM
 * prot:PAGE_KERNEL
 * vm_flags: 0
 * node: NUMA_NO_NODE
 * caller: 函数调用地址
*/
void *__vmalloc_node_range(unsigned long size, unsigned long align,
			unsigned long start, unsigned long end, gfp_t gfp_mask,
			pgprot_t prot, unsigned long vm_flags, int node,
			const void *caller)
{
	struct vm_struct *area;
	void *addr;
	unsigned long real_size = size;

	// 因为后面会走伙伴系统申请物理页, 因此需要按页对齐
	size = PAGE_ALIGN(size);
	if (!size || (size >> PAGE_SHIFT) > totalram_pages)
		goto fail;

	// start 和 end 分别为 VMALLOC_START, VMALLOC_END
	// 申请一块vmalloc区域
	area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED |
				vm_flags, start, end, node, gfp_mask, caller);
	if (!area)
		goto fail;

	// 申请物理空间并进行映射
	addr = __vmalloc_area_node(area, gfp_mask, prot, node);
	if (!addr)
		return NULL;

	return addr;

fail:
	warn_alloc_failed(gfp_mask, 0,
			  "vmalloc: allocation failure: %lu bytes\n",
			  real_size);
	return NULL;
}

3.2.1 查找可用的虚拟空间

  1. __get_vm_area_node
static struct vm_struct *__get_vm_area_node(unsigned long size,
		unsigned long align, unsigned long flags, unsigned long start,
		unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
	struct vmap_area *va;
	struct vm_struct *area;

	// 不能在中断中运行
	BUG_ON(in_interrupt());
	if (flags & VM_IOREMAP)
		align = 1ul << clamp_t(int, fls_long(size),
				       PAGE_SHIFT, IOREMAP_MAX_ORDER);

	// 按页对齐
	size = PAGE_ALIGN(size);
	if (unlikely(!size))
		return NULL;

	// 给 vm_struct 申请空间, 第1次调用申请函数
	area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
	if (unlikely(!area))
		return NULL;

	// 空间加大一个物理页大小
	if (!(flags & VM_NO_GUARD))
		size += PAGE_SIZE;

	// 创建 vmap_area, 找到一段没有使用的vmalloc区间
	va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
	if (IS_ERR(va)) {
		kfree(area);
		return NULL;
	}

	// 参数赋值到vm_struct
	setup_vmalloc_vm(area, va, flags, caller);

	return area;
}
  1. __get_vm_area_node->alloc_vmap_area
    函数目的就是找到一块没有使用的区间, 然后把区间链接到红黑树和链接中
static struct vmap_area *alloc_vmap_area(unsigned long size,
				unsigned long align,
				unsigned long vstart, unsigned long vend,
				int node, gfp_t gfp_mask)
{
	struct vmap_area *va;
	struct rb_node *n;
	unsigned long addr;
	int purged = 0;
	struct vmap_area *first;

	// ...
	// 给 vmap_area 申请空间, 第2次调用申请函数
	va = kmalloc_node(sizeof(struct vmap_area),
			gfp_mask & GFP_RECLAIM_MASK, node);
	if (unlikely(!va))
		return ERR_PTR(-ENOMEM);
retry:
	spin_lock(&vmap_area_lock);

	if (!free_vmap_cache ||
			size < cached_hole_size ||
			vstart < cached_vstart ||
			align < cached_align) {
nocache:
		cached_hole_size = 0;
		free_vmap_cache = NULL;
	}
	/* record if we encounter less permissive parameters */
	cached_vstart = vstart;
	cached_align = align;

	/* find starting point for our search */
	/*
	 * 上次添加或删除vmalloc区域会更新 free_vmap_cache
	 * 一般认为虚拟内存是连续的,因此从上次操作完的位置开始方便快速找到内存区间
	 *
	 * 比如 现在存在区间 A[10,20]->B[20,30]->C[30,40]
	 * 如果先删除区间B[20,30], 此时free_vmap_cache会指向区间A[10,20]
	 * 下次在分配需要大小为5的空间就可以快速找到合适区间[20,5]
	 */
	if (free_vmap_cache) {
		first = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
		addr = ALIGN(first->va_end, align);
		if (addr < vstart)
			goto nocache;
		if (addr + size < addr)
			goto overflow;

	} else {
		addr = ALIGN(vstart, align);
		if (addr + size < addr)
			goto overflow;

		// 红黑树根结点
		n = vmap_area_root.rb_node;
		first = NULL;

		while (n) {
			// 遍历红黑树上的成员, 查找符合的区间
			struct vmap_area *tmp;
			tmp = rb_entry(n, struct vmap_area, rb_node);
			if (tmp->va_end >= addr) {
				first = tmp;
				if (tmp->va_start <= addr)
				// 找到 addr在[tmp->va_start, tmp->va_end]的区域
					break;
				n = n->rb_left;
			} else
				n = n->rb_right;
		}
		// first为空, 说明没有找到, 直接跳到后面
		if (!first)
			goto found;
	}

	/* from the starting point, walk areas until a suitable hole is found */
	// while循环, 需要找到   addr在[first->end, firsr->next->start]的区间
	while (addr + size > first->va_start && addr + size <= vend) {
		if (addr + cached_hole_size < first->va_start)
			cached_hole_size = first->va_start - addr;
		addr = ALIGN(first->va_end, align);
		if (addr + size < addr)
			goto overflow;

		// 遍历到链表的结尾就跳出
		if (list_is_last(&first->list, &vmap_area_list))
			goto found;

		first = list_entry(first->list.next,
				struct vmap_area, list);
	}

found:
	if (addr + size > vend)
		goto overflow;

	/*
	 * 走到这里有2种情况
	 *   1 找到了空闲的区间
	 *   2 没有找到空闲区间
	 */
	va->va_start = addr;
	va->va_end = addr + size;
	va->flags = 0;
	// 添加到 vmap_area_root 红黑树中
	__insert_vmap_area(va);
	// 更新 free_vmap_cache, 方便下一次查找
	free_vmap_cache = &va->rb_node;
	spin_unlock(&vmap_area_lock);
	return va;

	// 内存溢出
overflow:
	spin_unlock(&vmap_area_lock);
	if (!purged) {
		// 回收已经不用的vmalloc区间
		purge_vmap_area_lazy();
		purged = 1;
		// 再次尝试
		goto retry;
	}
	// ...
	return ERR_PTR(-EBUSY);
}
  1. __get_vm_area_node->alloc_vmap_area->__insert_vmap_area
    该函数功能比较简单,就是把 vmap_area 插入到全局的红黑树,链接到全局的链表
static void __insert_vmap_area(struct vmap_area *va)
{
	// 全局红黑树
	struct rb_node **p = &vmap_area_root.rb_node;
	struct rb_node *parent = NULL;
	struct rb_node *tmp;

	while (*p) {
		struct vmap_area *tmp_va;
		// 以vmalloc区域起始地址大小来排序, 找到插入的位置
		parent = *p;
		tmp_va = rb_entry(parent, struct vmap_area, rb_node);
		if (va->va_start < tmp_va->va_end)
			p = &(*p)->rb_left;
		else if (va->va_end > tmp_va->va_start)
			p = &(*p)->rb_right;
		else
			BUG();
	}

	// 设置父结点
	rb_link_node(&va->rb_node, parent, p);
	// 添加到红黑树中
	rb_insert_color(&va->rb_node, &vmap_area_root);

	/* address-sort this list */
	/*
	 * vmap_area_list 链表也是按照从小到大链接的
	 * rb_prev 用来找到前项结点
	 * 如果存在则连接到该节点后,如果不存在说明当前结点是最小的,直接链接到vmap_area_list的开头
	 */
	tmp = rb_prev(&va->rb_node);
	if (tmp) {
		struct vmap_area *prev;
		prev = rb_entry(tmp, struct vmap_area, rb_node);
		list_add_rcu(&va->list, &prev->list);
	} else
		list_add_rcu(&va->list, &vmap_area_list);
}

  1. 回到 __get_vm_area_node->setup_vmalloc_vm
    前面通过alloc_vmap_area 已经找到了可用的vmalloc区间,这里只是简单的做赋值
static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,
			      unsigned long flags, const void *caller)
{
	spin_lock(&vmap_area_lock);
	vm->flags = flags;
	vm->addr = (void *)va->va_start;
	vm->size = va->va_end - va->va_start;
	vm->caller = caller;
	va->vm = vm;
	va->flags |= VM_VM_AREA;
	spin_unlock(&vmap_area_lock);
}

3.2.2 申请物理内存并建立映射

  1. __vmalloc_area_node
    首先给vm_struct->pages 申请空间, 然后走伙伴系统申请物理页,并把物理对应的page赋值给pages[]
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
				 pgprot_t prot, int node)
{
	const int order = 0;
	struct page **pages;
	unsigned int nr_pages, array_size, i;
	const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
	const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;

	// 大小换换为页数
	nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
	array_size = (nr_pages * sizeof(struct page *));

	area->nr_pages = nr_pages;
	/* Please note that the recursion is strictly bounded. */
	// 第3次调用申请函数
	if (array_size > PAGE_SIZE) {
		// 大于4K还是调用vmalloc
		pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
				PAGE_KERNEL, node, area->caller);
		area->flags |= VM_VPAGES;
	} else {
		// 小于4K走kmem_cache 申请内存
		pages = kmalloc_node(array_size, nested_gfp, node);
	}
	area->pages = pages;
	if (!area->pages) {
		remove_vm_area(area->addr);
		kfree(area);
		return NULL;
	}

	for (i = 0; i < area->nr_pages; i++) {
		struct page *page;

		/*
		 * 走vmalloc调用进来, 参数为NUMA_NO_NODE
		 * 因为order=0, 因此每次申请了物理页大小为4K
		 *
		 */
		// 第4次调用申请函数
		if (node == NUMA_NO_NODE)
			page = alloc_page(alloc_mask);
		else
		page = alloc_pages_node(node, alloc_mask, order);

		if (unlikely(!page)) {
			/* Successfully allocated i pages, free them in __vunmap() */
			area->nr_pages = i;
			goto fail;
		}
		// 分多次走伙伴系统申请4K的物理页, 物理页地址可能不是连续的
		// 就是常说的虚拟地址空间连续, 但是物理地址空间不连续的原因
		area->pages[i] = page;
		if (gfpflags_allow_blocking(gfp_mask))
			cond_resched();
	}

	// 建立映射
	if (map_vm_area(area, prot, pages))
		goto fail;
	return area->addr;

fail:
	warn_alloc_failed(gfp_mask, order,
			  "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
			  (area->nr_pages*PAGE_SIZE), area->size);
	vfree(area->addr);
	return NULL;
}
  1. __vmalloc_area_node->map_vm_area->vmap_page_range
    把申请到的vmalloc地址和对应的物理页做映射,配置到页表中
int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page **pages)
{
	unsigned long addr = (unsigned long)area->addr;
	unsigned long end = addr + get_vm_area_size(area);
	int err;

	err = vmap_page_range(addr, end, prot, pages);

	return err > 0 ? 0 : err;
}
static int vmap_page_range(unsigned long start, unsigned long end,
			   pgprot_t prot, struct page **pages)
{
	int ret;

	ret = vmap_page_range_noflush(start, end, prot, pages);
	flush_cache_vmap(start, end);
	return ret;
}
  1. vmap_page_range_noflush->vmap_pud_range->vmap_pmd_range->vmap_pte_range
    层层调用, 最后调用到 vmap_pte_range
static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
		unsigned long end, pgprot_t prot, struct page **pages, int *nr)
{
	pte_t *pte;

	/*
	 * nr is a running index into the array which helps higher level
	 * callers keep track of where we're up to.
	 */

	// 申请pte
	pte = pte_alloc_kernel(pmd, addr);
	if (!pte)
		return -ENOMEM;
	do {
		// page就是在__vmalloc_area_node中申请的4K物理页
		struct page *page = pages[*nr];

		if (WARN_ON(!pte_none(*pte)))
			return -EBUSY;
		if (WARN_ON(!page))
			return -ENOMEM;
		// 设置页表
		set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
		(*nr)++;
	} while (pte++, addr += PAGE_SIZE, addr != end);
	return 0;
}

在这里插入图片描述

3.3 vfree

调用vmalloc申请的内存需要使用vfree来释放,释放分为2部分
部分1 先释放申请的物理页,释放struct vm_struct->pages, 释放 struct vm_struct 在 __vunmap中完成
部分2 struct vmap_area 的释放在 try_purge_vmap_area_lazy 中完成

  1. vfree 主要调用__vunmap
void vfree(const void *addr)
{
	BUG_ON(in_nmi());

	kmemleak_free(addr);

	if (!addr)
		return;
	// 如果在中断中就需要延后释放
	if (unlikely(in_interrupt())) {
		struct vfree_deferred *p = this_cpu_ptr(&vfree_deferred);
		if (llist_add((struct llist_node *)addr, &p->list))
			schedule_work(&p->wq);
	} else
		__vunmap(addr, 1);
}

3.3.1 释放部分1

  1. __vunmap
static void __vunmap(const void *addr, int deallocate_pages)
{
	struct vm_struct *area;

	// ...
	// 通过地址从红黑树中找到对应的vmalloc区域
	// 清除页表
	area = remove_vm_area(addr);
	if (unlikely(!area)) {
		WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
				addr);
		return;
	}

	if (deallocate_pages) {
		int i;

		for (i = 0; i < area->nr_pages; i++) {
			struct page *page = area->pages[i];

			// 释放物理页
			BUG_ON(!page);
			__free_page(page);
		}

		// 释放 vm_struct->pages, 存放page的空间
		if (area->flags & VM_VPAGES)
			vfree(area->pages);
		else
			kfree(area->pages);
	}

	// 释放 vm_struct
	kfree(area);
	return;
}
  1. __vunmap->remove_vm_area
struct vm_struct *remove_vm_area(const void *addr)
{
	struct vmap_area *va;

	// 根据地址找到对于应的 vmap_area
	va = find_vmap_area((unsigned long)addr);
	if (va && va->flags & VM_VM_AREA) {
		struct vm_struct *vm = va->vm;

		spin_lock(&vmap_area_lock);
		va->vm = NULL;
		va->flags &= ~VM_VM_AREA;
		spin_unlock(&vmap_area_lock);

		vmap_debug_free_range(va->va_start, va->va_end);
		kasan_free_shadow(vm);
		// 清空物理地址与虚拟区间的映射
		free_unmap_vmap_area(va);

		return vm;
	}
	return NULL;
}
  1. free_unmap_vmap_area->free_unmap_vmap_area_noflush->free_vmap_area_noflush
    这里把要释放的 vmap_area设置了对应的标志位,但是还没有释放 vmap_area
static void free_vmap_area_noflush(struct vmap_area *va)
{
	// 待清除的区间标志位设置为  VM_LAZY_FREE
	va->flags |= VM_LAZY_FREE;
	atomic_add((va->va_end - va->va_start) >> PAGE_SHIFT, &vmap_lazy_nr);
	// 当数量超过 lazy_max_pages 才会调用 try_purge_vmap_area_lazy
	if (unlikely(atomic_read(&vmap_lazy_nr) > lazy_max_pages()))
		try_purge_vmap_area_lazy();
}

3.3.2 释放部分2

  1. try_purge_vmap_area_lazy
    需要删除的 vmap_area在free_vmap_area_noflush中会把flag配置为VM_LAZY_FREE
    会先把需要删除的 vmap_area链接到valist, 然后遍历链表valist, 调用__free_vmap_area 进行删除
static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,
					int sync, int force_flush)
{
	static DEFINE_SPINLOCK(purge_lock);
	LIST_HEAD(valist);
	struct vmap_area *va;
	struct vmap_area *n_va;
	int nr = 0;

	// ...
	rcu_read_lock();
	list_for_each_entry_rcu(va, &vmap_area_list, list) {
		// 需要释放的标志位才会配置为 VM_LAZY_FREE
		if (va->flags & VM_LAZY_FREE) {
			if (va->va_start < *start)
				*start = va->va_start;
			if (va->va_end > *end)
				*end = va->va_end;
			nr += (va->va_end - va->va_start) >> PAGE_SHIFT;
			// 要删除的vmap_area 临时添加到链表valist上
			list_add_tail(&va->purge_list, &valist);
			va->flags |= VM_LAZY_FREEING;
			va->flags &= ~VM_LAZY_FREE;
		}
	}
	rcu_read_unlock();

	if (nr)
		atomic_sub(nr, &vmap_lazy_nr);

	if (nr || force_flush)
		flush_tlb_kernel_range(*start, *end);

	if (nr) {
		spin_lock(&vmap_area_lock);
		// 遍历valist链表,删除上面的 vmap_area
		list_for_each_entry_safe(va, n_va, &valist, purge_list)
			__free_vmap_area(va);
		spin_unlock(&vmap_area_lock);
	}
	spin_unlock(&purge_lock);
}
  1. try_purge_vmap_area_lazy->__free_vmap_area
    把要删除的vmap_area 从红黑树和链表中移除, 最后释放 vmap_area
static void __free_vmap_area(struct vmap_area *va)
{
	BUG_ON(RB_EMPTY_NODE(&va->rb_node));

	if (free_vmap_cache) {
		if (va->va_end < cached_vstart) {
			free_vmap_cache = NULL;
		} else {
			struct vmap_area *cache;
			cache = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
			if (va->va_start <= cache->va_start) {
				// 更新 free_vmap_cache, 方便下次快速查找区间
				free_vmap_cache = rb_prev(&va->rb_node);
				/*
				 * We don't try to update cached_hole_size or
				 * cached_align, but it won't go very wrong.
				 */
			}
		}
	}
	// 从红黑树中删除结点
	rb_erase(&va->rb_node, &vmap_area_root);
	RB_CLEAR_NODE(&va->rb_node);
	// 从全局链表 vmap_area_list 中删除结点
	list_del_rcu(&va->list);

	 */
	if (va->va_end > VMALLOC_START && va->va_end <= VMALLOC_END)
		vmap_area_pcpu_hole = max(vmap_area_pcpu_hole, va->va_end);

	// 最后释放 vmap_area
	kfree_rcu(va, rcu_head);
}
  1. 一次删除的函数调用情况
    在这里插入图片描述

4 总结

  1. 申请内存部分
    1.1)会从vmalloc中找到一段空闲的区间,地址上会是连续的
    1.2)地址信息保存到 struct vmap_area 和 struct vm_struct 中, 把 vmap_area 按照起始地址从小到大添加到红黑树和全局链表中
    1.3)向伙伴系统申请物理页, 这里的物理页都是4K组成,物理页的地址可能不连续
    1.4) 把vmalloc中申请的空间和申请的物理页做映射关系

  2. 释放内存
    2.1)会先把 vm_struct , 物理页,vm_struct->pages全部释放
    2.2)vmap_area的释放会做延后处理,当vmalloc剩余空间不够或需要释放的地址大小超过一定的数值才会调用__purge_vmap_area_lazy进行最后的释放

  3. 因为 vmap_area的延后释放,导致全局链表和红黑树上的信息也存在,当连续调用vmalloc()、vfree() 申请释放内存, 得到的虚拟地址是递增的。

测试demo, 每次申请1Mx5次, 发现每次地址是递增的,当达到一定数量后才会释放

在这里插入图片描述

测试demo源码

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>


#include "my_error.h"

/* 代码思路
	测试vmalloc
*/
#define VMALLOC_TEST_NUM (5)
#define VMALLOC_TEST_SIZE (1024 * 1024)
static void *p[VMALLOC_TEST_NUM];

static int vmalloc_test_init(void)
{
	int i;

	PRINT_INFO("size:%d, array_num:%d \n", VMALLOC_TEST_SIZE, VMALLOC_TEST_NUM);

	for (i = 0; i < VMALLOC_TEST_NUM; i++)
	{
		p[i] = vmalloc(VMALLOC_TEST_SIZE);
		if (!p[i])
		{
			PRINT_ERR("i:%d, vmalloc fail \n", i);
			goto OUT;
		}
	}


	PRINT_INFO("%s init \n", __FUNCTION__);

	return 0;
OUT:
	for (; i > 0; i--)
	{
		vfree(p[i]);
	}

	return -1;
}

static void vmalloc_test_exit(void)
{
	int i;

	for (i = 0; i < VMALLOC_TEST_NUM; i++)
	{
		vfree(p[i]);
	}

	PRINT_INFO("%s exit \n", __FUNCTION__);
}

module_init(vmalloc_test_init);
module_exit(vmalloc_test_exit);

MODULE_LICENSE("GPL");

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值