linux内存管理-页面的换入

在i386 CPU将一个线性地址映射成物理地址的过程中,如果该地址的映射已经建立,但是发现相应页面表项或目录项中的P(present)标志位为0,则表明相应的物理页面不在内存中,从而无法完成本次内存访问。从理论上说,也许应该把这种情况称为受阻而不是失败,因为映射的关系毕竟已经建立,理应与尚未建立映射的情况有所区别,所以我们称为断开。但是,CPU的MMU硬件并不区分这两种不同的情况,只要P标志位为0就都认为是页面映射失败,CPU就会产生一次页面异常(page fault)。事实上,CPU在映射过程中首先看的就是页面表项或目录项中的P标志位。只要P标志位为0,其余各个位段的值就无意义了。至于当一个页面不在内存中时,还是因为映射尚未建立,乃是软件,也就是页面异常处理程序的事情。在越界访问的情景中,我们曾看到在函数handle_pte_fault中的开头几行:

do_page_fault=>handle_mm_fault=>handle_pte_fault

static inline int handle_pte_fault(struct mm_struct *mm,
	struct vm_area_struct * vma, unsigned long address,
	int write_access, pte_t * pte)
{
	pte_t entry;

	/*
	 * We need the page table lock to synchronize with kswapd
	 * and the SMP-safe atomic PTE updates.
	 */
	spin_lock(&mm->page_table_lock);
	entry = *pte;
	if (!pte_present(entry)) {
		/*
		 * If it truly wasn't present, we know that kswapd
		 * and the PTE updates will not touch it later. So
		 * drop the lock.
		 */
		spin_unlock(&mm->page_table_lock);
		if (pte_none(entry))
			return do_no_page(mm, vma, address, write_access, pte);
		return do_swap_page(mm, vma, address, pte, pte_to_swp_entry(entry), write_access);
	}
......

这里,首先区分的是pte_present,也就是检查表项中的P标志位,看看物理页面是否在内存中。如果不在,则进而通过pte_none检查表项是否为空,即全0.如果为空就说明映射尚未建立,所以要调用do_no_page。这在以前的情景中已经看到过了。反之,如果非空,就说明映射已经建立,只是物理页面不在内存中,所以要通过do_swap_page,从交换设备上换入这个页面。本情景在handle_pte_fault之前的处理以及执行路线都与越界访问的情景相同,所以我们直接进入do_swap_page。这个函数的代码如下:

do_page_fault=>handle_mm_fault=>handle_pte_fault=>do_swap_page


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)
{
	struct page *page = lookup_swap_cache(entry);
	pte_t pte;

	if (!page) {
		lock_kernel();
		swapin_readahead(entry);
		page = read_swap_cache(entry);
		unlock_kernel();
		if (!page)
			return -1;

		flush_page_to_ram(page);
		flush_icache_page(vma, page);
	}

	mm->rss++;

	pte = mk_pte(page, vma->vm_page_prot);

	/*
	 * 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);
	/* No need to invalidate - it was non-present before */
	update_mmu_cache(vma, address, pte);
	return 1;	/* Minor fault */
}

先看看调用时传过来的参数是些什么。建议读者先回到前面通过越界访问扩充堆栈的情景中,顺着CPU的执行路线走一遍,搞清楚这些参数的来龙去脉。参数表中的mm、vma还有address是一目了然的,分别是指向当前进程的mm_struct结构的指针、所属虚存区间的vm_area_struct结构的指针以及映射失败的线性地址。

参数page_table指向映射失败的页面表项,而entry则为该表项的内容。我们以前说过,当物理页面在内存中时,页面表项是一个pte_t结构,指向一个内存页面;而当物理页面不在内存中时,则是一个swp_entry_t结构,指向一个盘上页面。二者实际上都是32位无符号整数。这里要指出,所谓不在内存中是逻辑意义上的,是对CPU的页面映射硬件而言,实际上这个页面很可能在不活跃页面队列中,甚至在活跃页面队列中。

还有一个参数write_access,表示当映射失败时所进行的访问种类(读写),这是在do_page_fault的switch语句中根据CPU产生的出错代码error_code的bit1决定的(注意,在那个switch语句中,default与case 2:之间没有break语句)。此后便逐层传了下来。

由于物理页面不在内存中,所以entry是指向一个盘上页面的类型类似于指针的索引项(加上若干标志位)。

。。。。。。

上一篇:方法的可变参数


下一篇:ConcurrentReferenceHashMap