Chinaunix首页 | 论坛 | 博客
  • 博客访问: 294148
  • 博文数量: 44
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 1354
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-08 15:38
个人简介

人生像是在跑马拉松,能够完赛的都是不断地坚持向前迈进;人生就是像在跑马拉松,不断调整步伐,把握好分分秒秒;人生还是像在跑马拉松,能力决定了能跑短程、半程还是全程。人生其实就是一场马拉松,坚持不懈,珍惜时间。

文章分类

分类: LINUX

2017-05-14 00:26:48

前面分析了不连续页面管理的初始化以及申请的实现,最后以释放不连续页面空间vfree()收尾。

  1. 【file:/mm/vmalloc.c】
  2. /**
  3.  * vfree - release memory allocated by vmalloc()
  4.  * @addr: memory base address
  5.  *
  6.  * Free the virtually continuous memory area starting at @addr, as
  7.  * obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is
  8.  * NULL, no operation is performed.
  9.  *
  10.  * Must not be called in NMI context (strictly speaking, only if we don't
  11.  * have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling
  12.  * conventions for vfree() arch-depenedent would be a really bad idea)
  13.  *
  14.  * NOTE: assumes that the object at *addr has a size >= sizeof(llist_node)
  15.  */
  16. void vfree(const void *addr)
  17. {
  18.     BUG_ON(in_nmi());
  19.  
  20.     kmemleak_free(addr);
  21.  
  22.     if (!addr)
  23.         return;
  24.     if (unlikely(in_interrupt())) {
  25.         struct vfree_deferred *p = &__get_cpu_var(vfree_deferred);
  26.         if (llist_add((struct llist_node *)addr, &p->list))
  27.             schedule_work(&p->wq);
  28.     } else
  29.         __vunmap(addr, 1);
  30. }


    该函数首先对kmemleak内存泄漏跟踪进行解除,继而保证释放的地址空间不为空。如若当前释放操作在中断中,那么将释放的内存空间信息加入到当前CPUvfree_deferred管理链表中,继而通过schedule_work()唤醒工作队列,对内存进行异步释放操作;但如果当前操作不在中断中,将直接通过__vunmap()进行内存释放。

对于工作队列,是在vmalloc_init()中创建的free_work(),其具体实现。

  1. 【file:/mm/vmalloc.c】
  2. static void free_work(struct work_struct *w)
  3. {
  4.     struct vfree_deferred *p = container_of(w, struct vfree_deferred, wq);
  5.     struct llist_node *llnode = llist_del_all(&p->list);
  6.     while (llnode) {
  7.         void *p = llnode;
  8.         llnode = llist_next(llnode);
  9.         __vunmap(p, 1);
  10.     }
  11. }


     可以看到该函数实际上也是调用__vunmap()进行不连续内存页面进行释放的。

具体分析一下__vunmap()的实现,其实现也是非常简单的,实则是vmalloc()的倒序操作。

  1. 【file:/mm/vmalloc.c】
  2. static void __vunmap(const void *addr, int deallocate_pages)
  3. {
  4.     struct vm_struct *area;
  5.  
  6.     if (!addr)
  7.         return;
  8.  
  9.     if (WARN(!PAGE_ALIGNED(addr), "Trying to vfree() bad address (%p)\n",
  10.             addr))
  11.         return;
  12.  
  13.     area = remove_vm_area(addr);
  14.     if (unlikely(!area)) {
  15.         WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
  16.                 addr);
  17.         return;
  18.     }
  19.  
  20.     debug_check_no_locks_freed(addr, area->size);
  21.     debug_check_no_obj_freed(addr, area->size);
  22.  
  23.     if (deallocate_pages) {
  24.         int i;
  25.  
  26.         for (i = 0; i < area->nr_pages; i++) {
  27.             struct page *page = area->pages[i];
  28.  
  29.             BUG_ON(!page);
  30.             __free_page(page);
  31.         }
  32.  
  33.         if (area->flags & VM_VPAGES)
  34.             vfree(area->pages);
  35.         else
  36.             kfree(area->pages);
  37.     }
  38.  
  39.     kfree(area);
  40.     return;
  41. }


    可以看到该函数先是通过remove_vm_area()将虚拟地址空间从管理结构中移除,继而是相关调测。最后判断入参deallocate_pages,如果该值为0,则表示它是vumap()调用释放,否则是vfree()释放内存,其需要将申请的不连续物理内存页面释放掉。释放不连续物理内存页面的操作也很简单,通过循环遍历物理页面数组,将各页面逐一返回。页面释放完之后,最后将数组空间和vm_struct信息接口空间释放。

深入看一下remove_vm_area()的实现。

  1. 【file:/mm/vmalloc.c】
  2. /**
  3.  * remove_vm_area - find and remove a continuous kernel virtual area
  4.  * @addr: base address
  5.  *
  6.  * Search for the kernel VM area starting at @addr, and remove it.
  7.  * This function returns the found VM area, but using it is NOT safe
  8.  * on SMP machines, except for its size or flags.
  9.  */
  10. struct vm_struct *remove_vm_area(const void *addr)
  11. {
  12.     struct vmap_area *va;
  13.  
  14.     va = find_vmap_area((unsigned long)addr);
  15.     if (va && va->flags & VM_VM_AREA) {
  16.         struct vm_struct *vm = va->vm;
  17.  
  18.         spin_lock(&vmap_area_lock);
  19.         va->vm = NULL;
  20.         va->flags &= ~VM_VM_AREA;
  21.         spin_unlock(&vmap_area_lock);
  22.  
  23.         vmap_debug_free_range(va->va_start, va->va_end);
  24.         free_unmap_vmap_area(va);
  25.         vm->size -= PAGE_SIZE;
  26.  
  27.         return vm;
  28.     }
  29.     return NULL;
  30. }


