vmalloc 实现

来源:互联网 发布:misumi 选型软件 编辑:程序博客网 时间:2024/06/05 21:54
vmalloc 简介
。vmalloc区为非连续内存分区,首地址为 VMALLOC_START,结束地址为VMALLOC_END
。由若干vmalloc子区域组成,每个vmalloc子区域间隔4KB,作为安全隔离区防止非法访问
。用vm_struct表示每个vmalloc子区域,每次调用vmalloc()在内核成功申请一段连续虚拟内存后,都会对应一个vm_struct子区域
。所有的vmalloc子区域组成一个链表,表头指针为 vmlist.
vmalloc 基本流程
。size 修正为PAGE_SIZE的整数倍,保证对齐
。在vmalloc内存范围内查找一块合适的虚拟地址子内存空间,存储到vm_struct结构中.
。为申请到的vm_struct 子内存空间分配不连续的物理页框(physical frame)
。实现连续的vm_struct子内存空间到非连续的物理页框(physical frame)之间的映射.

vmalloc 区域描述图

vm_struct 结构体(kernel 3.10)
1
2
3
4
5
6
7
8
9
10
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;
};
结构体解析
next
所有的vm_struct子区域组成一个vmlist链表,next指针指向下一个vm_struct 节点地址
addr
vmalloc() 最终在内核空间申请一个vm_struct 内存子区域,addr指向该内存子区域首地址
size
表示该vm_struct子区域的大小
flags
表示该非连续内存区的类型标记
pages
指针数组成员是struct page*类型指针,每个成员都关联一个映射到该虚拟内存区的物理页框
nr_pages
指针数组pages中page结构的总数
phys_addr
通常为0,当使用ioremap()映射一个硬件设备的物理内存时才填充此字段
caller
返回地址

vmalloc() 分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void *vmalloc(unsigned long size)
{
    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)
{
    return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
                    node, __builtin_return_address(0));
}
static void *__vmalloc_node(unsigned long size, unsigned long align,
                gfp_t gfp_mask, pgprot_t prot,
                int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, prot, 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, int node, const void *caller)
{
    ...
}
vmalloc() 函数内部封装成了__vmalloc_node_rang ,相关参数解析如下:
size
需要申请子内存的大小,通过vmalloc()传过来
align 
表示将所申请的内存区分为几个部分,1表示size大小的虚拟内存区作为一个整体
start 
vmalloc区域范围从 VMALLOC_START 开始
end 
vmalloc区域范围到 VMALLOC_END 结束
gfp_mask 
页面分配标志,GFP_KERNEL | __GFP_HIGHMEM 表示将从内核高端内存区分配内存空间
prot 
描述当前页的保护标志
node 
表示在哪个节点上(struct pg_data_t)为这段子内存区分配空间,-1表示在当前节点分配
caller 
返回地址



__vmalloc_node_range() 分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, 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;
 
    area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,
                  start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;
 
    addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
    if (!addr)
        return NULL;
 
    clear_vm_unlist(area);
    kmemleak_alloc(addr, real_size, 3, gfp_mask);
    return addr;
 
fail:
    warn_alloc_failed(gfp_mask, 0,
              "vmalloc: allocation failure: %lu bytes\n",
              real_size);
    return NULL;
}
__vmalloc_node_range() 函数主要干了以下事情:
LINE 9
修正获取内存的size,PAGE_ALIGN 将size的大小修改成PAGE_SIZE的整数倍,假设要申请1KB的内存区,那么实际上分配的是PAGE_SIZE(4KB)大小 的区域,然后进行size合法性检查,若不合法则返回NULL,申请内存失败
LINE 13
在vmalloc区域VMALLOC_START,VMALLOC_END 范围内申请一块合适大小的子内存区vm_struct,这部分由函数__get_vm_area_node()来实现
LINE 18
为申请到的子内存区vm_struct 分配物理页框(physical frame),将不连续的physical frame分别映射到连续的vm_struct子内存区中,这部分由函数__vmalloc_area_node()来实现
LINE 22
~
LINE 24
新分配的vm_struct区域都有VM_UNLIST标记表明未完全初始化,这里初始化完成后,清除掉此标记,由函数clear_vm_unlist()来实现,表明已经完成VA->PA的映射,kmemleak_alloc()函数是调试用,最后返回addr.


