Tour para Linux0.11-copy_page_tables ()

最近 在 阅读 Linux0.11 源码, 除了 感叹 大 神Linus Torvalds超凡 的 系统 驾驭 能力 之外, 对其 C 语言 的 功底 和 风格 也 佩服 得 五 体 投 地.

这 几天 在 看fork (), 于是 分享 一下 对 页 表 拷贝 的 理解 。copy page tables () 函数 的 代码 如下 :

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) , 管理 范围 是 0 MB ~ 4 MB

1 项 (地址 是 4) , 管理 范围 是 4 MB ~ 8 MB

2 项 (地址 是 8) , 管理 范围 是 8 MB ~ 12 MB

所以 我们 可以 根据 线性 地址 计算 出 页 目录 项 地址

from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
to_dir
= (unsigned long *) ((to>>20) & 0xffc);

计算 页 目录 项 的 数目

除以 4 MB, 表明 线性 地址 空间 占用 了 多少 个 4 MB, 一个 4 MB 对应 一个 页 目录 项, , 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 位 是 页 表 地址, da tabela de página获得 父 进程 的 页 表 地址

from_page_table = (unsigned long *) (0xfffff000 & *from_dir);

为 子 进程 申请 一个 空闲 页面, 用来 拷贝 父 进程 的 页 表, 如果 申请 成功, para a tabela de página存放 子 进程 的 页 表.

if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */

为 刚刚 申请 的 页 表 设置 一个 页 目录 项, | 7 表示 将 最后 三位 只 为 111, 分别 表示 r / w, usuário, 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;

1 MB 以内 的 内核 区 不 参与 用户 分页 管理

if (this_page > LOW_MEM) {
*from_page_table = this_page;
this_page
-= LOW_MEM;
this_page
>>= 12;
mem_map
[this_page]++;
}

重置 CR3 为 0 , 刷新 “页 变换 高速 缓存”

invalidate();