首页 > 代码库 > Linux-0.11内核源码分析系列:内存管理up_wp_page()与do_wp_page()函数分析

Linux-0.11内核源码分析系列:内存管理up_wp_page()与do_wp_page()函数分析

/*
 * up_wp_page()函数用于解除物理页的共享状态,同时给发生写时复制的进程提供一页新的
 * 物理页,新物理页是之前共享页的数据相同的拷贝。
 * table_entry是共享物理页的地址的指针,即页表实际地址+表内偏移地址
 */
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) {
		*table_entry |= 2;    //如果该页位于主内存区,且不是共享页,设为可写
		invalidate();         //刷新页变换高速缓存
		return;               //直接退出
	}
	if (!(new_page=get_free_page()))  //如果是共享页,申请一页新物理页    
		oom();                    //如果0 = get_free_page(),内存溢出,die
	if (old_page >= LOW_MEM)          //如果共享页在主内存区
		mem_map[MAP_NR(old_page)]--;    //共享次数-1
	*table_entry = new_page | 7;            //将新页挂载到写时复制进程的页表中
	invalidate();                           //刷新页变换高速缓存
	copy_page(old_page,new_page);           //拷贝old_page内容到new_page
}	
  1 /*************************************************************************
  2  > File Name: table.c         
  3  > Author: Linpeng1577        
  4  > Mail:linpeng1577@gmail.com 
  5  > Created Time: Mon 24 Nov 2014 06:05:54 PM PST
  6  ************************************************************************/
  7                               
  8 #include<stdio.h>             
  9                               
 10 static int page_dir[1023]        = {0};
 11 static int page_table[1023]      = {0};
 12 static int phy_addr_table[1023]  = {0};
 13 static int new_phy_addr_table[1023] = {0};
 14                               
 15 int main(void)                
 16 {                             
 17     int *phy_page = NULL;     
 18                               
 19     phy_page        = phy_addr_table;
 20     page_table[114] = (int)phy_page;
 21     page_dir[83]    = (int)(&page_table);
 22                               
 23     printf("phy_addr_table = 0x%x\n", (int)phy_addr_table);
 24     printf("phy_page       = 0x%x\n", (int)phy_page);
 25     printf("page_table     = 0x%x\n", (int)page_table);
 26     printf("page_dir       = 0x%x\n", (int)page_dir);
 27                               
 28     printf("*(&page_dir[83])    = 0x%x\n", *(&page_dir[83]));
 29     printf("*(&page_table[114]) = 0x%x\n", *(&page_table[114]));
 30                               
 31     *(&page_table[114]) = (int)new_phy_addr_table;                                     
 32     printf("new_phy_addr_table  = 0x%x\n", (int)new_phy_addr_table);
 33     printf("*(&page_table[114]) = 0x%x\n", *(&page_table[114]));
 34 }                             
~                                                                                          
~                                                                                          
</lin/tests/linux/test_ptr/table.c [FORMAT=unix] [TYPE=C] [POS=31,30][91%] 24/11/14 - 21:27
"table.c" 34L, 1162C


root@ubuntu:/home/iubuntu/lin/tests/linux/test_ptr# ./table 
phy_addr_table = 0x804c040 //共享物理页地址
phy_page       = 0x804c040 
page_table     = 0x804b040 //页表地址
page_dir       = 0x804a040 //页目录地址
*(&page_dir[83])    = 0x804b040  
*(&page_table[114]) = 0x804c040  //old_page = 0xfffff000 & *table_entry;
new_phy_addr_table  = 0x804d040  //新物理页地址
*(&page_table[114]) = 0x804d040  //*table_entry = new_page | 7; 

1.假设存在实际物理地址phy_page = phy_addr_table;
2.这个地址由页表page_table的第114项管理,page_table[114] = phy_page;
3.这个页表的地址由页目录表page_dir的第83项管理,page_dir[83] = page_table;
4.现在已经知道页目录表page_dir,页表page_table,页表由第83页目录项管理,物理页由页表
  第114项管理,现在要取物理页地址phy_page;
5.通过 *(&page_dir[83])可以得到页表page_table的地址;
6.通过 *(&page_table[114])可以得到物理页地址phy_addr;
7.在void un_wp_page(unsigned long * table_entry)中,table_entry
  扮演着类似(&page_table[114])的角色。

/*
 * 用于写时复制
 * 通过给写共享页的进程分配新页,同时前共享页共享计数减1
 *
 * 如果该页属于代码段,报错
 */
      
/*
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 *
 * If it's in code space we exit with a segment error.
 */
void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0 //被linus屏蔽的代码
/* 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((unsigned long *)
		(((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc)))));
        /*
        address是线性地址
        
        (unsigned long *)
              (((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc))))
        
        这里的前提是pg_dir = 0;
        上面的地址可以分为下面[1][2]组合
        
        [1]((address>>10) & 0xffc)用于计算物理页在页表的偏移索引
        这里是取物理页地址,每个页表管理1024个页表项,每个页表项占4个字节
        每个页表项可以管理一个物理页
        所以这里是((address>>12) & 0x3ff)<<2
        
        [2](0xfffff000 & *((unsigned long *) ((address>>20) &0xffc))
        这里是取页表地址值
        页表保存在页目录项中
        1个页目录表有1024个页目录项
        1个页目录项占4个字节
        1个页目录项管理1个页表
        1个页表管理1024项页表项
        1个页表项管理1个物理页
        1个物理页占4K字节 = 1024*4
        即1个页表项管理4K连续内存
        页目录项数(1024) X 页表数(1024) X 4K = 4096M = 4G
        ((address>>22) &0x3ff))<<2这个是页目录项在页目录表中的偏移地址
        (unsigned long *) ((address>>22) &0x3ff))<<2是取保存在该目录项中页表的物理地址
        所以物理页被保存的地址是[2]页表的物理地址+[1]页表内偏移地址
        所以物理页的地址是
        (unsigned long *) (((address>>12) & 0x3ff)<<2) + (unsigned long *) ((address>>22) &0x3ff))<<2
              指针取指           页表内偏移地址                         页表物理地址       
}       */

Linux-0.11内核源码分析系列:内存管理up_wp_page()与do_wp_page()函数分析