通过上面的讨论,已经涉及了本文件的诸多函数,这里对已经有说明的文件
一笔带过,对感兴趣的,做个分析注解.
头六个函数就不多说了,见上面的分析.
(1) page cache 初始化
/*
* mempages: 物理页面个数
*/
void __init page_cache_init(unsigned long mempages)
{
unsigned long htable_size, order;
/*计算要为hash 表分配多少内存, 及其order值(power of 2)*/
htable_size = mempages;
htable_size *= sizeof(struct page *);
for(order = 0; (PAGE_SIZE << order) < htable_size; order++)
;
*计划分配一个能容下所有物理页的hash表,就看又没内存*/
do {
/*这个order能够容下的page个数数*/
unsigned long tmp = (PAGE_SIZE << order) / sizeof(struct page *);
/*计算这么大的表对应的hash值(hash表下标)最多有多少位*/
page_hash_bits = 0;
while((tmp >>= 1UL) != 0UL)
page_hash_bits++;
page_hash_table = (struct page **) /*看看有没有这么多连续内存*/
__get_free_pages(GFP_ATOMIC, order);
} while(page_hash_table == NULL && --order > 0);/*没有的话尝试少分点*/
printk("Page-cache hash table entries: %d (order: %ld, %ld bytes)\n",
(1 << page_hash_bits), order, (PAGE_SIZE << order));
if (!page_hash_table)
panic("Failed to allocate page hash table\n");
memset((void *)page_hash_table, 0, PAGE_HASH_SIZE * sizeof(struct page *));
}
(2) TryLockPage,lock_page和UnlockPage
static inline int sync_page(struct page *page)
逻辑简单,调用mapping->a_ops->sync_page(page),对于ext2就是ext2_aops
->block_sync_page->run_task_queue(&tq_disk)(fs/buffer.c).让磁盘有更多
机会运行回写,读入等任务.提供给 ___wait_on_page,__lock_page使用.
/*
* Wait for a page to get unlocked.
*
* This must be called with the caller "holding" the page,
* ie with increased "page->count" so that the page won't
* go away during the wait..
*/
void ___wait_on_page(struct page *page)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);
add_wait_queue(&page->wait, &wait);
do {
sync_page(page); /*给磁盘(may be other dev)一点运行机会
*说不定就不用再等了
*/
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
if (!PageLocked(page))
break;
run_task_queue(&tq_disk);/**/
schedule();
} while (PageLocked(page));
tsk->state = TASK_RUNNING;
remove_wait_queue(&page->wait, &wait);
}
也没有什么可以多说的,等待页面解锁时给页面同步相关的task queue多些运行
时间. void lock_page(struct page *page)和static void __lock_page(struct
page *page)同此函数.
include/linux/mm.h定义了
#define UnlockPage(page) do { \
smp_mb__before_clear_bit(); \
if (!test_and_clear_bit(PG_locked, &(page)->flags)) BUG(); \
smp_mb__after_clear_bit(); \
if (waitqueue_active(&page->wait)) \
wake_up(&page->wait); \
} while (0)
并且注释也说明了两个barrier的作用,
当用
TryLockPage
......
UnlockPage
组成一个临界区的时候,第一个barrier保证test_and_clear_bit在
test_and_set_bit之后执行,第二个barrier保证test_and_clear_bit和访问
wait_queue的次序.
问题是如何使用lock_page, UnlockPage,使用时机是什么?内核注释为"在进
行page上的IO操作时必须lock_page",这种解释有些简略.正在进行io的页面有如
下特征(几个典型情况):
1)如果页面归user space的进程使用,肯定是swap cache在进行io操作,并且页
面已经从用户的页表断开.
2)如果是user task进行文件读写操作,启动io的页面是page cache(normal file)
或者buffer cache.
3)如果是mmap,读写亦通过page cache进行.
4)首先page io在大部分情况下是一个异步操作,kernel不会"停下来"等待磁盘
操作的完成. 如典型的page fault需要换入时,新分配一个页面,加入swap
cache,启动io,最后当前进程wait on page.有可能内核处理swap的几个线程
会访问到此页,此种情况下需要进行互斥操作,不能在一个页面上启动两个io
操作.
5)或者SMP的情况下,一边进行io换入,另一个cpu也可以进行lru操作.
我相信作者一开始的时候准备用page lock这个机制防止对page io的重入.
但是此锁还同步了更多的东西:
看加入swap cache的情况:
void add_to_swap_cache(struct page *page, swp_entry_t entry)
{
unsigned long flags;
#ifdef SWAP_CACHE_INFO
swap_cache_add_total++;
#endif
if (!PageLocked(page)) //如果页面未锁,禁止加入swap cachecolor=blue>
BUG(); //出现此种情况是内核的bug
..................
}
为何加入page cache需要上锁?看下面这个函数
void add_to_page_cache_locked(struct page * page, struct address_space
*mapping, unsigned long index)
{
if (!PageLocked(page))
BUG();
page_cache_get(page); /*增加引用计数*/
}
恩,对页面的引用计数增一,想一想还操作了page->mapping.所以我的结论是,在
以下情况下需要page lock:
1.对page进行io操作
2.某些特定目的情况下操作page->mapping和page引用计数的情形
为了验证这个结论,搜索对lock_page的引用,绝大多数在进行page io操作,还有
部分处理加入/离开page cache,这些容易理解. 然后挑一个例子看看为什么也
使用了lock_page,先看一个filemap.c中的函数
/*
* Get the lock to a page atomically.
*/
struct page * __find_lock_page (struct address_space *mapping,
unsigned long offset, struct page **hash)
{
struct page *page;
/*
* We scan the hash list read-only. Addition to and removal from
* the hash-list needs a held write-lock.
*/
repeat:
spin_lock(&pagecache_lock); //操作page cache的锁color=blue>
page = __find_page_nolock(mapping, offset, *hash);
if (page) {
page_cache_get(page);
spin_unlock(&pagecache_lock);
lock_page(page); //判断page->mapping以求
//返回一个肯定在page cache
//的页面,必须锁定页面,否则
//可能被page cache清除
/* Is the page still hashed? Ok, good.. */
if (page->mapping)
return page;
/* Nope: we raced. Release and try again.. */
UnlockPage(page);
page_cache_release(page);
goto repeat;
}
spin_unlock(&pagecache_lock);
return NULL;
}
使用page lock的原因已经写入注释,此函数返回一个保证还在page cache的页,
并增加页面引用计数,可以直接拿来使用,如shmem_nopage.总之,如果你要保证
page->mapping有效的话,必须lock_page然后进行判断,内核多处如此使用.
接着分析一个特殊的例子
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct * vma,
unsigned long address, pte_t *page_table, pte_t pte)
{
struct page *old_page, *new_page;
old_page = pte_page(pte);
if (!VALID_PAGE(old_page))
goto bad_wp_page;
/*
* We can avoid the copy if:
* - we're the only user (count == 1)
* - the only other user is the swap cache,
* and the only swap cache user is itself,
* in which case we can just continue to
* use the same swap cache (it will be
* marked dirty).
*/
switch (page_count(old_page)) {
case 2:
/*
* Lock the page so that no one can look it up from
* the swap cache, grab a reference and start using it.
* Can not do lock_page, holding page_table_lock.
*/
if (!PageSwapCache(old_page) || TryLockPage(old_page))
break;
if (is_page_shared(old_page)) {
UnlockPage(old_page);
break;
}
UnlockPage(old_page); //解锁后如果有人从swap
cache共享了页面呢?
/* FallThrough */
case 1:
flush_cache_page(vma, address);
establish_pte(vma, address, page_table,
pte_mkyoung(pte_mkdirty(pte_mkwrite(pte))));
spin_unlock(&mm->page_table_lock);
return 1; /* Minor fault */
}
................
}
这个地方注释详尽,为了避免其他执行流从swap cache(only swap)共享此页
面,对页面加锁.但解锁之后设置pte可写是否正确呢?(解锁了,其他人即可共享啊)
我认为:
1)即使加锁后使pte可写,也无济于事,因为其他执行流照样可共享此页.
2)其他执行流共享此页后,不可能直接容许写,但到COW处理,重入此函数后
引用计数大于2,必须copy. 故不会出错.
3)如果计算是否是共享页面时不加锁则有可能两个进程同时拥有对此页面的
写权限.
(不能够是如此复杂的解释,到底应该怎样理解同步与互斥?2.22的确简单,这里有个
smp的大锁,lock kernel)这个锁锁定了一个临界区,保证计算一个确定的状态,同
时保证这个函数重入后不会的到相同的计算结果。
另一个类似函数是
static int do_swap_page(struct mm_struct * mm,
struct vm_area_struct * vma, unsigned long address,
pte_t * page_table, swp_entry_t entry, int write_access)
{
...........
/*
* Freeze the "shared"ness of the page, ie page_count + swap_count.
* Must lock page before transferring our swap count to already
* obtained page count.
*/
lock_page(page);
swap_free(entry);
if (write_access && !is_page_shared(page))
pte = pte_mkwrite(pte_mkdirty(pte));
UnlockPage(page);
set_pte(page_table, pte);
...............
return 1; /* Minor fault */
}
阅读(2018) | 评论(0) | 转发(0) |