********************************************
蛐蛐
http://qgjie456.blog.163.com/
MSN:qgjie@hotmail.com
本文适用于
linux-2.6.22.8
V 0.1
欢迎转载,但请保留作者信息
********************************************
在此主要讲述伙伴内存分配器空闲页面链表的初始化。
在 start_kernel() 调用 mem_init() 函数,
而 mem_init() 调用 free_all_bootmem() 函数,
而 free_all_bootmem() 函数调用 __free_pages_bootmem() 函数,
而 __free_pages_bootmem() 函数调用 __free_page() 或者 __free_pages() 函数,
而 __free_page() 或者 __free_pages() 函数建立伙伴分配器的空闲链表。
==========================================================================
#define __free_page(page) __free_pages((page), 0)
---------------------------------------
fastcall void __free_pages(struct page *page, unsigned int order)
{
这个 put_page_testzero() 函数,把 page 的计数原子性的减 1 ,
并测试是否为 0 ,如果为 0 ,返回 true,否则返回 false。
if (put_page_testzero(page)) {
if (order == 0)
free_hot_page(page);
else
__free_pages_ok(page, order);
}
}
********************************************
这个 free_hot_page() 函数释放单个物理内存页面。
在 linux 内核中,为每个内存管理区和每个 CPU 提供了两个高速缓存:热高速缓存,冷高速缓存。
在每个内存管理区描述符的 pageset 成员字段中,有一个 per_cpu_pageset 数组数据结构。
这个数组为每个 CPU 提供了一个元素,每个元素由两个 per_cpu_pages 描述符组成,
一个为热高速缓存,一个为冷高速缓存。
由管理区获取每 CPU 高速缓存地址(struct per_cpu_pageset 数据结构地址)的函数为
#ifdef CONFIG_NUMA
#define zone_pcp(__z, __cpu) ((__z)->pageset[(__cpu)])
#else
#define zone_pcp(__z, __cpu) (&(__z)->pageset[(__cpu)])
#endif
内核通过 free_hot_page() 和 free_cold_page() 函数来释放单个页面。
他们都是 free_hot_cold_page(page, 0) 函数的简单封装。
---------------------------------------
void fastcall free_hot_page(struct page *page)
{
free_hot_cold_page(page, 0);
}
---------------------------------------
struct per_cpu_pages {
int count; 高速缓存中页面的个数
int high; 上界,表示高速缓存页面太多了。
int batch; 每次添加或删除高速缓存的页面个数
struct list_head list; 高速缓存中所包含的页面描述符链表
};
---------------------------------------
static void fastcall free_hot_cold_page(struct page *page, int cold)
{
从 page->flags 字段获取包含页面的内存管理区的描述符地址。
struct zone *zone = page_zone(page);
struct per_cpu_pages *pcp;
unsigned long flags;
检测页面的是否为匿名页面(映射到用户虚拟地址空间)
if (PageAnon(page))
page->mapping = NULL;
检测页面是否可以释放。
if (free_pages_check(page))
return;
如果不是高端内存的页面,检测是否有代码拥有这个页面空间的锁。
如果没有定义 CONFIG_LOCKDEP 这个宏,这个函数为空函数。
if (!PageHighMem(page))
debug_check_no_locks_freed(page_address(page), PAGE_SIZE);
如果定义了 HAVE_ARCH_FREE_PAGE 这个宏,有自己特定体系结构的释放函数。
这个不是空函数,否则这个函数为空函数。
arch_free_page(page, 0);
如果没有定义 CONFIG_DEBUG_PAGEALLOC 这个宏,这个函数就是空函数。
kernel_map_pages(page, 1, 0);
获取有参数 cold 标志选择的管理区高速缓存的 per_cpu_pages 描述符的地址。
参考上面说明。
pcp = &zone_pcp(zone, get_cpu())->pcp[cold];
禁止当前 CPU 的中断,并保存中断标识。
local_irq_save(flags);
如果没有定义 CONFIG_VM_EVENT_COUNTERS 宏,这个函数为空函数。
这个函数增加 VW EVENT 的计数。每个 CPU 一个 VW EVENT 的计数。
__count_vm_event(PGFREE);
把这个内存页面 page 使用 lru 成员,加入 这个 CPU 的高速缓存链表。
list_add(&page->lru, &pcp->list);
增加这个 CPU 的高速缓存链表的内存页面数量。
pcp->count++;
如果这个 CPU 的高速缓存链表的内存页面数量大于设定的上限。
if (pcp->count >= pcp->high) {
free_pages_bulk(zone, pcp->batch, &pcp->list, 0);
pcp->count -= pcp->batch;
}
恢复当前 CPU 的中断标识。
local_irq_restore(flags);
使能内核抢占。
put_cpu();
}
********************************************
这个 free_pages_bulk() 函数按照伙伴系统策略释放页面。
这个函数释放一个由内存页面组成的内存块,这些页面在相同的内存管理区。
在这个函数中清除这个内存管理区的 all_unreclaimable 标志,这个标志不为 0 ,表明这个管理区
的页面都为不可回收的,在释放页面时,把这个标志设为 0 。
---------------------------------------
static void free_pages_bulk(struct zone *zone, int count,
struct list_head *list, int order)
{
获得这个 zone 的自旋锁。
spin_lock(&zone->lock);
当管理区填满不可回收的页面是使用此标志。
zone->all_unreclaimable = 0;
这个成员 pages_scanned 成员是页面回收时使用的计数器。
zone->pages_scanned = 0;
遍历这个链表的所有页面,调用 __free_one_page() 函数释放所有的页面。
while (count--) {
struct page *page;
VM_BUG_ON(list_empty(list));
取得 struct page 结构的指针。
page = list_entry(list->prev, struct page, lru);
把 struct page 结构从 struct page 结构的 lru 组成的链表上删除。
从上面可以看出, struct page 结构的 lru 成员组成了 pcp->list 的链表。
list_del(&page->lru);
调用伙伴分配系统的页面释放函数,释放一个页面。参考下面说明。
__free_one_page(page, zone, order);
}
释放这个 zone 的自旋锁。
spin_unlock(&zone->lock);
}
********************************************
static void __free_pages_ok(struct page *page, unsigned int order)
{
unsigned long flags;
int i;
int reserved = 0;
对要释放的页面进行检查,这些页面释放可以释放。
for (i = 0 ; i < (1 << order) ; ++i)
reserved += free_pages_check(page + i);
如果 reserved 不为 0, 这些页面中有一些正在使用,不能够释放。
if (reserved)
return;
如果不是高端内存的页面,检测是否有代码拥有这个页面空间的锁。
如果没有定义 CONFIG_LOCKDEP 这个宏,这个函数为空函数。
if (!PageHighMem(page))
debug_check_no_locks_freed(page_address(page),PAGE_SIZE<
如果定义了 HAVE_ARCH_FREE_PAGE 这个宏,有自己特定体系结构的释放函数。
这个不是空函数,否则这个函数为空函数。
arch_free_page(page, order);
如果没有定义 CONFIG_DEBUG_PAGEALLOC 这个宏,这个函数就是空函数。
kernel_map_pages(page, 1 << order, 0);
禁止当前 CPU 的中断,并保存中断标识。
local_irq_save(flags);
如果没有定义 CONFIG_VM_EVENT_COUNTERS 宏,这个函数为空函数。
这个函数增加 VW EVENT 的计数。每个 CPU 一个 VW EVENT 的计数。
__count_vm_events(PGFREE, 1 << order);
这个函数是 __free_one_page() 函数封装。
free_one_page(page_zone(page), page, order);
恢复当前 CPU 的中断标识。
local_irq_restore(flags);
}
********************************************
enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT,
FOR_ALL_ZONES(PGALLOC),
PGFREE, PGACTIVATE, PGDEACTIVATE,
PGFAULT, PGMAJFAULT,
FOR_ALL_ZONES(PGREFILL),
FOR_ALL_ZONES(PGSTEAL),
FOR_ALL_ZONES(PGSCAN_KSWAPD),
FOR_ALL_ZONES(PGSCAN_DIRECT),
PGINODESTEAL, SLABS_SCANNED, KSWAPD_STEAL, KSWAPD_INODESTEAL,
PAGEOUTRUN, ALLOCSTALL, PGROTATED,
NR_VM_EVENT_ITEMS
} ;
使用 ENUM 定义了 VW 的事件类型。
---------------------------------------
struct vm_event_state {
unsigned long event[NR_VM_EVENT_ITEMS];
};
DECLARE_PER_CPU(struct vm_event_state, vm_event_states);
定义每 CPU 变量 vm_event_state 类型的 vm_event_states 变量。
实际上是是一个 unsigned long 类型的数组,定义了 VW 的各种事件计数器。
---------------------------------------
static inline void __count_vm_event(enum vm_event_item item)
{
__get_cpu_var(vm_event_states).event[item]++;
}
********************************************
这个函数释放内存页面,把内存页面加入伙伴系统的空闲页面链表。
在这个函数中清除这个内存管理区的 all_unreclaimable 标志,这个标志不为 0 ,表明这个管理区
的页面都为不可回收的,在释放页面时,把这个标志设为 0 。
---------------------------------------
static void free_one_page(struct zone *zone, struct page *page, int order)
{
获得这个 zone 的自旋锁。
spin_lock(&zone->lock);
当管理区填满不可回收的页面是使用此标志。
zone->all_unreclaimable = 0;
这个成员 pages_scanned 成员是页面回收时使用的计数器。
zone->pages_scanned = 0;
调用伙伴分配系统的页面释放函数,释放一个页面。参考下面说明。
__free_one_page(page, zone, order);
释放这个 zone 的自旋锁。
spin_unlock(&zone->lock);
}
********************************************
伙伴系统算法是用来做内存管理的经典算法,目的是为了解决内存的外碎片。
避免外碎片的方法有两种:
1,利用分页单元把一组非连续的空闲页框映射到非连续的线性地址区间。
2,开发适当的技术来记录现存的空闲连续页框块的情况,以尽量避免为满足对小块的请求而把大块的空闲块进行分割。
内核选择第二种避免方法。
伙伴系统算法将所有空闲页框分组为11个块链表,每个块链表的每个块元素分别
包含 1、2、4、8、16、32、64、128、256、512、1024 个连续的页框,每个块的第一个页框的物理地址是该块大小的整数倍。
满足以下条件的两个块称为伙伴:
1) 两个块具有相同的大小,记做 b;
2) 它们的物理地址是连续的;
3) 第一个块的第一个页框的物理地址是2*b*2^12的倍数。
该算法迭代,如果成功合并所释放的块,会试图合并2b的块来形成更大的块。
参考《深入理解linux内核(第三版)》第八章 P312。
每个内存管理区描述符有一个 free_area[] 数组,这个数组每个元素对应这一种内存块的大小。
即第 k 个元素有 2^k 大小的内存块组成组成链表。
这个数组每个元素的 free_list 字段是一个双向链表的头,这个链表由 struct page 结构体
的 lru 成员组成。
---------------------------------------
static inline void __free_one_page(struct page *page,
struct zone *zone, unsigned int order)
{
unsigned long page_idx;
int order_size = 1 << order;
测试 PG_conmound 标志,这个标志表明是否是通过扩展分页机制处理的页面。
if (unlikely(PageCompound(page)))
destroy_compound_page(page, order);
把 page 转换成页面的物理地址。
page_idx = page_to_pfn(page) & ((1 << MAX_ORDER) - 1);
VM_BUG_ON(page_idx & (order_size - 1));
VM_BUG_ON(bad_range(zone, page));
这个函数根据是否定义 CONFIG_SMP 有所不同。
__mod_zone_page_state(zone, NR_FREE_PAGES, order_size);
循环 MAX_ORDER -1 - order 次,每次尽量把一个块和它的伙伴进行合并。
函数从最小的块开始,然后向上移动到最大的块。
while (order < MAX_ORDER-1) {
unsigned long combined_idx;
struct free_area *area;
struct page *buddy;
寻找要释放块的伙伴,参考下面。
buddy = __page_find_buddy(page, page_idx, order);
调用 page_is_buddy() 函数检查 buddy 是否描述的 order_size 的空闲页面的第一个页。
if (!page_is_buddy(page, buddy, order))
break;
把 buddy 从原来的空闲页面链表 free_area 的 free_list 链表摘除。
list_del(&buddy->lru);
把 order 大小的内存块计数减小。
area = zone->free_area + order;
area->nr_free--;
清除 struct page 的 flag 成员的 PG_buddy 标志,设置 page 的 private 成员等于 0。
rmv_page_order(buddy);
取得两个内存块合并之后的地址。
combined_idx = __find_combined_index(page_idx, order);
取得合并之后的内存的第一个 struct page 的指针。
page = page + (combined_idx - page_idx);
page_idx = combined_idx;
order++;
} 当再也找不到伙伴了之后,跳出循环。
设置 struct page 的 flag 成员的 PG_buddy 标志,设置 page 的 private 成员等于 order。
set_page_order(page, order);
把合并之后的内存的第一个 struct page 的 lru 加入 当前内存管理区的 free_area[] 数组
的 free_list 元素的链表。
list_add(&page->lru, &zone->free_area[order].free_list);
把加入 order 的链表计数加 1 。
zone->free_area[order].nr_free++;
}
---------------------------------------
static inline struct page *
__page_find_buddy(struct page *page, unsigned long page_idx, unsigned int order)
{
这个内存的伙伴的地址为 page_idx + order_size 或者 page_idx - order_size 。
即把 page_idx 的地址的 order 为清 0 或者 置 1 。
unsigned long buddy_idx = page_idx ^ (1 << order);
返回伙伴的 struct page 指针。
return page + (buddy_idx - page_idx);
}
---------------------------------------
static inline int page_is_buddy(struct page *page, struct page *buddy, int order)
{
这个 buddy 不是内存 holes 的空间,是有效的内存空间。
if (!pfn_valid_within(page_to_pfn(buddy)))
return 0;
这个 buddy 和 page 在一个内存管理区。
if (page_zone_id(page) != page_zone_id(buddy))
return 0;
这个 buddy 和 page 有相同的 order,即两个空间大小相同。
if (PageBuddy(buddy) && page_order(buddy) == order) {
BUG_ON(page_count(buddy) != 0);
return 1;
}
return 0;
}
---------------------------------------
static inline void rmv_page_order(struct page *page)
{
清除 struct page 的 flag 成员的 PG_buddy 标志。
__ClearPageBuddy(page);
设置 page 的 private 成员等于 0。
在伙伴系统中用于保存内存的 order 大小。
set_page_private(page, 0);
}
---------------------------------------
static inline void set_page_order(struct page *page, int order)
{
设置 page 的 private 成员等于 order。在伙伴系统中用于保存内存的 order 大小。
set_page_private(page, order);
设置 struct page 的 flag 成员的 PG_buddy 标志。
__SetPageBuddy(page);
}
********************************************
如果没有定义 CONFIG_SMP 这个宏。
static inline void __mod_zone_page_state(struct zone *zone,
enum zone_stat_item item, int delta)
{
参考下面。
zone_page_state_add(delta, zone, item);
}
---------------------------------------
如果定义了 CONFIG_SMP 这个宏。
void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
int delta)
{
获取当前 CPU 的管理区高速缓存的 per_cpu_pages 描述符的地址。
struct per_cpu_pageset *pcp = zone_pcp(zone, smp_processor_id());
s8 *p = pcp->vm_stat_diff + item;
long x;
x = delta + *p;
if (unlikely(x > pcp->stat_threshold || x < -pcp->stat_threshold)) {
zone_page_state_add(x, zone, item);
x = 0;
}
*p = x;
}
---------------------------------------
static inline void zone_page_state_add(long x, struct zone *zone,
enum zone_stat_item item)
{
atomic_long_add(x, &zone->vm_stat[item]);
atomic_long_add(x, &vm_stat[item]);
}
在内存管理区描述服中有 vm_stat 成员 atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS] 统计
本管理区的一些状态计数。
也有一个公共的 vm_stat 变量,atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS] 统计
全局的管理区的一些状态计数。
********************************************
问题:
1) 这个 zone_page_state() 函数的意思?
2)在 struct per_cpu_pageset 结构体中 stat_threshold 和 vm_stat_diff 的意义?
阅读(2321) | 评论(0) | 转发(0) |