1. 初始化页目录表与页表
1. boot/head.s中页目录表与页表的初始化
linux0.12的内存只有16M,一个页表能映射4M,所以需要16M/4M=4个页表
-
198 setup_paging:
-
199 movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
-
200 xorl %eax,%eax
-
201 xorl %edi,%edi /* pg_dir is at 0x000 */
-
202 cld; rep; stosl -->将[0x0--4K*5]的内存清零,将1个页目录表与4个页表清0
-
203 movl $pg0+7,pg_dir -->设置4个页表在 页目录表中的项: 页目录表项0-->0x00001007
-
204 movl $pg1+7,pg_dir+4 -->页目录表项1-->0x00002007
-
205 movl $pg2+7,pg_dir+8 -->页目录表项2-->0x00003007
-
206 movl $pg3+7,pg_dir+12 -->页目录表项3-->0x00004007
-
-
//207-212把4个页目录表与16M的内存一一映射
-
207 movl $pg3+4092,%edi
-
208 movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
-
209 std
-
210 1: stosl /* fill pages backwards - more efficient :-) */
-
211 subl $0x1000,%eax
-
212 jge 1b
-
//设置页目录表的基地址cr3=0x0
-
213 xorl %eax,%eax /* pg_dir is at 0x0000 */
-
214 movl %eax,%cr3 /* cr3 - page directory start */
-
//打开cr0的PG位,即开启分页机制
-
215 movl %cr0,%eax
-
216 orl $0x80000000,%eax
-
217 movl %eax,%cr0 /* set paging (PG) bit */
-
218 ret /* this also flushes prefetch-queue */
上图出自《linu0.12--5.linux0.12的内存管理情景分析》P99
1.2 分段之后的逻辑地址
在boot/head.s中
-
179 gdt:
-
180 dq 0x0000000000000000
-
181 dq 0x00c09a0000000fff
-
182 dq 0x00c0920000000fff
-
183 dq 0x0000000000000000
-
184 times 252 dq 0
可以看出code段与data段都是[0x0-16M]
-
<bochs:71> info gdt
-
Global Descriptor Table (base=0x00005cc0, limit=2047):
-
GDT[0x00]=??? descriptor hi=0x00000000, lo=0x00000000
-
GDT[0x01]=Code segment, base=0x00000000, limit=0x00ffffff, Execute/Read, Non-Conforming, 32-bit
-
GDT[0x02]=Data segment, base=0x00000000, limit=0x00ffffff, Read/Write, Accessed
即分段之后的逻辑地址是[0-16M]
所以到现在逻辑地址=线性地址=物理地址
2. 内存管理的初始化
2.1 在init/main.c中
-
void main(void)
-
{
-
memory_end = (1<<20) + (EXT_MEM_K<<10);
-
memory_end &= 0xfffff000;
-
if (memory_end > 16*1024*1024)
-
memory_end = 16*1024*1024;
-
if (memory_end > 12*1024*1024)
-
buffer_memory_end = 4*1024*1024;
-
else if (memory_end > 6*1024*1024)
-
buffer_memory_end = 2*1024*1024;
-
else
-
buffer_memory_end = 1*1024*1024;
-
main_memory_start = buffer_memory_end; //高速缓冲区[内核代码末端---4M]
-
main_memory_start += rd_init(main_memory_start, RAMDISK*1024); //ramdisk=[4M-4.5M]
-
mem_init(main_memory_start,memory_end); //将[4.5M-16M]这些空间进行内存管理
-
}
main执行后内存空间分布如下图所示:
上图出自《Linux内核完全注释V3.0.pdf》P99
2.2 mem_init --> 在mm/memory.c中
-
32 #define PAGING_MEMORY (15*1024*1024)
-
33 #define PAGING_PAGES (PAGING_MEMORY>>12)
-
34 #define MAP_NR(addr) (((addr)-LOW_MEM)>>12)
-
-
47 unsigned char mem_map [ PAGING_PAGES ] = {0,};
//当内存>=16M时,start_mem=4.5M,end_mem=16M
443 void mem_init(long start_mem, long end_mem)
-
444 {
-
445 int i;
-
446
-
447 HIGH_MEMORY = end_mem; //start_mem=4.5M, end_mem=16M
-
448 for (i=0 ; i<PAGING_PAGES ; i++) //PAGING_PAGES=3840, 即只管理1M以上的内存空间=3840*4k=15M
-
449 mem_map[i] = USED;
-
450 i = MAP_NR(start_mem); //i=896-->896*4096=3.5M,即扣除1M的start_mem
-
451 end_mem -= start_mem; //16M-4.5M=11.5M
-
452 end_mem >>= 12; //此时end_mem=2944-->2944*4096=11.5M
-
453 while (end_mem-->0)
-
454 mem_map[i++]=0; //从896-(896+2944)这2944项清0,即从将管理的3.5M-(3.5+11.5M)清0
-
455 } //清3.5(4.5-1)M----15M(16M-1M)的管理数组mem_map清0
[4.5M-16M]之间的页表数=16M-4.5M=11.5*1024*1024/4096=2944个页表
从1M处开始算: 4.5M-1M=3.5M*1024*1024/4096=896开始
上述是将 mem_map[0-896]处设为USED(100) ,将mem_map[896-(896+2944)]处清0
3. 内存的分配
3.1 分配1页内存 在mm/swap.c中
-
168 /*
-
169 * Get physical address of first (actually last :-) free page, and mark it
-
170 * used. If no free pages left, return 0.
-
171 */
-
172 unsigned long get_free_page(void)
-
173 {
-
174 register unsigned long __res;
-
175
-
176 repeat:
-
//从尾到头扫描全局数组mem_map中的0,若扫到0,则结束
-
177 __asm__("std ; repne ; scasb\n\t" -->std方向由高到低 扫描edi指向的字符串,若没有0,则继续,有0则结束
-
178 "jne 1f\n\t" -->注意:repne中ecx自动减1,刚开始一直以为ecx是个常数不会变呢。
-
//把刚才扫到mem_map中为0的一项置1,说明这页内存己被占用
-
179 "movb $1,1(%%edi)\n\t" -->在最后一次执行scasb时edi多减了一次1,有点类似for循环
-
//ecx就是存的从尾到头数第1个为0的下标,也就是扣除1M后的以页为单位的相对地址,所以将ecx*4K+1M就是实际地址
-
180 "sall $12,%%ecx\n\t" -->ecx*4K
-
181 "addl %2,%%ecx\n\t" -->ecx*4k+1M
-
182 "movl %%ecx,%%edx\n\t" -->将实际地址存到edx中
-
//把找到的1页地址清0
-
183 "movl $1024,%%ecx\n\t"
-
184 "leal 4092(%%edx),%%edi\n\t"
-
185 "rep ; stosl\n\t"
-
//将edx放到eax中作为返回值
-
186 "movl %%edx,%%eax\n"
-
187 "1:"
-
188 :"=a" (__res)
-
189 :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES), -->"0"(0)就是将第0个寄存器也就是eax赋值为0
-
190 "D" (mem_map+PAGING_PAGES-1) -->mem_map+PAGING_PAGES-1放在edi中
-
191 :"dx");
-
192 if (__res >= HIGH_MEMORY)
-
193 goto repeat;
-
194 if (!__res && swap_out())
-
195 goto repeat;
-
196 return __res;
-
197 }
cld,清方向标志,即(DF)=0,地址从低到高
std,置方向标志1,DF=1,地址从高到低
上图出自《Linux内核完全注释V3.0.pdf》P658
3.2 将物理页面映射到虚拟地址上
-
168 /*
-
169 * This function puts a page in memory at the wanted address.
-
170 * It returns the physical address of the page gotten, 0 if
-
171 * out of memory (either when trying to access page-table or
-
172 * page.)
-
173 */
-
174 static unsigned long put_page(unsigned long page,unsigned long address)
-
175 {
-
176 unsigned long tmp, *page_table;
-
177
-
178 /* NOTE !!! This uses the fact that _pg_dir=0 */
-
179 //检查参数,物理地址page要在[1M-16M]范围内,判断page是否正在使用
-
180 if (page < LOW_MEM || page >= HIGH_MEMORY) //page是物理地址,page>1M && page<16M
-
181 printk("Trying to put page %p at %p\n",page,address);
-
182 if (mem_map[(page-LOW_MEM)>>12] != 1) //正常情况下这个page在mem_map中是0,即当前未被使用
-
183 printk("mem_map disagrees with %p at %p\n",page,address);
-
//根据线性地址找到页目录表项的地址,正确的表示是:cr3+page_table,但因cr3=0,所以这儿只用page_table表示即可
-
184 page_table = (unsigned long *) ((address>>20) & 0xffc); -->这儿的真正意图是:(address>>22)*4
-
//如果页表不存在则分配一页页表,同时将页目录表项映射到页表
-
185 if ((*page_table)&1) -->如果页目录表项的P位==1,说明己存在
-
186 page_table = (unsigned long *) (0xfffff000 & *page_table); -->找到页目录表项对应的页表
-
187 else {
-
188 if (!(tmp=get_free_page())) -->页目录表项所对应的页表不存在,则分配一页内存作为页表
-
189 return 0;
-
190 *page_table = tmp | 7; -->将页目录表项映射到页表上
-
191 page_table = (unsigned long *) tmp; -->page_table指向页表
-
192 }
-
//设置页表项,设置线性地址address对应的页表项,使这个页表项指向物理地址为page,这样page与address对应起来了。
-
193 page_table[(address>>12) & 0x3ff] = page | 7;
-
194 /* no need for invalidate */
-
195 return page;
-
196 }
a. 对于上面L184,为毛不是address>>22,看了下面的帖子才知道
%5C_page
“右移22位得到的是是页目录的项号。但是每一项占用4个字节,因此需要*4(即再左移2位)才能得到真正的指针。”
b.对于address到page的映射
首先 根据线性地址address>>22找到页目录表项,上面的L184
然后 根据页目录表项的内容找到页表的地址,上面的L186 或 L191
最后 address>>12&0x3FF是页表内的偏移,将这个偏移与page挂钩
c. 为什么address>>12不用*4?
page_table是long, page_table[
address>>12&0x3FF]己经乘以sizeof(long)=4了
4.内存的回收
回收1页内存-->只是将mem_map中管理该物理地址的当前使用次数的数值减1
-
49 /*
-
50 * Free a page of memory at physical address 'addr'. Used by
-
51 * 'free_page_tables()'
-
52 */
-
53 void free_page(unsigned long addr)
-
54 {
-
//检查参数addr的边界,要在[1M-16M]范围内
-
55 if (addr < LOW_MEM) return;
-
56 if (addr >= HIGH_MEMORY)
-
57 panic("trying to free nonexistent page");
-
//获取addr在mem_map中的下标,(在mem_map中的下标是物理地址扣除1M内存之后以页为单位的数值)
-
58 addr -= LOW_MEM;
-
59 addr >>= 12;
-
//若将物理地址使用数不为0,则减1返回。 否则出错
-
60 if (mem_map[addr]--) return;
-
61 mem_map[addr]=0;
-
62 panic("trying to free free page");
-
63 }
-
65 /*
-
66 * This function frees a continuos block of page tables, as needed
-
67 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
-
68 */
-
69 int free_page_tables(unsigned long from,unsigned long size)
-
70 {
-
71 unsigned long *pg_table;
-
72 unsigned long * dir, nr;
-
73
-
//检查参数from是否在4M边界处,同时from不能为NULL
-
74 if (from & 0x3fffff)
-
75 panic("free_page_tables called with wrong alignment");
-
76 if (!from)
-
77 panic("Trying to free up swapper memory space");
-
//1个页目录表可以映射4M内存,size长度所占的页目录表项数=size/4M
-
78 size = (size + 0x3fffff) >> 22;
-
//dir是线性地址from所对应的页目录表项的地址,frome>>20=(from>>22)*4
-
79 dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
-
80 for ( ; size-->0 ; dir++) {
-
81 if (!(1 & *dir)) //*dir的P位==0,说是这个页目录表没有使用,也就不用释放
-
82 continue;
-
83 pg_table = (unsigned long *) (0xfffff000 & *dir); //由页目录表的内容找到页表的首地址
-
//清一个页表及其所指向的内存:1024*4096=4M,先释放页表所指向的1024*4K的内存,然后释放页表本身所占的内存
-
84 for (nr=0 ; nr<1024 ; nr++) { //每个页表1024项
-
85 if (*pg_table) { //若页表项的内容不为0,
-
86 if (1 & *pg_table) //且P==1,说明页表项所指的内存己被使用
-
87 free_page(0xfffff000 & *pg_table); //所以将页表所指向的内存释放掉
-
88 else
-
89 swap_free(*pg_table >> 1);
-
90 *pg_table = 0; //并将页表项的内容清0
-
91 }
-
92 pg_table++; //++是指向下一个页表项
-
93 }
-
94 free_page(0xfffff000 & *dir); //将页表本身的所占的内存释放
-
95 *dir = 0;
-
96 }
-
97 invalidate();
-
98 return 0;
-
99 }
linux0.11对于16M以下内存的处理
1. linux0.11是16M以上不支持,16M以下内存是支持的。
2. 对于不够16M的内存,例如这里设置bochs的内存是12M
用gdb调试结果:
(gdb) p /x main_memory_start
$2 = 0x280000 =2.5M(打开ramdisk时,mm的开始是2.5M)
(gdb) p /x memory_end
$3 = 0xc00000 (结束是12M)
(gdb) p /x mem_map
$4 = {0x64 , 0x0 , 0x64 }
3. 从上面可以看出
2.5M-1M=1.5*1024*1024/4096=384个页面设为USED (从2.5M扣除1M开始管理共384个页面)
12M-2.5M=9.5×1024×1024÷4096=2432个页面设为0 (从12M扣除1M 到 2.5M扣除1M 之间2432个页面)
16M-12M=4×1024×1024÷4096=1024个页面设为USED (从16M扣除1M 到 12M扣除1M 之间 1024个页面)
4. get_free_page是从mem_map中从尾到头找第一个不为0的项,也就是从上面2432个页面的最后1项开始分配。
5.所以说当内存等于12M时,mem_map只对(12M-2.5M)这部分进行了内存管理,对于12M-16M的部分根本就没有用。
附录1. gdb调试
a.打印数组的内容
-
(gdb) p /x *mem_map@10
-
$22 = {0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64}
-
(gdb) p /x *mem_map@1024
-
$23 = {0x64 <repeats 896 times>, 0x0 <repeats 128 times>}
2.关于mem_map的地址
-
a. 查看mem_map的地址
-
cong@msi:/work/os/linux12/linux12$ nm ./tools/system | grep "mem_map"
-
0002bac0 B mem_map
-
这个mem_map的地址=0x2bac0=178880=174.6875K
-
-
b. 用gdb调试验证一下
-
(gdb) x /1000xb 0x2bac0 -->用gdb查看指定地址的数据
-
0x2bac0 : 0x64 0x64 0x64 0x64 0x64 0x64 0x64 0x64
0x2bac8 : 0x64 0x64 0x64 0x64 0x64 0x64 0x64 0x64
-
c.综上所述mem_map是在地址0x2bac0=约174K处,就是一个全局变量嘛,并不是说mem_map首地址为1MB,
-
而是说mem_map管理内存是从1M开始的。
-
-
d. (gdb) help x
-
Examine memory: x/FMT ADDRESS.
-
ADDRESS is an expression for the memory address to examine.
-
FMT is a repeat count followed by a format letter and a size letter. -->显示格式用两部分表示
-
Format letters are: -->格式类型
-
o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary),
-
f(float), a(address), i(instruction), c(char), s(string) z(hex, zero padded on the left).
-
Size letters are: -->长度类型
-
b(byte), h(halfword), w(word), g(giant, 8 bytes).
-
The specified number of objects of the specified size are printed according to the format.
3.关于mem_map的内容
mem_map[0] 就代表内存地址 [1M, 1M+4K]
mem_map[1]
就代表内存地址 [1M+4K, 1M+8K]
... ---> 0-896虽然在mem_map中有内容,但是不会分配
mem_map[896]代表内存地址[4.5M, 4.5M+4K] -->如果内存有16M,这个就是内存管理的开始
....
mem_map[3840-1]
就代表内存地址 [16M-4K, 16M] -->如果内存有16M,这个就是内存管理的结束
阅读(1614) | 评论(0) | 转发(0) |