本系列的第三篇文章主要来介绍与共享物理页面相关的两个函数。
//在发生缺页异常的时,首先看看能否与运行同一个文件的其他进程进行页面共享处理。该函数首先判断系统中是否有另外进程也在运行与当前进程一样的执行文件。若有,则在系统当前任务中找寻这样的任务。若找到了这样的任务就尝试与其共享指定地址处的页面。判断系统中是否有另一个进程也在执行同一个可执行文件的方法是利用进程任务数据结构中的executable字段。该字段执行进程正在执行程序的内存中的i节点,根据该i节点的引用次数i_count我们就可以判断。
static int share_page(struct m_inode *inode,unsigned long address)
{
struct task_struct **p;
if(inode->i_count<2 || !inode)
return 0;
for(p=&LAST_TASK;p>&FIRST_TASK;--p)
{
if(!*p)
continue;
if(current==*p)
continue;
if(address<LIBRARY_OFFSET)
if(inode!=(*p)->executable)
continue;
else{
if(inode!=(*p)->library)
continue;
}
if(try_to_share(address,*p))
return 1;
}
return 0;
}
//在任务p中检查位于线性地址"address"处的页面,看页面是否存在,是否干净。如果p进程address处的页面存在并且没有被修改过的话,就让当前进程与p进程共享之。同时还需要验证置顶的地址处是否已经申请了页面,若是则出错。
static int try_to_share(unsigned long address,struct task_struct *p)
{
unsigned long from;
unsigned long to;
unsigned long from_page;
unsigned long to_page;
unsigned long phys_addr;
//首先分别求指定进程p中和当前进程中逻辑地址address对应的页目录项。为了计算方便,先计算出address处“逻辑"页目录项号,即以进程空间0-64MB算出的页目录项号。该"逻辑"页目录项号加上进程p在cpu 4G线性空间中起始地址对应的页目录项即得p中的地址address处页面所对应的4G线性空间中的实际页目录项from_page
from_page=to_page=((address>>20) & 0xffc);
from_page+=((p->start_code) & 0xffc);
to_page+=((current->start_code) & 0xffc);
//在得到p和当前进程address对应的页目录项后,下面分别对进程p和当前进程进行处理。首先对p进程的表项进程操作。目标是取得p进程中address对应的物理内存页面地址,并且该物理页面存在并且干净(没被修改)。方法是首先取目录项内容,如果该目录项无效(P=0),表示目录项对应的二级页表不存在,于是返回。否则取该目录项对应的页表地址from,从而计算出逻辑地址address对应的页表项指针,并取出该页表项内容临时保存在phys_addr中。
from=*(unsigned long*)from_page;
if(!(from & 1))
return 0;
from &=0xfffff000;
from_page=from+((address+10) &0xffc;
phys_addr=*(unsigned long*)from_page;
//接着看看页表映射的物理页面是否存在并干净。0x41对应页面中的D和P标志。如果页面不干净或者无效则返回。然后我们从该表象中取出物理地址保存在phys_addr中,最后我们检查这个物理页面地址的有效性。
if(phys_addr & 0x41)!=0x01)
return 0;
phys_addr &=0xfffff000;
if(phys_addr>=HIGH_MEMORY || phys_addr<LOW_MEM)
return 0;
//下面对当前进程的表项进程操作。目标是取得当前进程中address对应的页表项地址,并且该页表项还没有映射物理页面,即其P=0.首先取得当前进程页目录项内容->to。如果该目录项无效P=0,即目录项对应的二级页表不存在,则首先申请空闲物理页来存放页表,并更新目录象to_page内容,让其指向该内容页面。
to=*(unsigned long*)to_page;
if(!(to & 1))
{
if(to=get_free_page())
*(unsigned long*)to_page=to|7;
else
com();
}
to&=0xfffff000;
to_page=to+((address>>10) &0xffc);
if(1 & *(unsigned long*)to_page)
panic("try_to_share:to_page already exists");
//共享处理:首先对p进程的页表项进行修改,设置其写保护标志,然后让当前进程复制p进程这个页表项。此时当前进程逻辑地址address处页面就被映射到p进程逻辑地址address处映射的物理页面上。
*(unsigned long*)from_page&=~2;
*(unsigned long*)to_page=*(unsigned long*)from_page;
invalidate();
phys_addr-=LOW_MEM;
phys_addr>>12;
mem_map[phys_addr]++;
return 1;
}
使用一个图来总结share_page的工作: