16.释放页(__free_pages详解)

__free_pages是一个基础函数,用于实现内核API中所有涉及内存释放的函数。其代码流程图如图3-35所示。

 

 

__free_pages首先判断所需释放的内存是单页还是较大的内存块?如果释放单页,则不还给伙伴系统,而是置于per-CPU缓存中,对很可能出现在CPU高速缓存的页,则放置到热页的列表中。出于该目的,内核提供了free_hot_page辅助函数。如果free_hot_cold_page判断per-CPU缓存中页的数目超出了pcp->count,则将数量为pcp->batch的一批内存页还给伙伴系统。该策略称之为惰性合并(lazy coalescing)。如果单页直接返回给伙伴系统,那么会发生合并,而为了满足后来的分配请求又需要进行拆分。因而惰性合并策略阻止了大量可能白费时间的合并操作。free_pages_bulk用于将页还给伙伴系统。
 
如果不超出惰性合并的限制,则页只是保存在per-CPU缓存中。但重要的是将private成员设置为页的迁移类型。根据前文所述,这使得可以从per-CPU缓存分配单页,并选择正确的迁移类型。
 
如果释放多个页,那么__free_pages将工作委托给__free_pages_ok,最后到__free_one_page。与其名称不同,该函数不仅处理单页的释放,也处理复合页释放。
mm/page_alloc.c
static inline void __free_one_page (struct page *page,struct zone *zone, unsigned int order)

 

该函数是内存释放功能的基础。相关的内存区被添加到伙伴系统中适当的free_area列表。在释放伙伴对时,该函数将其合并为一个连续内存区,放置到高一阶的free_area列表中。如果还能合并一个进一步的伙伴对,那么也进行合并,转移到更高阶的列表。该过程会一直重复下去,直至所有可能的伙伴对都已经合并,并将改变尽可能向上传播。
 
但内核如何知道一个伙伴对的两个部分都位于空闲页的列表中,上文没有给出答案。为将内存块放回伙伴系统,内核必须计算潜在伙伴的地址,以及在有可能合并的情况下合并后内存块的索引。有两个辅助函数可用于该计算:
mm/page_alloc.c
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);
}//有可能变高或变低
static inline unsigned long__find_combined_index(unsigned long page_idx, unsigned int order)
{
  return (page_idx & ~(1 << order));
}//有可能不变或变低

 

不过,我们首先还需要介绍另一个辅助函数。根据伙伴的页索引信息,并不足以作出判断。内核还必须确保属于伙伴的所有页都是空闲的,并包含在伙伴系统中,以便进行合并。
mm/page_alloc.c
static inline int page_is_buddy(struct page *page, struct page *buddy,int order)
{
...
  if (PageBuddy(buddy) && page_order(buddy) == order) {
    return 1;
  }
  return 0;
}

 

伙伴的第1页如果在伙伴系统中,则对应的struct page实例会设置PG_buddy标志位。但这不足以作为合并两个伙伴的根据。在释放具有2order页的内存块时,内核必须确保第2个伙伴的2order页也包含伙伴系统中。这很容易检查,因为空闲内存块的分配阶存储在第1个struct page实例的private成员中,而page_order可以读取该值。
下列代码用于确定一对伙伴是否能够合并:
mm/page_alloc.c
static inline void __free_one_page(struct page *page,struct zone *zone, unsigned int order)
{
  int migratetype = get_pageblock_migratetype(page);
...
  while (order < MAX_ORDER-1) {
    unsigned long combined_idx;
    struct page *buddy;
    buddy = __page_find_buddy(page, page_idx, order);
    if (!page_is_buddy(page, buddy, order))
      break; /* 将伙伴向上移动一级。 */
    list_del(&buddy->lru);
    zone->free_area[order].nr_free--;
    rmv_page_order(buddy);
    combined_idx = __find_combined_index(page_idx, order);
    page = page + (combined_idx -page_idx);
    page_idx = combined_idx;
    order++;
}
...

 

最后,需要将包含24 =16页的内存块放置到伙伴系统的空闲列表上。这并不很复杂:
mm/page_alloc.c
  set_page_order(page, order);
  list_add(&page->lru,&zone->free_area[order].free_list[migratetype]);
  zone->free_area[order].nr_free++;
}
 

 

请注意,内存块的分配阶保存在其中第一个page实例的private成员中。这样,内核会知道不仅页0,而且[0, 15]整个页范围都在伙伴系统中,且是空闲的。
posted @ 2022-03-21 00:20  while(true);;  阅读(227)  评论(0编辑  收藏  举报