copy_mem函数
创建中新进程数据段与代码段在线性地址空间中的基地址等于 64MB * 其任务号。
被copy_mem调用的copy_page_tables函数
复制指定线性地址和长度(页表个数)内存对应的页目录项和页表,从而被复制的页目录和
页表对应的原物理内存区被共享使用。
// 复制指定地址和长度的内存对应的页目录项和页表项。需申请页面来存放新页表,原内存区被共享;
// 此后两个进程将共享内存区,直到有一个进程执行写操作时,才分配新的内存页(写时复制机制)。
150 int copy_page_tables(unsigned long from,unsigned long to,long size)
151 {
152 unsigned long * from_page_table;
153 unsigned long * to_page_table;
154 unsigned long this_page;
155 unsigned long * from_dir, * to_dir;
156 unsigned long nr;
157
// 源地址和目的地址都需要是在 4Mb 的内存边界地址上(页表对齐)。否则出错,死机。
158 if ((from&0x3fffff) || (to&0x3fffff))
159 panic("copy_page_tables called with wrong alignment");
// 取得源地址和目的地址的目录项指针(from_dir 和 to_dir)。
160 from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
161 to_dir = (unsigned long *) ((to>>20) & 0xffc);
// 计算要复制的内存块占用的页表数(也即目录项数)。
162 size = ((unsigned) (size+0x3fffff)) >> 22;
// 下面开始对每个占用的页表依次进行复制操作。
163 for( ; size-->0 ; from_dir++,to_dir++) {
// 如果目的目录项指定的页表已经存在(P=1),则出错,死机。
164 if (1 & *to_dir)
165 panic("copy_page_tables: already exist");
// 如果此源目录项不存在(P=0),则不用复制对应页表,跳过。
166 if (!(1 & *from_dir))
167 continue;
// 取当前源目录项中页表的地址Îfrom_page_table。
168 from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
// 为目的页表取一页空闲内存,如果返回是 0 则说明没有申请到空闲内存页面。返回值=-1,退出。
169 if (!(to_page_table = (unsigned long *) get_free_page()))
170 return -1; /* Out of memory, see freeing */
// 设置目的目录项信息。7 是标志信息,表示(Usr, R/W, Present)。
171 *to_dir = ((unsigned long) to_page_table) | 7;
// 针对当前处理的页表,设置需复制的页面数。如果是在内核空间,则仅需复制头 160 页(640KB),
// 否则需要复制一个页表中的所有 1024 页面。
172 nr = (from==0)?0xA0:1024;
// 对于当前页表,开始复制指定数目 nr 个内存页面。
173 for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
174 this_page = *from_page_table; // 取源页表项内容。
175 if (!(1 & this_page)) // 如果当前源页面没有使用,则不用复制。
176 continue;
// 复位页表项中 R/W 标志(置 0)。(如果 U/S 位是 0,则 R/W 就没有作用。如果 U/S 是 1,而 R/W 是 0,
// 那么运行在用户层的代码就只能读页面。如果 U/S 和 R/W 都置位,则就有写的权限。)
177 this_page &= ~2;
178 *to_page_table = this_page; // 将该页表项复制到目的页表中。
// 如果该页表项所指页面的地址在 1MB 以上,则需要设置内存页面映射数组 mem_map[],于是
//计算页面号,并以它为索引在页面映射数组相应项中增加引用次数。而对于位于 1MB 以下的页面,说明
// 是内核页面,因此不需要对 mem_map[]进行设置。因为 mem_map[]仅用于管理主内存区中的页面使用
// 情况。因此,对于内核移动到任务 0 中并且调用 fork()创建任务 1 时(用于运行 init()),由于此时
// 复制的页面还仍然都在内核代码区域,因此以下判断中的语句不会执行。只有当调用 fork()的父进程
// 代码处于主内存区(页面位置大于 1MB)时才会执行。这种情况需要在进程调用了 execve(),装载并
// 执行了新程序代码时才会出现。
179 if (this_page > LOW_MEM) {
// 下面这句的含义是令源页表项所指内存页也为只读。因为现在开始有两个进程共用内存区了。
// 若其中一个内存需要进行写操作,则可以通过页异常的写保护处理,为执行写操作的进程分配
// 一页新的空闲页面,也即进行写时复制的操作。
180 *from_page_table = this_page; // 令源页表项也只读。
181 this_page -= LOW_MEM;
182 this_page >>= 12;
183 mem_map[this_page]++;
184 }
185 }
186 }
187 invalidate(); // 重置CR3为0,刷新页变换高速缓冲。
188 return 0;
189 }
对于内核移动到进程0 中并且调用 fork()创建任务 1 时(用于运行 init()),由于此时被复制的页面还仍然都在内核代码区域,因此以下判断中的语句不会执行。只有当调用 fork()的父进程 代码处于主内存区(页面位置大于 1MB)时才会执行。这种情况需要在进程调用了 execve(),装载并执行了新程序代码时才会出现。