法律声明:《linux 3.4.10 内核内存管理源代码分析》系列文章由陈晋飞(ancjf@163.com)发表于http://write.blog.csdn.net/postedit/8943026,文章遵循GPL协议。欢迎转载,转载请注明作者和此条款。
4 伙伴系统内存释放
free_pages函数
伙伴系统释放页面的主函数是free_pages,在mm/page_alloc.c中实现,代码如下:
2517 void free_pages(unsigned long addr,unsigned int order)
2518 {
2519 if (addr != 0) {
2520 VM_BUG_ON(!virt_addr_valid((void *)addr));
2521 __free_pages(virt_to_page((void *)addr), order);
2522 }
2523 }
free_pages函数只是在地址不为零的情况下简单调用virt_to_page函数从物理地址得到该地址的管理结构page的地址,然后调用__free_pages函数释放内存。
virt_to_page宏
virt_to_page宏在arch/x86/include/asm/page.h中定义:
#definevirt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
__pa是一个宏,在arch/x86/include/asm/page.h中定义:
36 #define __pa(x) __phys_addr((unsigned long)(x))
__phys_addr是一个宏,在arch/x86/include/asm/page_32.h中定义
16 #define __phys_addr(x) __phys_addr_nodebug (x)
__phys_addr_nodebug也是一个宏,在arch/x86/include/asm/page_32.h中定义
#define __phys_addr_nodebug(x) ((x) - PAGE_OFFSET)
在x86上,PAGE_OFFSET的值是0
一层一层拨开后__pa(x)也就是返回x的值
pfn_to_page也是一个宏,在include/asm-generic/memory_model.h中定义
30 #define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
ARCH_PFN_OFFSET也是个宏,在include/asm-generic/memory_model.h中定义
8 #ifndef ARCH_PFN_OFFSET
9#define ARCH_PFN_OFFSET (0UL)
10#endif
pfn_to_page只是在全局变量mem_map加上页帧号获得该页帧的page管理结构。
virt_to_page通过宏__pa返回物理地址,物理地址右移PAGE_SHIFT位后得到页帧,页帧号加上全局变量mem_map就得到地址所对应的page管理结构地址。
__free_pages函数
2505void __free_pages(struct page *page, unsigned int order)
2506 {
2507 if (put_page_testzero(page)) {
2508 if (order == 0)
2509 free_hot_cold_page(page, 0);
2510 else
2511 __free_pages_ok(page,order);
2512 }
2513 }
__free_pages函数2507行调用函数put_page_testzero在include/linux/mm.h中定义
275 static inline intput_page_testzero(struct page *page)
276{
277 VM_BUG_ON(atomic_read(&page->_count) == 0);
278 return atomic_dec_and_test(&page->_count);
279}
atomic_dec_and_test函数对原子类型的变量原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
put_page_testzero函数的就是自减引用计算,并返回结构。
__free_pages函数2507行自减页面引用计数并返回当然自减后测试结果,如果自减后的引用计数为0,表面没有使用则,则分两种情况释放页面,阶为零的情况调用free_hot_cold_page函数释放页面,否则调用__free_pages_ok释放页面。 free_hot_cold_page函数中伙伴系统的内存迁移一节一节分析过。
free_pages_prepare函数
free_pages_prepare函数主要做释放内存的前期工作,在mm/page_alloc.c中实现,代码如下:
690static bool free_pages_prepare(struct page *page, unsigned int order)
691{
692 int i;
693 int bad = 0;
694
695 trace_mm_page_free(page, order);
696 kmemcheck_free_shadow(page, order);
697
698 if (PageAnon(page))
699 page->mapping = NULL;
700 for (i = 0; i < (1 << order); i++)
701 bad += free_pages_check(page +i);
702 if (bad)
703 return false;
704
705 if (!PageHighMem(page)) {
706 debug_check_no_locks_freed(page_address(page),PAGE_SIZE<<order);
707 debug_check_no_obj_freed(page_address(page),
708 PAGE_SIZE << order);
709 }
710 arch_free_page(page, order);
711 kernel_map_pages(page, 1 << order, 0);
712
713 return true;
714}
695行是调试代码,696行是关于kmemcheck内存调试的代码。
701行调用free_pages_check函数对要释放的页面做检查,free_pages_check在前面已经分析过
710行的arch_free_page函数是空函数
如果不打开内存调试宏kernel_map_pages也是个空函数。
__free_pages_ok函数
__free_pages_ok在mm/page_alloc.c中实现,代码如下:
716 static void __free_pages_ok(struct page*page, unsigned int order)
717{
718 unsigned long flags;
719 int wasMlocked = __TestClearPageMlocked(page);
720
721 if (!free_pages_prepare(page, order))
722 return;
723
724 local_irq_save(flags);
725 if (unlikely(wasMlocked))
726 free_page_mlock(page);
727 __count_vm_events(PGFREE, 1 << order);
728 free_one_page(page_zone(page), page, order,
729 get_pageblock_migratetype(page));
730 local_irq_restore(flags);
731}
__free_pages_ok函数721行调用free_pages_prepare函数做释放前的准备工作
724行关中断
725-726行并对mlock页调用free_page_mlock函数。
727行增加释放页面的统计计数。
728-729行调用free_one_page函数释放内存。获得迁移类型的函数get_pageblock_migratetype在伙伴系统的迁移类型一节已经分析过。
730恢复中断。
free_one_page函数
free_one_page函数在mm/page_alloc.c中实现,代码如下:
678static void free_one_page(struct zone *zone, struct page *page, int order,
679 intmigratetype)
680{
681 spin_lock(&zone->lock);
682 zone->all_unreclaimable = 0;
683 zone->pages_scanned = 0;
684
685 __free_one_page(page, zone, order, migratetype);
686 __mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
687 spin_unlock(&zone->lock);
688}
681获得回环锁
682-683行是因为向伙伴系统释放了内存,通知内存交换系统这样区域已经可以进行回收和需要再次扫描。
685行调用__free_one_page释放函数,因为进入free_one_page函数的时候已经关中断,681行也获得了回旋锁,所以__free_one_page是指原子上下文中执行,不会不中断,页不会被其他cpu打扰。
686行增加区域的空闲页计数。
687释放回旋锁。
__free_one_page函数
伙伴系统的内存释放工作是在__free_one_page函数中完成的,进入__free_one_page函数的时候已经关了中断,并且也已经获取了区域的自锁锁,这样保证__free_one_page执行是原子的。
__free_one_page函数参数page是要释放的块的首页的page结构地址;zone是要把空闲块释放到的区域结构指针;order是要释放的块的阶,migratetype是要释放的块的迁移类型。__free_one_page函数在mm/page_alloc.c中实现,代码如下:
524 static inline void __free_one_page(structpage *page,
525 struct zone *zone, unsignedint order,
526 int migratetype)
527{
528 unsigned long page_idx;
529 unsigned long combined_idx;
530 unsigned long uninitialized_var(buddy_idx);
531 struct page *buddy;
532
533 if (unlikely(PageCompound(page)))
534 if(unlikely(destroy_compound_page(page, order)))
535 return;
536
537 VM_BUG_ON(migratetype == -1);
538
539 page_idx = page_to_pfn(page) & ((1<< MAX_ORDER) - 1);
540
541 VM_BUG_ON(page_idx & ((1 << order) - 1));
542 VM_BUG_ON(bad_range(zone, page));
543
544 while (order < MAX_ORDER-1) {
545 buddy_idx = __find_buddy_index(page_idx,order);
546 buddy = page + (buddy_idx -page_idx);
547 if (!page_is_buddy(page,buddy, order))
548 break;
549 /*
550 * Our buddy is free or it isCONFIG_DEBUG_PAGEALLOC guard page,
551 * merge with it and move upone order.
552 */
553 if (page_is_guard(buddy)) {
554 clear_page_guard_flag(buddy);
555 set_page_private(page, 0);
556 __mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
557 } else {
558 list_del(&buddy->lru);
559 zone->free_area[order].nr_free--;
560 rmv_page_order(buddy);
561 }
562 combined_idx = buddy_idx &page_idx;
563 page = page + (combined_idx -page_idx);
564 page_idx = combined_idx;
565 order++;
566 }
567 set_page_order(page, order);
568
569 /*
570 * If this is not the largest possible page, check if the buddy
571 * of the next-highest order is free. If it is, it's possible
572 * that pages are being freed that will coalesce soon. In case,
573 * that is happening, add the free page to the tail of the list
574 * so it's less likely to be used soon and more likely to be merged
575 * as a higher order page
576 */
577 if ((order < MAX_ORDER-2) &&pfn_valid_within(page_to_pfn(buddy))) {
578 struct page *higher_page,*higher_buddy;
579 combined_idx = buddy_idx &page_idx;
580 higher_page = page +(combined_idx - page_idx);
581 buddy_idx =__find_buddy_index(combined_idx, order + 1);
582 higher_buddy = page +(buddy_idx - combined_idx);
583 if (page_is_buddy(higher_page,higher_buddy, order + 1)) {
584 list_add_tail(&page->lru,
585 &zone->free_area[order].free_list[migratetype]);
586 goto out;
587 }
588 }
589
590 list_add(&page->lru,&zone->free_area[order].free_list[migratetype]);
591out:
592 zone->free_area[order].nr_free++;
593}
对伙伴系统的每个空闲块,都有一个对应阶order,阶的最大值是MAX_ORDER-1。对阶不是最大阶的块,都有一个伙伴块,伙伴块的阶也是order。如果把阶是最大值MAX_ORDER-1,起始页帧号是2^ (MAX_ORDER-1)的倍数的块叫做最大块。则伙伴块都在同一最大块中,也就是说伙伴块的页帧号大于MAX_ORDER-1的位是相同的。在迁移类型一节中我们知道,同一最大块的迁移类型是相同的。
伙伴系统内存释放就是要把伙伴块合并成一个更大的块,直到块已经是最大块或者当前要释放的块的伙伴块不是空闲块为止。这样可以防止内存碎片。
对一个页的页帧号,我们把0到MAX_ORDER-1位的部分叫内部页帧号,两个伙伴块的页帧号不同只在内存页帧号。
局部变量page_idx是当前要释放的块的内部页帧号,page是其首页的page管理结构地址。buddy_idx是当前要释放的页的伙伴的页帧号,buddy是其首页的page管理结构地址。
539行获得当前要释放块的首页内部帧号。
545行获取伙伴块的首页帧号。一个阶为的order块的首页帧号和伙伴块的首页帧号只是在order为不同而已,__find_buddy_index函数把当前页号的onder位做异或就得到了伙伴块的首页帧号。
546获得伙伴块的首页的page结构地址。
547-548判断两个块是不是伙伴块。不是则退出循环。
553-557是用于调试的代码。
558-560行把伙伴块从所在的空闲链表中摘除,减少所在的空闲区域的空闲块计数,并清除阶性息,因为合并后的首页可能不再是这一页。
562获得是当前块和伙伴或的内部页帧号中较小的一个,也就是合并后的块的首页帧号。
563-564从新设置当前块首页的page管理结构地址和当前块的首页内部页帧号。
567保存阶,块的阶保存在块的首页的page管理结构的成员private中。
577-588如果合并后的当前块还不是最大块,则把当前块和当前块的伙伴块作为一个更大的块与上一阶阶的伙伴块尝试进行一次合并。如果合并是允许的,把当前块释放到空闲链表的末尾,使其更加晚的分配出去,因为分配的时候总是先分配链表头的块。因为这意味着如果当前块的伙伴块被释放的话,就可以合并成更大的块。使可能合成更大块的块更晚的分配出去,有防止内存碎片的作用。
590把合并后的块链接到空闲链表中。
592自增空闲区域空闲块计数。