首页 > 代码库 > 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>