__get_vm_area_node()分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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) {
        int bit = fls(size);
 
        if (bit > IOREMAP_MAX_ORDER)
            bit = IOREMAP_MAX_ORDER;
        else if (bit < PAGE_SHIFT)
            bit = PAGE_SHIFT;
 
        align = 1ul << bit;
    }
 
    size = PAGE_ALIGN(size);
    if (unlikely(!size))
        return NULL;
 
    area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
    if (unlikely(!area))
        return NULL;
 
    size += PAGE_SIZE;
 
    va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
    if (IS_ERR(va)) {
        kfree(area);
        return NULL;
    }
 
    if (flags & VM_UNLIST)
        setup_vmalloc_vm(area, va, flags, caller);
    else
        insert_vmalloc_vm(area, va, flags, caller);
 
    return area;
}
__get_vm_area_node() 函数主要动作有:
LINE 20
修正获取内存的size,修正为PAGE_SIZE的整数倍大小,实现页对齐;
LINE 24
使用kmalloc分配一段连续内存,用于存储vm_struct 结构
LINE 28
每个vm_struct子区域之间有4KB大小的安全间隙,所以需要加上PAGE_SIZE的偏移
LINE 30

将在vmalloc整个非连续内存区域范围内查找一块size大小的子内存区,该函数先遍历整个vmap_area_list链表,依次比对链表中每个vmap_area子区域大小,直到找到合适的内存区域为止, 由 alloc_vmap_area()函数实现
LINE 37
将查找到的vmap_area 加载到vm_struct子内存中,然后将这个子vm_struct子内存区插入到整个vmlist链表中,由setup_vmalloc_vm()函数实现
alloc_vmap_area()分析
以下为查找va区域的关键比对代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    struct vmap_area *va;
    struct rb_node *n;
    unsigned long addr;
    int purged = 0;
    struct vmap_area *first;
 
    ...
    ...
 
        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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct vmap_area *tmp;
tmp = rb_entry(n, struct vmap_area, rb_node);
 
#define rb_entry(ptr, type, member) container_of(ptr, type, member)
 
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:  the pointer to the member.
 * @type: the type of the container struct this is embedded in.
 * @member:   the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
    (type *)( (char *)__mptr - offsetof(type,member) );})

LINE 17~
LINE 27上面代码的  tmp = rb_entry(n, struct vmap_area, rb_node) 只通过上下文明白大概意思,实现细节没看懂.
----就是树结构,需要复习 data struct 课程.


__vmalloc_area_node()实现
__get_vm_area_node()创建完新的vm_struct子内存区后,需要通过__vmalloc_area_node()为这个字内存区域分配物理页.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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;
    array_size = (nr_pages * sizeof(struct page *));
 
    area->nr_pages = nr_pages;
    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                PAGE_KERNEL, node, caller);
        area->flags |= VM_VPAGES;
    else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }
    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;
}

LINE 9
~
LINE 10
根据PAGE_SIZE计算所需要的内存映射页框(frame)数,保存在nr_pages中,根据nr_pages计算出pages指针数组的大小array_size,pages指针数组每个元素指向一个用于描述物理页框(frame)的page 结构.LINE 15
~
LINE 19如果pages指针数组大小超过一个PAGE_SIZE的大小(4kB),那么将递归调用
__vmalloc_node()为其分配空间,否则通过kmalloc_node()为pages数组分配一段连续内存空间,此段内存空间位于kernel空间的物理内存线性映射区域.
LINE 29
~
LINE 43通过一个循环,为pages指针数组中的每个page结构分配物理页框(frame),这里的page结构只是用于藐视物理页框结构,不是代表物理内存,如果node<0,说明未指定物理内存所在节点,使用alloc_page()分配一个页框,否则通过alloc_page_node()在指定的节点上分配物理页框,然后把刚刚分配的page装载到pages[i]中.LINE 46上面完成了分配所需要的物理页框(frame),然后由map_vm_area()完成pages数组中每个页框到连续vmalloc子区的映射关系.





















0 0
原创粉丝点击