参考: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_areastruct 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 数据结构之间的关系有一个全局的链表vmap_area_list, 所有的struct vmap_area->list 都链接到这里链表上, 按照起始地址从小到大排列 同时还有一个全局的红黑树根结点 vmap_area_root , 所有的struct vmap_area->rb_node 也都链接在这个红黑树上,同样按照起始地址来排列 这里之所有把每个struct vmap_area 同时链接到链表和红黑树的原因如下: 2.1 红黑树方便找到某个地址是否存在,具体函数见find_vm_area() 2.2 链表方便遍历所有的struct vmap_area 数据结构之间的关系如下图
3 源码分析3.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;
} 其中的一个调用流程如下 另一个是在模块初始化注册了一个/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大步骤 找到一块可用的虚拟区间 申请物理内存并建立映射
创建一个虚拟内存区间需要调用4次申请内存的函数 1)给 struct vm_struct 申请空间 2)给 struct vmap_area 申请空间 3)给 struct vmap_area->pages 申请空间用于存放物理页 4)申请物理页, 存放到 struct vmap_area->pages 中 下面开始源码分析 前面几个函都是简单的封装 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);
} __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 查找可用的虚拟空间__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;
} __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);
} __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);
} 回到 __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 申请物理内存并建立映射__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;
} __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;
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 中完成 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__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;
} __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;
} 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 释放部分2try_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);
} 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);
} 一次删除的函数调用情况
4 总结申请内存部分 1.1)会从vmalloc中找到一段空闲的区间,地址上会是连续的 1.2)地址信息保存到 struct vmap_area 和 struct vm_struct 中, 把 vmap_area 按照起始地址从小到大添加到红黑树和全局链表中 1.3)向伙伴系统申请物理页, 这里的物理页都是4K组成,物理页的地址可能不连续 1.4) 把vmalloc中申请的空间和申请的物理页做映射关系 释放内存 2.1)会先把 vm_struct , 物理页,vm_struct->pages全部释放 2.2)vmap_area的释放会做延后处理,当vmalloc剩余空间不够或需要释放的地址大小超过一定的数值才会调用__purge_vmap_area_lazy进行最后的释放 因为 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");
|