linux/mm.memory.c/copy_page_tables

// 总的来说,copy_page_tables是一个被fork调用的功能函数,完成在fork一个
// 子进程时将父进程的内存以一个页表为单位复制给子进程,从而让子进程可以
// 共享父进程的内存页面。
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;


// 和free_page_tables函数一样,copy_page_tables也是一次操作一个页表和其包含
// 的1024个页面。因此函数开头要先检测from和to,确保地址为4MB对齐
if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
// 计算from地址对应的页表在页目录表中表项首地址指针
from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
// 计算to对应在页目录表中的表项首地址指针
to_dir = (unsigned long *) ((to>>20) & 0xffc);
// 将size由字节单位转换为页表个数
size = ((unsigned) (size+0x3fffff)) >> 22;
// 循环处理每个页表
for( ; size-->0 ; from_dir++,to_dir++) {
// to_dir的P位为1(表示to_dir已经有了对应的页表)则报错,
// 因此即将被复制到的地址肯定要是个空闲页目录表项
if (1 & *to_dir)
panic("copy_page_tables: already exist");
// from的P位为0则报错,因此复制源肯定要是个有效页目录表项。
// 这里要注意如果to_dir不为空闲,即认为是致命错误,要终止进程的
// 而from_dir为空闲则只是跳过不复制而继续处理后面一个页表(size++)
if (!(1 & *from_dir))
continue;
// from_page_table是源页表基地址指针,from_dir是源页目录表项指针,
// 实际上from_dir指向的页目录表项内存放的即为页目录表基地址地址值
// 所以from_page_table和from_dir效果实际上是相同的
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
// get_free_page()内嵌汇编形式函数,功能为在主内存区内页内存中获取
// 一页空闲内存,并返回该页内存的起始地址。函数内部实际是通过查找
// mem_map数组,从mem_map的最后一个字节开始向前查找,直到找到一个
// 内容为0的字节,并按照对应关系反向计算得到这个mem_map项对应的物理
// 内存首地址,将之返回
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
// *to_dir为目的地址在页目录表中对应表项内容。to_page_table是新得到的
// 一页内存的基地址,将to_page_table赋给*to_dir即完成了新申请到的一页
// 内存到目的页目录表目录项的挂接。可见这个新申请的一页内存是用来走目的
// 页表的。 |7人为地强行改变了目的页表在页目录中表项内容的低三位,这三位
// 置1表示用户级、可读写、存在,实际是对这个新申请的目的页表的属性设置
*to_dir = ((unsigned long) to_page_table) | 7;
// 确定了目的页表,然后就是复制页表内的页面了。nr即代表要复制的页表内的
// 页面数,有两种情况。如果from=0,即从内核空间复制则nr=160,如果是其他
// 则nr=1024
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
// this_page用来中转源页表和目的页表的各个表项。之所以不直接复制而要
// 用this_page来中转这个表项内容,是因为还要先检测和处理
this_page = *from_page_table;
// 若源页表这个表项无效,则不用复制,直接continue跳过
if (!(1 & this_page))
continue;
// 将页表项bit1置0,将该页面设置为只读。从这里可以看出,在复制进程页表
// 的整个操作里有以下几个特点:
// 1、复制是以页表为单位向上取整的。按照Linus的说法这样是为了简化操作
// 2、复制操作中实际只用到了一页物理内存,这页是用来放页表的。而页表
// 对应的1024个物理页面实际并未在此时获取,猜测是在execve时才会copy_on_write
// 3、复制后将目的页表的属性设置成了用户级、可读写、存在(在页目录表的
// 表项中设置);而将页面属性设置成了用户级、只读、存在(在页表表项中设置)
// 这样因为在execve时实际是使用页表内页面的,所以可以触发do_wp_page
this_page &= ~2;
*to_page_table = this_page;
if (this_page > LOW_MEM) {
// 若源页面不在内核区,即父进程不是进程0或进程1时(用户进程都是由shell进程
// 或其子进程建立的,所以父进程至少为2,因此这里都要执行的。只有在进程0创建
// 进程1和进程1创建进程2时可以绕过这里),将源页表内表项属性也设置为只读
// 这样就实现了父进程创建子进程后,两个进程的页面都成了只读,不管对哪个进行
// 写操作,都会触发do_wp_page
*from_page_table = this_page;
// 一下三句是在mem_map中注册this_page。这个操作关系重大,内涵多多啊
// 首先必须清楚,这里并没有为目的页表在主内存区申请1024个物理页面,因此也不
// 需要在mem_map中注册1024个新的页面。真正新申请的物理页面只有页表占有的一个
// 其次要明白,在两个页表之间复制时是完全的表项复制,复制后两个页表内1024个
// 表项完全相同(跟原来相比,只是权限都变成了只读),这就揭示了一个重点:复制
// 后新进程的页表内1024个页面,实际对应的物理页面其实就是父进程这个页表对应的
// 1024个页面。所以这里在mem_map中的操作,其实还是对父进程页表内的1024个页面
// 的引用计数进行更改。
// 所以这里的mem_map[this_page]++,就表示父进程页表对应的1024个物理页面,在这里
// 被子进程再次引用了一遍,所以这1024个页面的引用记录就要再加一次。
// 实际上子进程建立后,肯定会execve来加载执行自己的程序,所以肯定要有自己的页面
// 的,所以在execve里缺页或者do_wp_page来给子进程分配物理内存时还是要解除子进程
// 和父进程的物理页面之间的关联的,那时肯定又会再次操作mem_map里这些父进程页面
// 对应的项目,让他们再次减一。
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++;
}
}
}
invalidate();
return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

朱有鹏老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值