Last Updated: February 25, 2016
·
847
· njucslqq

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();