该函数先是通过find_vmap_area()查找前面注册到红黑树的不连续内存的vmap_area管理信息,继而清除VM_VM_AREA标记,最后调用free_unmap_vmap_area()释放相关的内存页面。

具体看一下find_vmap_area()实现。

  1. 【file:/mm/vmalloc.c】
  2. static struct vmap_area *find_vmap_area(unsigned long addr)
  3. {
  4.     struct vmap_area *va;
  5.  
  6.     spin_lock(&vmap_area_lock);
  7.     va = __find_vmap_area(addr);
  8.     spin_unlock(&vmap_area_lock);
  9.  
  10.     return va;
  11. }


该函数主要是加保护锁后调用__find_vmap_area()进行查找。

  1. 【file:/mm/vmalloc.c】
  2. static struct vmap_area *__find_vmap_area(unsigned long addr)
  3. {
  4.     struct rb_node *n = vmap_area_root.rb_node;
  5.  
  6.     while (n) {
  7.         struct vmap_area *va;
  8.  
  9.         va = rb_entry(n, struct vmap_area, rb_node);
  10.         if (addr < va->va_start)
  11.             n = n->rb_left;
  12.         else if (addr >= va->va_end)
  13.             n = n->rb_right;
  14.         else
  15.             return va;
  16.     }
  17.  
  18.     return NULL;
  19. }


可以看到很熟悉的代码,类似前面分配虚拟内存空间一样,遍历vmap_area_root红黑树进行查找。

    现在回到free_unmap_vmap_area()进行收尾。

  1. 【file:/mm/vmalloc.c】
  2. /*
  3.  * Free and unmap a vmap area
  4.  */
  5. static void free_unmap_vmap_area(struct vmap_area *va)
  6. {
  7.     flush_cache_vunmap(va->va_start, va->va_end);
  8.     free_unmap_vmap_area_noflush(va);
  9. }


该函数主要用于清除告诉缓冲的指定的虚拟地址空间,当然这取决于硬件体系结构。其中flush_cache_vunmap()x86环境中是空函数。而free_unmap_vmap_area_noflush()函数。

  1. 【file:/mm/vmalloc.c】
  2. /*
  3.  * Free and unmap a vmap area, caller ensuring flush_cache_vunmap had been
  4.  * called for the correct range previously.
  5.  */
  6. static void free_unmap_vmap_area_noflush(struct vmap_area *va)
  7. {
  8.     unmap_vmap_area(va);
  9.     free_vmap_area_noflush(va);
  10. }


unmap_vmap_area()主要是通过vunmap_page_range()指定范围,最终通过vunmap_page_range()将该虚拟地址空间释放到内核页表。其中unmap_vmap_area()实现。

  1. 【file:/mm/vmalloc.c】
  2. /*
  3.  * Clear the pagetable entries of a given vmap_area
  4.  */
  5. static void unmap_vmap_area(struct vmap_area *va)
  6. {
  7.     vunmap_page_range(va->va_start, va->va_end);
  8. }


    而vunmap_page_range()实现了内存页面的映射。

  1. 【file:/mm/vmalloc.c】
  2. static void vunmap_page_range(unsigned long addr, unsigned long end)
  3. {
  4.     pgd_t *pgd;
  5.     unsigned long next;
  6.  
  7.     BUG_ON(addr >= end);
  8.     pgd = pgd_offset_k(addr);
  9.     do {
  10.         next = pgd_addr_end(addr, end);
  11.         if (pgd_none_or_clear_bad(pgd))
  12.             continue;
  13.         vunmap_pud_range(pgd, addr, next);
  14.     } while (pgd++, addr = next, addr != end);
  15. }


至于free_vmap_area_noflush()的实现。

  1. 【file:/mm/vmalloc.c】
  2. /*
  3.  * Free a vmap area, caller ensuring that the area has been unmapped
  4.  * and flush_cache_vunmap had been called for the correct range
  5.  * previously.
  6.  */
  7. static void free_vmap_area_noflush(struct vmap_area *va)
  8. {
  9.     va->flags |= VM_LAZY_FREE;
  10.     atomic_add((va->va_end - va->va_start) >> PAGE_SHIFT, &vmap_lazy_nr);
  11.     if (unlikely(atomic_read(&vmap_lazy_nr) > lazy_max_pages()))
  12.         try_purge_vmap_area_lazy();
  13. }


则是将vmp_area加入到vmap_purge_list链表,当加入该链表的数量超过一定值之后,才会通过try_purge_vmap_area_lazy()vmap_area真正释放掉。这是一个延迟的释放过程,最终会把vmap_area及相关的页面全部释放掉。

阅读(2918) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~