首页 > 代码库 > Linux内核之内存管理(4)--缺页处理程序

Linux内核之内存管理(4)--缺页处理程序

本文主要解说缺页处理程序,凝视足够具体,不再解释。

//以下函数将一页内存页面映射到指定线性地址处,它返回页面的物理地址
//把一物理内存页面映射到线性地址空间指定处或者说把线性地址空间指定地址address处的页面映射到主内存区页面page上。主要工作是在相关也文件夹项和页表项中设置指定页面的信息。在处理缺页异常函数do_no_page中会调用这个函数。
參数:address--线性地址;page--是分配的主内存区中某一页面指针
static unsigned long put_page(unsigned long page,unsigned long address)
{
	unsigned long tmp,*page_table;
	//首先判定给定page的有效性。假设该页面位于LOW_MEM或者超出系统高端内存HIGH_MEMORY,则发出警告。再查看一下page是不是已经分配的页面。若没有发出警告,即判定其在内存页面映射字节图mem_map[]中相应字节是否已经置位。
	if(page<LOW_MEM || page>=HIGH_MEMORY)
		printk("Trying to put page at %p at %p",page,address);
	if(mem_map[(page-LOW_MEM)>>12]!=1)
		printk("mem_map disagrees with %p at %p\n",page,address);
	//依据參数指定的线性地址address计算其在页文件夹表中相应的文件夹项指针,并从中取得二级页表地址。假设该文件夹项有效(p=1),即指定的页表在内存中,则从中取得指定页表地址放到page_table变量中,否则申请一空暇页面给页表使用,并在相应文件夹项中设置相应标志,然后将该页表地址放到page_table变量中
	page_table=(unsigned long *)((address>>20) & 0xffc)
	if((*page_table) & 1)
		page_table=(unsigned long *) (0xfffff000 & *page_table);
	else
	{
		if(!(tmp=get_free_page())
			return 0;
		*page_table=tmp|7;
		page_table=(unsigned long *)tmp;
	}
	page_table[(address>>12) &0x3ff]=page|7;
	return page;
}
//运行缺页处理,页异常中断处理过程中调用的函数。在page.s程序中被调用。函数參数error_code和address是进程在訪问页面时由CPU因缺页产生异常而自己主动生成。error_code指出出错类型,address是产生缺页的线性地址。
//该函数首先查看所缺页是否在交换设备中,若是则交换进来。否则尝试与已载入的同样文件进行页面共享,或者仅仅是由于进程动态申请内存页面仅仅需映射一页物理内存页就可以。若共享操作不成功,那么仅仅能从相应文件里读入所缺的数据页面到指定线性地址处
void do_no_page(unsigned long error_code,unsigned long address)
{
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	int block,i;
	struct m_inode *inode;
	
	if(address<TASK_SIZE)
		printk("\n\rBAD! KERNEL PAGE MISSING\n\r");
	if(address-current->start_code>TASK_SIZE)
	{
		printk("Bad things happen:nonexistent page error in no_page\n\r");
		do_exit(SIGSEGV);
	}
	//然后依据指定的线性地址address求出其相应的二级页表项指针,并依据该页表项内容推断address处的页面是否在交换设备中。若是则调入页面并退出。方法是首先取指定线性地址address相应的文件夹项内容。假设相应二级页表存在,则取出该文件夹项中二级页表的地址,加上页表项偏移即得线性地址address相应的页面指针,从而获得页表项的内容。若页表项内容不为0而且页表项存在为P=0,则说明该页表项指定的物理页面应该在交换设备中。于是从交换设备中调入指定页面后退出
	page=*(unsigned long *)((address>>20) & 0xffc);
	if(page & 1)
	{
		page &=0xfffff000;
		page+=(address>>10) & 0xffc
		tmp=*(unsigned long *)page;
		if(tmp && !(1 & tmp))
		{
			swap_in(unsigned long*)page);
			return;
		}
	}
	//否则取线性空间中指定地址address处页面地址,并计算出指定线性地址在进程空间中相对于进程基址的偏移长度值tmp,即相应的逻辑地址。从而能够算出缺页页面在运行文件映像或在库文件里的详细起始数据块号。由于设备上存放的可运行文件映像第1块数据是程序头结构,因此在读取该文件时须要跳过第一块数据。所以须要首先计算缺页所在的数据块号。由于每块数据长度为BLOCK_SIZE=1KB,因此一页内存可存放4个数据块。进程逻辑地址tmp除以数据块大小再加1就可以得处缺少页面在运行映像文件里起始块号block。
	address &=0xfffff000;
	tmp=address-current->start_code;
	
	if(tmp>=LIBRARY_OFFSET){
		inode=current->library;
		block=1+(tmp-LIBRARY_OFFSET);
	}
	else if(tmp<current->end_data){
		inode=current->executable;
		block=1+tmp/BLOCK_SIZE;
	}else{
		inode=NULL;
		block=0
	}
	//是动态申请的数据内存页面
	if(!node)
	{
		get_empty_page(address);
		return;
	}
	//尝试共享tmp处的物理页面
	if(share_page(inode,tmp))
		return;
	if(!(page=get_free_page()))
		oom();
		
	for(i=0;i<4;block++,i++)
		nr[i]=bmap(inode,block);
	bread_page(page,inode->i_dev,nr);
	...
	if(put_page(page,address))
		return;
	free_page(page);
	oom();
}</span>