内存管理:vmalloc模块初始化及申请内存的主要流程
分析vmalloc模块初始化(vmalloc_init())及通过vmalloc申请内存(vmalloc())的主要流程
vmalloc模块初始化vmalloc_initvmalloc初始化: start_kernel()->mm_init()->vmalloc_init()
void __init vmalloc_init(void){ struct vmap_area *va; struct vm_struct *tmp; int i; /* * 遍历每CPU的vmap_block_queue和vfree_deferred变量并进行初始化; * vmap_block_queue是非连续内存块队列管理结构,主要是队列以及对应的保护锁 * vfree_deferred是vmalloc的内存延迟释放管理 */ 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); } /* 将挂接在vmlist链表的各项__insert_vmap_area()输入到非连续内存块的管理中 */ 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;}__insert_vmap_area
/* * 将struct vmap_area 塞入全局红黑树中;并塞入全局双向链表vmap_area_list中 * 1. while循环是对vmap_area_root红黑树进行查找,找到合适的位置后通过rb_link_node()插入红黑树; * 2. 在通过rb_insert_color()对红黑树进行平衡调整; * 3. 根据插入的红黑树的父子节点关系插入到vmap_area_list链表中; */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; /* 遍历红黑树,找到va合适的插入点 */ while (*p) { struct vmap_area *tmp_va; parent = *p; /* 找到parent节点所属的struct vmap_area结构体指针 */ 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(); } /* 找到合适的插入点后,把节点塞入树中;p为最终位置,parent为p的父节点 */ rb_link_node(&va->rb_node, parent, p); /* 调整颜色,翻转后使其变成标准红黑树 */ rb_insert_color(&va->rb_node, &vmap_area_root); /* 查找插入的内存块管理树的父节点,有则插入到该节点链表的后面位置,否则作为链表头插入到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 /* 作为链表头插入到vmap_area_list链表中 */ list_add_rcu(&va->list, &vmap_area_list);}
更多Linux内核视频教程文档资料免费领取后台私信【内核】自行获取。
内核学习网站:
Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
vmalloc内存申请__vmalloc_node_rangevmalloc()->__vmalloc_node_flags()->__vmalloc_node()->__vmalloc_node_range()/* vmalloc具体申请内存过程 * 1. 通过__get_vm_area_node()向slub系统申请结构体vm_struct空间 * 2. 在__vmalloc_area_node()通过查询红黑树、链表找到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; /* 首先对申请内存的大小做对齐后,如果大小为0或者大于总内存,则返回失败 */ size = PAGE_ALIGN(size); if (!size || (size >> PAGE_SHIFT) > totalram_pages) goto fail; /* 使用kmalloc,在slub中分配vm_struct数据结构体,并返回 */ area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED | vm_flags, start, end, node, gfp_mask, caller); if (!area) goto fail; /* 根据vm_struct的信息进行物理空间申请,建立页表映射 */ addr = __vmalloc_area_node(area, gfp_mask, prot, node); if (!addr) return NULL; /* * 标示内存空间已经初始化 */ clear_vm_uninitialized_flag(area); /* * 调用kmemleak_alloc()进行内存分配泄漏调测 */ kmemleak_alloc(addr, real_size, 2, gfp_mask); return addr;fail: warn_alloc_failed(gfp_mask, 0, "vmalloc: allocation failure: %lu bytes\n", real_size); return NULL;}__get_vm_area_node
/* 在vmalloc中寻找合适的虚拟内存 * 1. 通过kmalloc申请管理内存 vm_struct以及 vmap_area * 2. 通过alloc_vmap_area从vmalloc区中寻找内存,并插入红黑树中; */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; /* * 核心函数:kmalloc_node()->__kmalloc_node()申请管理结构体内存 * 1. 通过kmalloc_slab()寻找合适的slub申请内存 * 2. 通过slab_alloc_node()从slub中分配内存,具体过程可以参考[slub申请内存](%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%9D-slub%E7%94%B3%E8%AF%B7%E5%86%85%E5%AD%98/) */ area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!area)) return NULL; /* 如果没有标记,size要多算一个guard page用于分隔 */ if (!(flags & VM_NO_GUARD)) size += PAGE_SIZE; /* 申请指定的虚拟地址范围内的未映射空白;即申请不连续的物理内存 */ va = alloc_vmap_area(size, align, start, end, node, gfp_mask); if (IS_ERR(va)) { kfree(area); return NULL; } /* 将vmap_area结构体中数据同步到vm_struct结构体中 */ setup_vmalloc_vm(area, va, flags, caller); return area;}alloc_vmap_area
/* * 从vmalloc区取出一段虚拟地址vmalloc : 0xf0800000 - 0xff800000 ( 240 MB) * 1. alloc_vmap_area会缓存上次一次搜索的节点到free_vmap_cache, 这样是认为一般虚拟内存是按顺序连续分配的 * 2. 如果通过缓存没有搜索到就要从全局的vmap_area_root开始搜索: 先红黑树再链表 * a. 在红黑树中根据指定区间寻找合适的区间,找到一个区间正好包含vstart * b. 在该区间的链表后面寻找合适的空洞进行分配(也有可能在红黑树上找到的就是空洞就不用接着遍历了) */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; BUG_ON(!size);// 长度为0不行 BUG_ON(size & ~PAGE_MASK);// 长度不是页面整数倍不行 BUG_ON(!is_power_of_2(align));// 长度不是2的n次方不行 /* 先通过kmalloc申请管理结构体 struct vmap_area 内存,过程类似kzalloc_node(),少了一个内存初始化要求 */ va = kmalloc_node(sizeof(struct vmap_area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!va)) return ERR_PTR(-ENOMEM); /* * Only scan the relevant parts containing pointers to other objects * to avoid false negatives. */ kmemleak_scan_area(&va->rb_node, SIZE_MAX, gfp_mask & GFP_RECLAIM_MASK);retry: spin_lock(&vmap_area_lock); /* * free_vmap_cache记录着最近释放的或最近注册使用的不连续内存页面空间,是用以加快空间的搜索速度 * 1. 在红黑树中根据指定区间寻找合适的区间,找到一个区间正好包含vstart * 2. 在该区间的链表后面寻找合适的空洞进行分配(也有可能在红黑书上找到的就是空洞就不用接着遍历了) * 3. 记录本次分配情况 */ 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; /* 寻找符合条件的struct vmap_area区域, 如果上次搜索的节点非空,则从上次搜索的节点开始 */ if (free_vmap_cache) {/* 从上次分配的区域开始向后查找 */ /* 找到上一次分配节点的struct vmap_area结构体指针 */ first = rb_entry(free_vmap_cache, struct vmap_area, rb_node); /* 上次分配的结束地址向上对齐,得到本次可用的地址 */ addr = ALIGN(first->va_end, align); if (addr < vstart) /* 要找的是: va_begin< vstart <va_end; 缓存的末尾对齐之后超出了范围从头开始搜索 */ goto nocache; if (addr + size < addr) goto overflow;/* 整数溢出 */ } else { addr = ALIGN(vstart, align); if (addr + size < addr) goto overflow;/* 整数溢出 */ /* 第一次搜索,缓存free_vmap_cache为空,从全局vmap_area_root开始搜索 */ n = vmap_area_root.rb_node; first = NULL; /* 要找对齐之后的va_start<=vstart<=va_end的节点 */ 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) /* 在first为空的情况下,表示vstart为起始的虚拟地址空间未被使用过,将会直接对该虚拟地址空间进行分配 */ goto found; } /* * 若first不为空,意味着该空间曾经分配过,进入while分支进行处理 * 该循环是从first为起点遍历vmap_area_list链表管理的虚拟地址空间链表进行查找 * 如果找合适的未使用的虚拟地址空间或者遍历到了链表末尾,都表示找到了该空间 */ 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; /* 运行到这里,说明找到合适的地址区间,初始化KVA数据结构 */ 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); BUG_ON(va->va_start & (align-1)); BUG_ON(va->va_start < vstart); BUG_ON(va->va_end > vend); return va;overflow:/* 运行到这里,说明地址空间不足 */ spin_unlock(&vmap_area_lock); if (!purged) {/* 如果我们还没有进行KVA地址空间清理(如果幸运的话,我们可以清理出一些可用空间) */ /* 清理懒模式下没有及时释放的KVA空间,内核中经常用到懒模式,性能方面的原因 */ purge_vmap_area_lazy(); /* 设置标志,表示我们已经尝试过清理KVA空间了 */ purged = 1; /* 再试一次,如果还不行再返回错误 */ goto retry; } if (printk_ratelimit()) pr_warn("vmap allocation for size %lu failed: " "use vmalloc=<size> to increase size.\n", size); kfree(va); return ERR_PTR(-EBUSY);}__vmalloc_area_node
/* 根据vm_struct的信息进行物理空间申请,建立页表映射 */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; /* 计算本次申请的vmalloc共需要多少页内存 */ nr_pages = get_vm_area_size(area) >> PAGE_SHIFT; array_size = (nr_pages * sizeof(struct page *)); area->nr_pages = nr_pages; /* 根据需要的内存页数,申请管理结构体pages内存空间. */ if (array_size > PAGE_SIZE) { /* 如果需要多余1页内存,则通过vmalloc申请. */ pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM, PAGE_KERNEL, node, area->caller); area->flags |= VM_VPAGES; } else { /* 否则从slub中申请. */ 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; /* 从伙伴系统中申请内存. */ 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; } area->pages[i] = page; if (gfp_mask & __GFP_WAIT) cond_resched(); } /* * 物理内存申请成功后,进行内存映射. * vmap_page_range_noflush: 建立映射; * set_pte_at(&init_mm, addr, pte, mk_pte(page, prot)): 将页描述符对应的页框和页表项进行关联,映射关系被建立 * flush_cache_vmap:刷新缓存; * 详细过程可参考资料 */ 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内存释放vfree
/** * vfree - release memory allocated by vmalloc() * @addr: memory base address * * 释放通过以下函数申请的内存:vmalloc(), vmalloc_32() or __vmalloc(). * 如果当前CPU处于中断中,则需要异步释放 * 最终的释放操作都是由__vunmap()执行 */void vfree(const void *addr){ BUG_ON(in_nmi()); kmemleak_free(addr); if (!addr) return; if (unlikely(in_interrupt())) { /* * 如果当前释放操作在中断中,则将释放的内存空间信息加入到当前CPU的vfree_deferred管理链表中 * 继而通过schedule_work()唤醒工作队列,对内存进行一步释放操作 */ 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);}__vunmap
/* * 释放vmalloc内存 * deallocate_pages:0表示由vumap()调用;1表示由vfree()调用 * 涉及3块内存的释放: * 1. vmap_struct管理结构体内存:延迟释放 * 在free_vmap_area_noflush()中将vmp_area加入到vmap_purge_list链表 * 当加入该链表的数量超过一定值之后,才会通过try_purge_vmap_area_lazy()将vmap_area真正释放掉 * 2. page管理结构体:vfree(area->pages)、kfree(area->pages) * 3. vmalloc真实内存:__free_page(page); * 4. vm_struct管理结构体:kfree(area) */static void __vunmap(const void *addr, int deallocate_pages){ struct vm_struct *area; if (!addr) return; if (WARN(!PAGE_ALIGNED(addr), "Trying to vfree() bad address (%p)\n", addr)) return; /* * remove_vm_area()->find_vmap_area()->__find_vmap_area()通过addr在红黑树中找到对应节点对应的struct vmap_area结构体 * 再通过free_unmap_vmap_area()清除指定的虚拟地址 */ area = remove_vm_area(addr); if (unlikely(!area)) { WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n", addr); return; } debug_check_no_locks_freed(addr, area->size); debug_check_no_obj_freed(addr, area->size); 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); } if (area->flags & VM_VPAGES) vfree(area->pages); else kfree(area->pages); } kfree(area); return;}附录vm_struct
struct vm_struct { /* 使得内核可以将vmalloc区域中的所有子区域保存在一个单链表上. */ struct vm_struct *next; /* 定义了分配的子区域在虚拟地址空间中的起始地址. */ void *addr; /* size表示该子区域的长度. */ unsigned long size; /* 存储了与该内存区关联的标志集合, 它只用于指定内存区类型. */ unsigned long flags; /* 二维指针,指向page指针的数组;每个数组成员都表示一个映射到虚拟地址空间中的物理内存页的page实例. */ struct page **pages; /* 指定pages中数组项的数目,即涉及的内存页数目. */ unsigned int nr_pages; /* 仅当用ioremap映射了由物理地址描述的物理内存区域时才需要. */ phys_addr_t phys_addr; /* The first cache line has the info for VMA tree walking. */ const void *caller;};vmap_area
vmap_area表示内核空间的vmalloc区域的一个vmalloc,由rb_node和list进行串联
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;};vm_area_struct
/* * This struct defines a memory VMM memory area. There is one of these * per VM-area/task. A VM area is any part of the process virtual memory * space that has a special rule for the page-fault handlers (ie a shared * library, the executable area etc). * 用户态进程内存对应内核管理结构体;每个vm_area_struct代表进程的一个虚拟地址空间 */struct vm_area_struct { /* The first cache line has the info for VMA tree walking. */ unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end address within vm_mm. */ /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next, *vm_prev; struct rb_node vm_rb; /* * Largest free memory gap in bytes to the left of this VMA. * Either between this VMA and vma->vm_prev, or between one of the * VMAs below us in the VMA rbtree and its ->vm_prev. This helps * get_unmapped_area find a free area of the right size. */ unsigned long rb_subtree_gap; /* Second cache line starts here. */ struct mm_struct *vm_mm; /* 指向VMA所属进程的struct mm_struct结构. */ pgprot_t vm_page_prot; /* VMA访问权限. */ unsigned long vm_flags; /* VMA标志位. */ /* * For areas with an address space and backing store, * linkage into the address_space->i_mmap interval tree. */ struct { struct rb_node rb; unsigned long rb_subtree_last; } shared; /* * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma * list, after a COW of one of the file pages. A MAP_SHARED vma * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack * or brk vma (with NULL file) can only be in an anon_vma list. */ struct list_head anon_vma_chain; /* 用于管理RMAP反向映射 */ struct anon_vma *anon_vma; /* 用于管理RMAP反向映射 */ /* VMA操作函数合集,常用于文件映射. */ const struct vm_operations_struct *vm_ops; /* Information about our backing store: */ unsigned long vm_pgoff; /* 指定文件映射的偏移量,单位是页面;标识文件映射部分的起始位置 */ struct file * vm_file; /* 描述一个被映射的文件. 如果非文件映射,则NULL */ void * vm_private_data; /* was vm_pte (shared mem) */#ifndef CONFIG_MMU struct vm_region *vm_region; /* NOMMU mapping region */#endif#ifdef CONFIG_NUMA struct mempolicy *vm_policy; /* NUMA policy for the VMA */#endif};