linux/mm/memory.c/do_wp_page and un_wp_page

void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
if (CODE_SPACE(address))
do_exit(SIGSEGV);
#endif
// 调用un_wp_page函数来处理写共享页面的问题。这里传给un_wp_page函数
// 的参数是要写的共享页面的物理地址。而参数address是线性地址,所以
// 这个函数的关键技术就是将address这个线性地址转成物理地址并传参给
// un_wp_page函数。
// 线性地址怎么转成物理地址呢?要参考分页内存管理的映射图。在linux0.11
// 的分页内存管理系统中,线性地址被分成三部分:前10bit为页目录表中表项
// 索引,查页目录表可得页表基地址;次10bit为页表中页面索引,查表可得
// 页面基地址,最后12bit为页面内偏移值,可以精确定位到内存的字节单元。
// 因此物理地址和线性地址的转换就要参考这个映射规律了。在这里将线性地址
// address拆开成三份,用前两份来计算合成这个物理地址。具体方法是:
// 首先(address>>10)&0xffc得到address所在页面在其页表中的索引值,其次
// (address>>20)&0xffc得到address所在页表在页目录表中的索引值。而关键就在于
// 物理地址和线性地址的分界线---这个分界线就是页目录表中的表项,也即页表的
// 基地址。所以用address分析索引到了页目录表内,也就和物理地址挂上钩了。只要
// 根据(address>>20)&0xffc这个索引取值得到的页表基地址,就是物理地址的最高10
// bit了,物理地址的次10bit仍然是(address>>10)&0xffc,低12bit是页面内偏移不去
// 论之。
// 写到这里其实不难发现,线性地址和物理地址之所以能够映射,功劳全在
// 页目录表。页目录表很特殊,它处于物理内存0x00000000处,这个位置开始的一页
// 的线性地址和物理地址是重合的。这个重合就为两种地址机制的映射提供了契机。
// 任意给定的线性地址,很容易根据其前10bit在页目录表中找到对应的表项,然后从
// 这个表项中拿出来的就成了物理地址的页表基地址,这样线性地址的后22bit其实一直
// 都是在物理地址区间在使用的。
// 再进一步思考,从线性地址到物理地址很容易映射,那么从物理地址到线性地址能不能
// 实现映射呢?我得出的结论是不能···因为至少这样的映射不唯一,至少在copy_page_tables
// 函数中就实现了两个不同的线性地址对应一个物理地址。实际上也很好理解,只要在
// 页目录表中让两个表项中内容相同,那么这两个表项代表的两个线性地址就对应到同一个
// 物理地址中去了。而我们如果想从物理地址找回来找到对应的线性地址,除非先用这个
// 物理地址的次10bit得到他的页表基地址,然后在页目录表中去遍历它的1024个表项,看看
// 哪个表项中的页表地址和这个地址相同,才能知道这个物理地址反映射回去的线性地址。
// 这样看来,其实通过查一个物理地址在mem_map数组中的引用记录,如果它只被一个进程
// 引用,那么倒是可以通过遍历页目录表来反映射到它的线性地址了。即使它被共享了,也
// 可以通过遍历来得到共享这页物理内存的进程的线性地址,从而得知这些进程是哪几个进程
// 所以刚才的结论是错误的,是可以反映射的,只是麻烦些。不知道实际有没有这种需求
un_wp_page((unsigned long *)
(((address>>10) & 0xffc) + (0xfffff000 &
*((unsigned long *) ((address>>20) &0xffc)))));


}

// un_wp_page即为un_write_protection_page,用来解除内存页面只读的问题
// 该函数被do_wp_page调用。这里的table_entry为一页内存的物理地址
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page,new_page;


// 内存页面只读有两种情况。一种是该内存页面只被引用了一次,即该页面不是
// 共享页面。(按照我的分析,这种情况就是子进程的只读页面。因为在copy_page_tables
// 内把父子进程的页面都设置成了只读,所以对哪个进行写操作都会触发这个异常
// 最终调用过来。子进程的页面申请后就只被引用过一次,而父进程至少会被引用
// 过两次,所以属于另一种情况了)
old_page = 0xfffff000 & *table_entry;
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
*table_entry |= 2;
invalidate();
return;
}
// 能执行到这里,即说明是另一种情况,即该页面被引用了多于一次。这种情况应该是用户
// 建立了子进程后先对父进程进行了写操作触发了异常。这种情况下首先要申请新的页面,
// 然后对原页面引用次数减一(我在看copy_page_tables的时候就预料到在处理共享页面时
// 一定要再执行减1操作,现在得到了证实)将新页面的地址先设置为可读写,然后再覆盖掉
// 原地址。最后用copy_page函数将原页面的一页内容复制到新页面内。
if (!(new_page=get_free_page()))
oom();
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]--;
*table_entry = new_page | 7;
invalidate();
copy_page(old_page,new_page);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朱有鹏老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值