linux 源代码分析4:伙伴系统内存释放

  1. 伙伴系统内存释放
    1. free_pages函数
    2. __free_pages函数
    3. free_pages_prepare函数
    4. __free_pages_ok函数
    5. free_one_page函数
    6. __free_one_page函数

伙伴系统内存释放

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((unsignedlong)(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#ifndefARCH_PFN_OFFSET
9#defineARCH_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自增空闲区域空闲块计数。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 ancjf@163.com

文章标题:linux 源代码分析4:伙伴系统内存释放

本文作者:ancjf

发布时间:2013-05-21, 20:25:33

最后更新:2020-04-18, 15:57:34

原始链接:http://ancjf.com/2013/05/21/linux-3-4-10-%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%904-%E4%BC%99%E4%BC%B4%E7%B3%BB%E7%BB%9F%E5%86%85%E5%AD%98%E9%87%8A%E6%94%BE/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