author:kf701 mail:kf_701@21cn.com hefei of china 5/2005 写作中...
最近一段时间在阅读Linux的源代码,想把看到的东西写出来,觉得内存这一部分最简单,就先写了出来。请指正!
***于17/5/2005***
内存最低4K的地址是一张页目录(page_dir),页目录共1024项,每项4字节。目录项的结构如下:
____________________________________
|32-12位为页框地址 | |U|R|p|
| | |S|W| |
|_________________|______ |_|_ |_|
随后的16K,用来做了4张页表,页表项结构和页目录项结构相同。页表的每一项指向一个物理页面,
也就是指向内存中的一个4K大小的空间。有了这4张页表,已能寻址16M的内存了。
下面就是在系统初始化的时候在head.s程式中配置一张页目录和四张页表的代码。此时页目录中
仅前4项有效,正是指向位于其下面的4张页表,而这4张页表寻址了内存的最低16M。
198 setup_paging:
199 movl 24*5,靫 /* 5 pages - pg_dir 4 page tables */
200 xorl 陎,陎
201 xorl 韎,韎 /* pg_dir is at 0x000 */
202 cld;rep;stosl
203 movl $pg0 7,_pg_dir /* set present bit/user r/w */
204 movl $pg1 7,_pg_dir 4 /* --------- " " --------- */
205 movl $pg2 7,_pg_dir 8 /* --------- " " --------- */
206 movl $pg3 7,_pg_dir 12 /* --------- " " --------- */
207 movl $pg3 4092,韎
208 movl xfff007,陎 /* 16Mb - 4096 7 (r/w user,p) */
209 std
210 1: stosl /* fill pages backwards - more efficient :-) */
211 subl x1000,陎
212 jge 1b
以后每次有fork新进程,都要为新进程分配内存。但具体是怎么做的呢,我也想知道,一起看吧。
当执行fork时,他使用int0x80调用sys_fork函数,sys_fork的代码位于system_call.s中,很短如下:
208 _sys_fork:
209 call _find_empty_process
210 testl 陎,陎
211 js 1f
212 push %gs
213 pushl %esi
214 pushl 韎
215 pushl 雙
216 pushl 陎
217 call _copy_process
218 addl ,%esp
219 1: ret
看到其中调用了两个函数,find_empty_process and copy_process,这两个函数在fork.c文档里实现的。
find_empty_process是为将要创建的新进程找一个pid,保存在last_pid里,然后调用copy_process,这
是sys_fork真正的主程式,其中有如此句:
77 p = (struct task_struct *) get_free_page();
先为新进程分配一张物理页面,用来存放进程的PCB结构,即task_struct结构。光给新进程一张物
理页面来存放他的task_struct,显然是不能满足他的。
我们知道,在创建之初,新进程是和其父进程共享代码和数据的。这是人为定的,但是这样的好处
不言而喻。因此在创建的时候就没有必要将其代码和数据全部copy到新内存地址里,而只为新进程创建
页目录项和页表就能够了。代码如下:
115 if (copy_mem(nr,p)) { /*copy_mem调用memory.c里的copy_page_tables*/
116 task[nr] = NULL;
117 free_page((long) p);
118 return -EAGAIN;
119 }
copy_mem为新进程分配页表空间,并把父进程的页表内容copy到新进程的页表空间里,这样新进
程的页表的每一项指向的物理页面和其父进程页表的相应每一项指向的物理页面是相同的。少说了一些,
不能只copy页表就完事了。
32位线性地址转换为物理地址的时候,最先要找到32位线性地址对应的页目录项,再用页目录项找
到页表地址。新进程有了自己的页表,并且页表也都指向了物理地址,现在少的就是页目录项了。
新进程在创建的时候,在4G线性空间里给其分配了64M的线性空间,是通过配置LDT来完成的:
130 set_ldt_desc(gdt (nr<<1) FIRST_LDT_ENTRY,&(p->ldt));
这64M的线性地址是从nr*64M的地址处开始的,这个地址正好能够被映射到页目录里的一项,这项的地
址是:((nr*64M)>>20)&0xffc。只要从这里开始,在页目录里建一些页目录项,指向新创建的进程的页
表地址(copy_mem调用copy_page_tables()来做的)。
到这里,copy_mem的工作能够说是完成了,但是一定不能少了这一句:
177 this_page &= ~2; (memory.c)
由于新进程和其父进程共享物理内存页面,因此把这些物理页面重新都设成只读是必要的。上面这句是
放在copy_page_tables函数里面的循环中的。copy_mem主要是靠调用这个程式来完成工作的。
分析到这里,我终于能够小舒一口气了。不如回顾一下:
系统初始化的时候在内存起始处建一张页目录(page_dir),以后任何的进程都使用这张页目录。并为系
统建了4张页表。以后每有新进程产生,便为之分配空间存放PCB(即struct task_struct),然后为之通
过复制父进程的页表来创建自己的页表,并创建相应的页目录项。
***于18/5/2005中午***
程式运行了,问题又来了。终于读到了“写时复制”和请求调页的部分。当程式访问的线性地址没
有被映射到一个物理页面,或欲写操作的线性地址映射的物理页面仅是只读,都会产生一个页异常,然
后就会转去页异常中断处理程式(int 14)执行,页异常中断处理程式(page.s)如下:
14 _page_fault:
15 xchgl 陎,(%esp)
16 pushl 靫
17 pushl 韝
18 push %ds
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




