【linux 内存管理】深入理解linux内核架构 内存管理(5)vmalloc

一、内核中不连续页的分配

    我们知道物理上连续的映射对内核是最好的,但并不总能成功地使用。在分配一大块内存时,可能竭尽全力也无法找到连续的内存块。在用户空间中这不是问题,因为普通进程设计为使用处理器的分页机制,当然这会降低速度并占用TLB。在内核中也可以使用同样的技术。内核分配了其虚拟地址空间的一部分,用于建立连续映射。

 

    每个vmalloc分配的子区域都是自包含的,与其他vmalloc子区域通过一个内存页分隔,也就是各个子区域由1个内存页分隔。类似于直接映射和vmalloc区域之间的边界,不同vmalloc子区域之间的分隔也是为防止不正确的内存访问操作。

1、vmalloc函数

    

void *vmalloc(unsigned long size)
{
	return __vmalloc_node_flags(size, NUMA_NO_NODE,
				    GFP_KERNEL);
}

    vmalloc是一个接口函数,内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存。

    因为用于vmalloc的内存页总是必须映射在内核地址空间中,因此使用ZONE_HIGHMEM内存域的页要优于其他内存域。这使得内核可以节省更宝贵的较低端内存域,而又不会带来额外的坏处。因此,vmalloc(连同其他映射函数在3.5.8节讨论)是内核出于自身的目的(并非因为用户空间应用程序)使用高端内存页的少数情形之一。

    内核在管理虚拟内存中的vmalloc区域时,内核必须跟踪哪些子区域被使用、哪些是空闲的。为此定义了一个数据结构,将所有使用的部分保存在一个链表中。

    一下是两个数据结构都是描述虚拟内存区域的结构,这个两个结构最终都会插入到相应的红黑树中,暂时不明白为什么需要两个数据结构来管理

<vmalloc.h>
struct vm_struct 
{
    struct vm_struct *next;
    void *addr;
    unsigned long size;
    unsigned long flags;
    struct page **pages;
    unsigned int nr_pages;
    unsigned long phys_addr;
};
 addr定义了分配的子区域在虚拟地址空间中的起始地址。 size表示该子区域的长度。可以根
据该信息来勾画出vmalloc区域的完整分配方案。
 flags存储了与该内存区关联的标志集合,这几乎是不可避免的。它只用于指定内存区类型,
当前可选值有以下3个。
 VM_ALLOC指定由vmalloc产生的子区域。
 VM_MAP用于表示将现存pages集合映射到连续的虚拟地址空间中。
 VM_IOREMAP表示将几乎随机的物理内存区域映射到vmalloc区域中。这是一个特定于体系
结构的操作。
3.5.7节说明了后两个值如何使用。
 pages是一个指针,指向page指针的数组。每个数组成员都表示一个映射到虚拟地址空间中的
物理内存页的page实例。
nr_pages指定pages中数组项的数目,即涉及的内存页数目。
 phys_addr仅当用ioremap映射了由物理地址描述的物理内存区域时才需要。该信息保存在
phys_addr中。
 next使得内核可以将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 list_head purge_list;    /* "lazy purge" list */
    struct vm_struct *vm;
    struct rcu_head rcu_head;
};

 

    最终vmalloc函数调用的都是__vmalloc_node_range()函数,下面简单看下__vmalloc_node_range():

size:vmalloc申请的内存大小字节
align:调用时是1,内存对齐的base
start,end:是高端内存的起始位置,VMALLOC_START和VMALLOC_END(0xffffffff)
mask:申请内存需要的标记
prot:PAGE_KERNEL,不知道什么用
flags:调用时是0
caller:哪个函数调用的vmalloc,不明白什么用

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);
    /* 从虚拟内存中申请可用的区域 */
	area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED |
				vm_flags, start, end, node, gfp_mask, caller);
    /* 物理帧的申请,放入到area中的pages数组中,这里就是用到了伙伴系统的单页申请 */
	addr = __vmalloc_area_node(area, gfp_mask, prot, node);

    /* 将area插入到相应的红黑树种 */
	kmemleak_vmalloc(area, size, gfp_mask);

	return addr;

fail:
	warn_alloc(gfp_mask, NULL,
			  "vmalloc: allocation failure: %lu bytes", real_size);
	return NULL;
}


    下面分别来介绍__get_vm_area_node()和__vmalloc_area_node()

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

    /* 申请的内存进行对齐 */
	size = PAGE_ALIGN(size);
    /* 申请vm_struct结构的内存 */
	area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
	if (unlikely(!area))
		return NULL;
    /* 每个虚拟内存子区域之间都会有1个4k的间隔 */
	if (!(flags & VM_NO_GUARD))
		size += PAGE_SIZE;
    /* 查找vmap_area的红黑树,找到一个合适的位置,并将新的va结点插入到红黑树种 */
	va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
	if (IS_ERR(va)) {
		kfree(area);
		return NULL;
	}
    /* 根据新建的vmap_area *va 来给vm_struct *area赋值*/
	setup_vmalloc_vm(area, va, flags, caller);

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



	va = kmalloc_node(sizeof(struct vmap_area),
			gfp_mask & GFP_RECLAIM_MASK, node);

	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)
				break;
			n = n->rb_left;
		} else
			n = n->rb_right;
	}
	if (!first)
		goto found;


	/* from the starting point, walk areas until a suitable hole is found */
	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_next_entry(first, list);
	}

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

	va->va_start = addr;
	va->va_end = addr + size;
	va->flags = 0;
	__insert_vmap_area(va);
	free_vmap_cache = &va->rb_node;
	spin_unlock(&vmap_area_lock);


	return va;


}



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


    这个函数alloc_vmap_area很多地方没有明白,以后需要仔细再看一下这个红黑树结构,现在记录下目前的理解
一会如果有新的理解再更新。

__vmalloc_area_node()函数:

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node, const void *caller)
{
    const int order = 0;
    struct page **pages;
    unsigned int nr_pages, array_size, i;
    gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    /*需要分配的页面数量*/
    nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
    /*需要一个数组管理涉及到的物理页面地址即page指针,这个数组的大小*/
    array_size = (nr_pages * sizeof(struct page *));

    area->nr_pages = nr_pages;
    area->pages = pages;
    area->caller = caller;
    if (!area->pages) {
        remove_vm_area(area->addr);
        kfree(area);
        return NULL;
    }

    for (i = 0; i < area->nr_pages; i++) {
        struct page *page;
        gfp_t tmp_mask = gfp_mask | __GFP_NOWARN;
        /* 开始从伙伴系统申请单页帧 */
        if (node < 0)
            page = alloc_page(tmp_mask);
        else
            page = alloc_pages_node(node, tmp_mask, order);

        if (unlikely(!page)) {
            /* Successfully allocated i pages, free them in __vunmap() */
            area->nr_pages = i;
            goto fail;
        }
        area->pages[i] = page;
    }
    /* 虚拟地址与页帧的映射 */
    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;
}

    以上就是vmalloc的全部,很多遗留的问题没搞懂,之后有时间继续。

    这个博文不错,但是没解决我的疑问https://www.cnblogs.com/ck1020/p/7143199.html

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值