Chinaunix首页 | 论坛 | 博客
  • 博客访问: 315940
  • 博文数量: 72
  • 博客积分: 2580
  • 博客等级: 少校
  • 技术积分: 675
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-07 17:36
文章分类

全部博文(72)

文章存档

2012年(7)

2011年(17)

2010年(46)

2009年(2)

分类: LINUX

2011-10-14 14:40:50

1.       内存管理之内存分配

Linux为了实现内存分配的高效、防止大量碎片产生,最终产生了当前的内存分配的层次结构:buddy allocator/slob allocator/memory poll,其层次关系如下图。

 

Buddy Allocator

Buddy 是为了避免系统在频繁申请、释放不同大小的页框组导致的内存外碎片。道理很简单,频繁的申请释放,很有可能导致无法申请一组大的连续内存,虽然系统当前确实有这么大的剩余内存。

Buddy的分配思想很简单,将管理区的每种可用内存划分成order012…..n的连续内存块,分配时在大小最接近的块中分配,然后将这个块分裂放入低order块组中;在内存释放时,再大小相关的伙伴块合并,放入高order块组中。当然为了保证合并后的内存块就是之前分裂的那个大块,同时满足以下3个条件的两个块才被合并:

l         两个块相同的大小,记为b

l         他们的物理地址连续

l         第一块的第一个页框的物理地址为 的倍数(保证合并块的顺序)

下面来看看buddy allocatorLinux下的实现。

 

       /* Find a page of the appropriate size in the preferred list */

       for (current_order = order; current_order < MAX_ORDER; ++current_order) {

              area = &(zone->free_area[current_order]);

              if (list_empty(&area->free_list[migratetype]))

                     continue;

 

              page = list_entry(area->free_list[migratetype].next,

                                                 struct page, lru);

              list_del(&page->lru);

              rmv_page_order(page);

              area->nr_free--;    // count of mem-block of this order

              expand(zone, page, order, current_order, area, migratetype); // 维护buddy结构

              return page;

       }

接下来看看分配中最重要的函数,它维护者buddy的分配结构。

static inline void expand(struct zone *zone, struct page *page,

       int low, int high, struct free_area *area,

       int migratetype)

{

       unsigned long size = 1 << high;

 

while (high > low) {    // 如果一个块需要被分裂,则将它为分配的块放入低级的

//快组中

              area--;

              high--;

              size >>= 1;

              VM_BUG_ON(bad_range(zone, &page[size]));

              list_add(&page[size].lru, &area->free_list[migratetype]); // 将分裂后的小块加入到小

                                                                                    //order对应的组中,并计数+1

              area->nr_free++;

              set_page_order(&page[size], high); //page[size] 为分裂后小块的第一个页,将其order

                                                                // 设置为 high

       }

}

这样就成功的将分裂后的块迁移到了相应的组中,在这里Kernel充分利用了内存结构的特点,使用page[size]就获得的分裂的第一个页框(在一个块组中,所有页都是连续的)

 

接下来就是伙伴页的释放合并了。

static inline void __free_one_page(struct page *page,

              struct zone *zone, unsigned int order,

              int migratetype)

{

……

       while (order < MAX_ORDER-1) {

              buddy = __page_find_buddy(page, page_idx, order); // 见下面的函数说明

              if (!page_is_buddy(page, buddy, order)) // 这里只是检查buddy页是否有确有buddy

                                                                      // 且他们的order是否相等

                     break;

 

              /* Our buddy is free, merge with it and move up one order. */

              list_del(&buddy->lru);

              zone->free_area[order].nr_free--;

              rmv_page_order(buddy);       //buddy从现有的area上移除,

              combined_idx = __find_combined_index(page_idx, order);

              page = page + (combined_idx - page_idx);

              page_idx = combined_idx;     // 页合并,

              order++;                              // 继续查看是否有更高级的order可用作合并

       }

       set_page_order(page, order);

 

       /*

        * If this is not the largest possible page, check if the buddy

        * of the next-highest order is free. If it is, it's possible

        * that pages are being freed that will coalesce soon. In case,

        * that is happening, add the free page to the tail of the list

        * so it's less likely to be used soon and more likely to be merged

        * as a higher order page

        */

       if ((order < MAX_ORDER-1) && pfn_valid_within(page_to_pfn(buddy))) {

              struct page *higher_page, *higher_buddy;

              combined_idx = __find_combined_index(page_idx, order);

              higher_page = page + combined_idx - page_idx;

              higher_buddy = __page_find_buddy(higher_page, combined_idx, order + 1);

              if (page_is_buddy(higher_page, higher_buddy, order + 1)) {

                     list_add_tail(&page->lru,

                            &zone->free_area[order].free_list[migratetype]);

                     goto out;

              }

       }

 

       list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);

 

// 在这里kernel巧妙的保证了2*b*4k的要求,其使用页的物理页帧号和其order,用orderMask 异或 物理页帧的低order位及得到当前页的buddy

static inline struct page *

__page_find_buddy(struct page *page, unsigned long page_idx, unsigned int order)

{

       unsigned long buddy_idx = page_idx ^ (1 << order);

 

       return page + (buddy_idx - page_idx);

}

 

这样就实现了buddy块的合并,nice,我们又有大的连续物理内存可用了。

 

另外,为了加快页框的分配,Linux还加入了冷热页的告诉缓存,用以快速的分配单个页框。当然了,冷热页的分配也是基于buddy,从buddy里面进行分配。。

这样,最底层的页框分配就结束了,无论思想还是代码都很简单,紧凑。

 

Slab allocator

       Buddy实现了以页为单位的大块物理内存的分配,但在实际的使用过程中,我们大多时候很少一次分配这么大的以页为单位的内存块,更多的是任意长度的存储单元。Kernel使用Slab分配器很好的解决了这个问题。为了分析slab,还得引入高速缓存(cache),具体描述就不多讲了。

       Slab分配器的组成可以简单的描述如下。

 

 

待续……

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