1、进程A和进程B共享页面,代码如下:
if (!(pid=fork())) { 压栈操作;//子进程B } if (pid>0){ 压栈操作;//父进程A }
2、我们假设现在系统有一个用户进程A,他自己对应的程序代码已经载入内存中,此时该进程内存中所占用的页面引用计数都为”1“,接下来他开始执行,通过调用fork函数创建一个新进程(进程B)。在新进程创建的过程中,系统将进程A的页表项全部复制给进程B,并设置进程B的页目录项。此时这两个进程就共享页面,被共享页面的引用计数累加为2,并将此共享页面全部设置为”只读“属性,即无论是进程A还是进程B,都只能对这些共享的页面进程读操作,而不能进行写操作。执行代码如下:
int copy_page_tables(unsigned long from,unsigned long to,long size) { ...... for( ; size-->0 ; from_dir++,to_dir++) { ...... for ( ; nr-- > 0 ; from_page_table++,to_page_table++) { this_page = *from_page_table; if (!(1 & this_page)) continue; this_page &= ~2;//进程A对页面的操作属性被设置为只读 *to_page_table = this_page;//进程B对页面的操作属性被设置为只读 if (this_page > LOW_MEM) { *from_page_table = this_page; this_page -= LOW_MEM; this_page >>= 12; mem_map[this_page]++;//引用计数记录在mem_map中,累加为2 } } } invalidate(); return 0; }
3、我们假设接下来轮到进程A执行,进程A接下来的动作是一个压栈动作,现在看看会发生什么。
现在进程A的程序对应的所有页面都是只读状态的。压栈是个写操作,所以会产生”页写保护“异常,”页写保护“中断对应的服务程序时up_wp_page()函数。
函数执行时,先要在主内存中申请一个空闲页面(以后我们称之为新页面),以便备份刚才压栈的位置所在页面(以后我们称之为原页面)的全部数据,然后将原页面的引用计数减1。
代码如下:
void un_wp_page(unsigned long * table_entry)//页表项,现在指向原页面 { unsigned long old_page,new_page; old_page = 0xfffff000 & *table_entry; ...... if (!(new_page=get_free_page()))//申请到新页面 oom(); if (old_page >= LOW_MEM) mem_map[MAP_NR(old_page)]--;//页面引用计数递减1 *table_entry = new_page | 7; //7的二进制形式为111,标志着新页面可读可写了,将进程A的页表项指向新申请的页面 invalidate(); copy_page(old_page,new_page);//复制原页面的内容到进程A新申请的页面 }
4、将进程A的页表项指向新申请的页面,代码是*table_entry = new_page | 7;
5、复制原页面的内容到进程A新申请的页面,代码是copy_page(old_page,new_page);如下图:
6、进程A在用户空间开始执行压栈,仿佛什么都没有发生,压栈的数据却已经压入了新申请的页面。
7、进程A执行一段时间后,就该轮到他的子进程--进程B执行了,进程B仍然使用着原页面。进行压栈操作,也会出现”页写保护“异常,执行up_wp_page()函数。由于原页面的引用计数已经被削减为1了,所以现在就要将原页面的属性设置为”可读可写“,如下:
void un_wp_page(unsigned long * table_entry) { unsigned long old_page,new_page; old_page = 0xfffff000 & *table_entry; if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {//发现原页面引用计数为1,不用共享了 *table_entry |= 2;//2的二进制形式为010,R/W位被设置为1,可读可写 invalidate(); return; } if (!(new_page=get_free_page())) oom(); if (old_page >= LOW_MEM) mem_map[MAP_NR(old_page)]--; *table_entry = new_page | 7; invalidate(); }
进程B在用户空间开始执行压栈,仿佛什么都没有发生,压栈的数据却已经压入了原页面。
最后形成如下图:
8、进程A和进程B在压栈数据的处理方面可以操作不同的页面了。这些页面都是可读可写的,而且引用计数都为1,以后彼此都不会干扰对方。
现在进程B并没有自己的程序,如果将来它有了自己的程序,就会和原页面解除关系,原页面的引用计数将会继续减1,于是变成0,系统将认定他为”空闲页面“,可以重新用于分配。