Tour to Linux0.11-copy_page_tables()
最近在阅读Linux0.11源码,除了感叹大神Linus Torvalds超凡的系统驾驭能力之外,对其C语言的功底和风格也佩服得五体投地。
这几天在看fork(),于是分享一下对页表拷贝的理解。copypagetables()函数的代码如下:
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;
if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);
size = ((unsigned) (size+0x3fffff)) >> 22;
for( ; size-->0 ; from_dir++,to_dir++) {
if (1 & *to_dir)
panic("copy_page_tables: already exist");
if (!(1 & *from_dir))
continue;
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
*to_dir = ((unsigned long) to_page_table) | 7;
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
this_page = *from_page_table;
if (!(1 & this_page))
continue;
this_page &= ~2;
*to_page_table = this_page;
if (this_page > LOW_MEM) {
*from_page_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++;
}
}
}
invalidate();
return 0;
}
地址对齐检查
检测父进程与子进程的基址是不是从0x000000开始的4M的整数倍的线性地址
if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
·
计算页目录项
每个页目录项(占用四个字节)管理着特定的线性地址空间,他们之间是一一对应的,比如:
0项(地址是0),管理范围是0MB~4MB
1项(地址是4),管理范围是4MB~8MB
2项(地址是8),管理范围是8MB~12MB
所以我们可以根据线性地址计算出页目录项地址
from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);
计算页目录项的数目
除以4MB,表明线性地址空间占用了多少个4MB,一个4MB对应一个页目录项,因此size为所需页目录项的个数,表明进程需要从to_dir开始的连续的size个页目录项
size = ((unsigned) (size+0x3fffff)) >> 22;
循环设置每一个目录项
for( ; size-->0 ; from_dir++,to_dir++)
页目录项最后一个比特为1,表示该目录项已经存在
if (1 & *to_dir)
panic("copy_page_tables: already exist");
父进程目录项最后一位为0,表示该目录项不存在,在父进程中并未使用
if (!(1 & *from_dir))
continue;
每个页目录项32位,其中高20位是页表地址,frompagetable获得父进程的页表地址
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
为子进程申请一个空闲页面,用来拷贝父进程的页表,如果申请成功,topagetable存放子进程的页表地址
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
为刚刚申请的页表设置一个页目录项,|7表示将最后三位只为111,分别表示r/w,user,p,即只读
*to_dir = ((unsigned long) to_page_table) | 7;
计算需要拷贝的页表项的数目,如果是父进程是0号进程,则只拷贝前160项,负责拷贝所有的页表项(总共1024项)
nr = (from==0)?0xA0:1024;
循环拷贝页表项
for ( ; nr-- > 0 ; from_page_table++,to_page_table++)
先将父进程页表项拷贝给一个临时变量,操作之后再赋给相应的子进程页表项
this_page = *from_page_table;
if (!(1 & this_page)) //最后一位为0,该页面未使用
continue;
this_page &= ~2; //设置页表项属性,2是010,~2是101,代表用户,只读,存在
*to_page_table = this_page;
1MB以内的内核区不参与用户分页管理
if (this_page > LOW_MEM) {
*from_page_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++;
}
重置CR3为0,刷新“页变换高速缓存”
invalidate();
Written by Vincent 强强
Related protips
Have a fresh tip? Share with Coderwall community!
Post
Post a tip
Best
#Linux
Authors
Sponsored by #native_company# — Learn More
#native_title#
#native_desc#