系列文章目录
Linux 内核设计与实现
深入理解 Linux 内核(一)
深入理解 Linux 内核(二)
Linux 设备驱动程序(一)
Linux 设备驱动程序(二)
Linux 设备驱动程序(三)
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)
深入理解Linux虚拟内存管理(五)
文章目录
- 系列文章目录
- 一、物理页面分配
- 1、分配页面
- 2、分配辅助函数
- 3、释放页面
- (1)__free_pages
- (a)⇐ mm.h
- (2)__free_pages_ok
- (a)⇐ zone_t
- (b)⇐ free_area_init_core
- 4、释放辅助函数
- (1)free_pages
- (a)⇐ page.h
- (2)__free_page
- (3)free_page
- 二、补充配置
- 1、空格
一、物理页面分配
1、分配页面
2、分配辅助函数
3、释放页面
(1)__free_pages
这个函数的调用图如图 6.4 所示。很容易误解的是,alloc_pages() 对应的函数并不是 free_pages(),而是 __free_pages()。free_pages() 是一个以地址为参数的辅助函数。
// mm/page_alloc.c
// 参数是我们将释放的 page 和块的幂次。
void __free_pages(struct page *page, unsigned int order)
{
// 有效性检查。PageReserved()表示页面由引导内存分配器保留。put_page_testzero() 仅
// 是一个对 atomic_dec_and_test() 的宏封装。它将使用计数减 1,保证它为 0。
if (!PageReserved(page) && put_page_testzero(page))
// 调用函数来完成所有的复杂工作。
__free_pages_ok(page, order);
}
(a)⇐ mm.h
// include/linux/mm.h
#define put_page_testzero(p) atomic_dec_and_test(&(p)->count)
#define PageLRU(page) test_bit(PG_lru, &(page)->flags)
#define ClearPageUptodate(page) clear_bit(PG_uptodate, &(page)->flags)
#define PageDirty(page) test_bit(PG_dirty, &(page)->flags)
#define SetPageDirty(page) set_bit(PG_dirty, &(page)->flags)
#define ClearPageDirty(page) clear_bit(PG_dirty, &(page)->flags)
#define PageLocked(page) test_bit(PG_locked, &(page)->flags)
#define LockPage(page) set_bit(PG_locked, &(page)->flags)
#define TryLockPage(page) test_and_set_bit(PG_locked, &(page)->flags)
#define PageChecked(page) test_bit(PG_checked, &(page)->flags)
#define SetPageChecked(page) set_bit(PG_checked, &(page)->flags)
#define PageLaunder(page) test_bit(PG_launder, &(page)->flags)
#define SetPageLaunder(page) set_bit(PG_launder, &(page)->flags)
#define ClearPageLaunder(page) clear_bit(PG_launder, &(page)->flags)
/*
* The first mb is necessary to safely close the critical section opened by the
* TryLockPage(), the second mb is necessary to enforce ordering between
* the clear_bit and the read of the waitqueue (to avoid SMP races with a
* parallel wait_on_page).
*/
#define PageError(page) test_bit(PG_error, &(page)->flags)
#define SetPageError(page) set_bit(PG_error, &(page)->flags)
#define ClearPageError(page) clear_bit(PG_error, &(page)->flags)
#define PageReferenced(page) test_bit(PG_referenced, &(page)->flags)
#define SetPageReferenced(page) set_bit(PG_referenced, &(page)->flags)
#define ClearPageReferenced(page) clear_bit(PG_referenced, &(page)->flags)
#define PageTestandClearReferenced(page) test_and_clear_bit(PG_referenced, &(page)->flags)
#define PageSlab(page) test_bit(PG_slab, &(page)->flags)
#define PageSetSlab(page) set_bit(PG_slab, &(page)->flags)
#define PageClearSlab(page) clear_bit(PG_slab, &(page)->flags)
#define PageReserved(page) test_bit(PG_reserved, &(page)->flags)
#define PageActive(page) test_bit(PG_active, &(page)->flags)
#define SetPageActive(page) set_bit(PG_active, &(page)->flags)
#define ClearPageActive(page) clear_bit(PG_active, &(page)->flags)
#define PageLRU(page) test_bit(PG_lru, &(page)->flags)
#define TestSetPageLRU(page) test_and_set_bit(PG_lru, &(page)->flags)
#define TestClearPageLRU(page) test_and_clear_bit(PG_lru, &(page)->flags)
#ifdef CONFIG_HIGHMEM
#define PageHighMem(page) test_bit(PG_highmem, &(page)->flags)
#else
#define PageHighMem(page) 0 /* needed to optimize away at compile time */
#endif
#define SetPageReserved(page) set_bit(PG_reserved, &(page)->flags)
#define ClearPageReserved(page) clear_bit(PG_reserved, &(page)->flags)
(2)__free_pages_ok
这个函数将完成实际的释放页面工作,并在可能的情况下合并伙伴。
/*
* Freeing function for a buddy system allocator.
* Contrary to prior comments, this is *NOT* hairy, and there
* is no reason for anyone not to understand it.
*
* The concept of a buddy system is to maintain direct-mapped tables
* (containing bit values) for memory blocks of various "orders".
* The bottom level table contains the map for the smallest allocatable
* units of memory (here, pages), and each level above it describes
* pairs of units from the levels below, hence, "buddies".
* At a high level, all that happens here is marking the table entry
* at the bottom level available, and propagating the changes upward
* as necessary, plus some accounting needed to play nicely with other
* parts of the VM system.
* At each level, we keep one bit for each pair of blocks, which
* is set to 1 iff only one of the pair is allocated. So when we
* are allocating or freeing one, we can derive the state of the
* other. That is, if we allocate a small block, and both were
* free, the remainder of the region must be split into blocks.
* If a block is freed, and its buddy is also free, then this
* triggers coalescing into a block of larger size.
*
* -- wli
*/
static void FASTCALL(__free_pages_ok (struct page *page, unsigned int order));
// 参数是待释放页面块的开始和待释放页面的幂次数。
static void __free_pages_ok (struct page *page, unsigned int order)
{
unsigned long index, page_idx, mask, flags;
free_area_t *area;
struct page *base;
zone_t *zone;
/*
* Yes, think what happens when other parts of the kernel take
* a reference to a page in order to pin it for io. -ben
*/
// 在标志 I/O 时,LRU 中的脏页面将仍然设置有 LRU 位。一旦 I/O 完成,它就会
// 被释放,所以现在必须从 LRU 链表中移除。
if (PageLRU(page)) {
if (unlikely(in_interrupt()))
BUG();
// mm/swap.c
lru_cache_del(page);
}
// 有效性检查。
if (page->buffers)
BUG();
if (page->mapping)
BUG();
if (!VALID_PAGE(page))
BUG();
if (PageLocked(page))
BUG();
if (PageActive(page))
BUG();
// 由于页面现在空闲,没有在使用中,这个标志位表示页面已经被引用,而且是必须被
// 清洗的脏页面。
page->flags &= ~((1<<PG_referenced) | (1<<PG_dirty));
// 如果设置了该标志,这些已经释放了的页面将保存在释放它们的进程中。如果
// 调用者是它自己在释放页面,而不是等待 kswapd 来释放,在分配页面时这里调用
// balance_classzone()。
if (current->flags & PF_FREE_PAGES)
goto local_freelist;
back_local_freelist:
// 页面所属管理区用页面标志位编码。宏 page_zone() 返回该管理区。
zone = page_zone(page);
// 有关掩码计算的讨论在随书附带的文档中。它基本上与伙伴系统的地址计算有关。
mask = (~0UL) << order;
// base 是这个 zone_mem_map 的起始端。对伙伴计算而言,它与地址 0 有关,这样地址
// 就是 2 的幕。
base = zone->zone_mem_map;
// page_idx 视 zone_mem_map 为一个由页面组成的数组,这是映射图中的页索引。
page_idx = page - base;
// 如果索引不是 2 的幕,则肯定是某个地方出现严重错误,伙伴的计算将不会进行。
if (page_idx & ~mask)
BUG();
// index 是 free_area->map 的位索引。
index = page_idx >> (1 + order);
// area 是存储空闲链表和映射图的区域,其中映射图是释放页面的有序块。
area = zone->free_area + order;
// 管理区将改变,所以这里上锁。由于中断处理程序可能在这个路径上分配页面,所以
// 这个锁是一个中断安全的锁。
spin_lock_irqsave(&zone->lock, flags);
// mask 计算的另一个副作用是 -mask 是待释放的页面数。
zone->free_pages -= mask;
// 分配器将不断地试着合并块直到不能再合并,或者到达了可以合并的最高次。
// 对合并的每一次序块,mask 都将调整。当到达了可以合并的最高次的时候,while 循环将为 0
// 并退出。
//
while (mask + (1 << (MAX_ORDER-1))) {
struct page *buddy1, *buddy2;
// 如果发生什么意外,mask 被损坏,这个检查将保证 free_area 不会超过末端读。
if (area >= zone->free_area + MAX_ORDER)
BUG();
// 表示伙伴对的位置位。如果以前该位是 0,则两个伙伴都在使用中。因此,在这个伙
// 伴释放后,另外一个正在使用中,所以不能合并。
if (!__test_and_change_bit(index, area->map))
/*
* the buddy page is still allocated.
*/
break;
/*
* Move the buddy up one level.
* This code is taking advantage of the identity:
* -mask = 1+~mask
*/
// 这两个地址的计算在第 6 章讨论。
buddy1 = base + (page_idx ^ -mask);
buddy2 = base + page_idx;
// 有效性检查保证页面在正确的 zone_mem_map 中,而且实际上属于这个管理区。
if (BAD_RANGE(zone,buddy1))
BUG();
if (BAD_RANGE(zone,buddy2))
BUG();
// 伙伴已经被释放,所以这里将其从包含它的链表中移除。
list_del(&buddy1->list);
// 准备检查待合并的高次伙伴。
//
// 将掩码左移 1 位到次 2^(k+1)
mask <<= 1;
// area 是一个数组内指针,所以 area++ 移到下一个下标。
area++;
// 高次位图的索引。
index >>= 1;
// 待合并 zone_mem_map 中的页面索引。
page_idx &= mask;
}
// 由于尽可能多地合并已经完成,而且释放了一个新页面块,所以这里将其加入到该次
// 的 free_list 中。
list_add(&(base + page_idx)->list, &area->free_list);
// 对管理区的改变已经完成,所以这里释放锁并返回。
spin_unlock_irqrestore(&zone->lock, flags);
return;
// 这是在页面没有释放到主页面池时的代码路径,它将页面保留给释放的进程。
local_freelist:
// 如果进程已经有保留页面,则这里不允许再保留页面,所以返回。这里很不寻
// 常,因为 balance_classzone() 假设多于一个页面块可能从该链表上返回。这很有可能能过虑了,
// 但是如果释放的第一个页面是同一次的,而 balance_classzone()请求管理区,则这里仍然可以
// 工作。
if (current->nr_local_pages)
goto back_local_freelist;
// 一个中断没有进程上下文,所以它必须象平常一样释放。现在还不明白这里的
// 中断如何结束。这里的检查似乎是假的,而且不可能为真的。
if (in_interrupt())
goto back_local_freelist;
// 将页面块加入到链表中处理 local_pages。
list_add(&page->list, ¤t->local_pages);
// 记录分配的次数,从而方便后面的释放操作。
page->index = order;
// 将 nr_local_pages 使用计数加 1。
current->nr_local_pages++;
}
(a)⇐ zone_t
每个管理区由一个 zone_t 描述,具体可参考 ⇒ 2.2 管理区,2.6 页面映射到管理区
// include/linux/mm.h
extern struct zone_struct *zone_table[];
static inline zone_t *page_zone(struct page *page)
{
return zone_table[page->flags >> ZONE_SHIFT];
}
(b)⇐ free_area_init_core
这个函数负责初始化所有的区域,并在节点中分配它们的局部 Imem_map(类型 struct page *)。并初始化 pg_data_t 中字段 node_mem_map,node_size,node_start_paddr,node_start_mapnr,nr_zones,node_zones 以及全局 zone_table。
传送门 free_area_init_core
4、释放辅助函数
这些函数与页面分配的辅助函数非常相似,因为它们也不完成 “实际” 的工作,它们依赖于 __free_pages() 函数来完成实际的释放。
(1)free_pages
// mm/page_alloc.c
// 这个函数以一个地址,而不是以一个页面作为参数来进行释放操作。
void free_pages(unsigned long addr, unsigned int order)
{
if (addr != 0)
// 宏 virt_to_page() 返回 addr 的 struct page。
__free_pages(virt_to_page(addr), order);
}
(a)⇐ page.h
// include/asm/page.h
#define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
(2)__free_page
// include/linux/mm.h
// 这个宏仅调用函数 __free_pages() ,参数为 0 幂次和一个页面。
#define __free_page(page) __free_pages((page), 0)
(3)free_page
// include/linux/mm.h
// 这个小宏仅调用 free_pages() 。这个宏与 __free_page() 的主要区别在于这个函数以一个
// 虚拟地址为参数,而 __free_page() 以一个 struct page 为参数
#define free_page(addr) free_pages((addr),0)
二、补充配置
1、空格
  为“全角空格”
  为“全角空格”
为“不换行空格”
123