页写保护

    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,系统将认定他为”空闲页面“,可以重新用于分配。

你可能感兴趣的:(页写保护)