/*
* 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()函数分析
原文地址:http://blog.csdn.net/linpeng12358/article/details/41510575