Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2159597
  • 博文数量: 438
  • 博客积分: 3871
  • 博客等级: 中校
  • 技术积分: 6075
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-10 00:11
个人简介

邮箱: wangcong02345@163.com

文章分类

全部博文(438)

文章存档

2017年(15)

2016年(119)

2015年(91)

2014年(62)

2013年(56)

2012年(79)

2011年(16)

分类: LINUX

2016-09-19 19:15:20

1. 初始化页目录表与页表
1. boot/head.s中页目录表与页表的初始化
linux0.12的内存只有16M,一个页表能映射4M,所以需要16M/4M=4个页表
  1. 198 setup_paging:
  2. 199      movl $1024*5,%ecx   /* 5 pages - pg_dir+4 page tables */
  3. 200      xorl %eax,%eax
  4. 201      xorl %edi,%edi      /* pg_dir is at 0x000 */
  5. 202      cld; repstosl     -->将[0x0--4K*5]的内存清零,将1个页目录表与4个页表清0
  6. 203      movl $pg0+7,pg_dir     -->设置4个页表在 页目录表中的项: 页目录表项0-->0x00001007
  7. 204      movl $pg1+7,pg_dir+4   -->页目录表项1-->0x00002007
  8. 205      movl $pg2+7,pg_dir+8   -->页目录表项2-->0x00003007
  9. 206      movl $pg3+7,pg_dir+12  -->页目录表项3-->0x00004007

  10. //207-212把4个页目录表与16M的内存一一映射
  11. 207      movl $pg3+4092,%edi   
  12. 208      movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
  13. 209     std
  14. 210 1: stosl /* fill pages backwards - more efficient :-) */
  15. 211      subl $0x1000,%eax
  16. 212      jge 1b
  17. //设置页目录表的基地址cr3=0x0
  18. 213      xorl %eax,%eax /* pg_dir is at 0x0000 */
  19. 214      movl %eax,%cr3 /* cr3 - page directory start */
  20. //打开cr0的PG位,即开启分页机制
  21. 215      movl %cr0,%eax
  22. 216      orl $0x80000000,%eax
  23. 217      movl %eax,%cr0 /* set paging (PG) bit */
  24. 218      ret /* this also flushes prefetch-queue */


上图出自《linu0.12--5.linux0.12的内存管理情景分析》P99
1.2 分段之后的逻辑地址
在boot/head.s中
  1. 179 gdt:
  2. 180 dq 0x0000000000000000
  3. 181 dq 0x00c09a0000000fff
  4. 182 dq 0x00c0920000000fff
  5. 183 dq 0x0000000000000000
  6. 184 times 252 dq 0
可以看出code段与data段都是[0x0-16M]
  1. <bochs:71> info gdt
  2. Global Descriptor Table (base=0x00005cc0, limit=2047):
  3. GDT[0x00]=??? descriptor hi=0x00000000, lo=0x00000000
  4. GDT[0x01]=Code segment, base=0x00000000, limit=0x00ffffff, Execute/Read, Non-Conforming, 32-bit
  5. GDT[0x02]=Data segment, base=0x00000000, limit=0x00ffffff, Read/Write, Accessed
即分段之后的逻辑地址是[0-16M]
所以到现在逻辑地址=线性地址=物理地址
2. 内存管理的初始化
2.1  在init/main.c中
  1. void main(void)  
  2. {
  3.     memory_end = (1<<20) + (EXT_MEM_K<<10);
  4.     memory_end &= 0xfffff000;
  5.     if (memory_end > 16*1024*1024)
  6.         memory_end = 16*1024*1024;
  7.     if (memory_end > 12*1024*1024)
  8.         buffer_memory_end = 4*1024*1024;
  9.     else if (memory_end > 6*1024*1024)
  10.         buffer_memory_end = 2*1024*1024;
  11.     else
  12.         buffer_memory_end = 1*1024*1024;
  13.     main_memory_start = buffer_memory_end;    //高速缓冲区[内核代码末端---4M]
  14.     main_memory_start += rd_init(main_memory_start, RAMDISK*1024);  //ramdisk=[4M-4.5M]
  15.     mem_init(main_memory_start,memory_end);        //将[4.5M-16M]这些空间进行内存管理
  16. }
main执行后内存空间分布如下图所示:

上图出自《Linux内核完全注释V3.0.pdfP99

2.2 mem_init --> 在mm/memory.c中
  1. 32 #define PAGING_MEMORY (15*1024*1024)
  2. 33 #define PAGING_PAGES (PAGING_MEMORY>>12)
  3. 34 #define MAP_NR(addr) (((addr)-LOW_MEM)>>12)

  4. 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)
  5. 444 {
  6. 445     int i;
  7. 446
  8. 447     HIGH_MEMORY = end_mem;     //start_mem=4.5M, end_mem=16M
  9. 448     for (i=0 ; i<PAGING_PAGES ; i++)    //PAGING_PAGES=3840, 1M间=3840*4k=15M
  10. 449        mem_map[i] = USED;
  11. 450     i = MAP_NR(start_mem);           //i=896-->896*4096=3.5M,扣除1M的start_mem
  12. 451     end_mem -= start_mem;            //16M-4.5M=11.5M
  13. 452     end_mem >>= 12;                  //end_mem=2944-->2944*4096=11.5M
  14. 453     while (end_mem-->0)
  15. 454       mem_map[i++]=0;                //从896-(896+2944)这2944清0,3.5M-(3.5+11.5M)清0
  16. 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中
  1. 168 /*
  2. 169 * Get physical address of first (actually last :-) free page, and mark it
  3. 170 * used. If no free pages left, return 0.
  4. 171 */
  5. 172 unsigned long get_free_page(void)
  6. 173 {
  7. 174 register unsigned long __res;
  8. 175
  9. 176 repeat:
  10. //从尾到头扫描全局数组mem_map中的0,若扫到0,则结束
  11. 177 __asm__("std ; repne ; scasb\n\t"     -->std方向由高到低 扫描edi指向的字符串,若没有0,则继续,有0则结束
  12. 178     "jne 1f\n\t"                      -->注意:repne中ecx自动减1,刚开始一直以为ecx是个常数不会变呢。
  13. //把刚才扫到mem_map中为0的一项置1,说明这页内存己被占用
  14. 179     "movb $1,1(%%edi)\n\t"           -->在最后一次执行scasb时edi多减了一次1,有点类似for循环
  15. //ecx就是存的从尾到头数第1个为0的下标,也就是扣除1M后的以页为单位的相对地址,所以将ecx*4K+1M就是实际地址
  16. 180     "sall $12,%%ecx\n\t"             -->ecx*4K
  17. 181     "addl %2,%%ecx\n\t"              -->ecx*4k+1M 
  18. 182     "movl %%ecx,%%edx\n\t"           -->将实际地址存到edx中
  19. //把找到的1页地址清0
  20. 183     "movl $1024,%%ecx\n\t"
  21. 184     "leal 4092(%%edx),%%edi\n\t"
  22. 185     "rep ; stosl\n\t"
  23. //将edx放到eax中作为返回值
  24. 186     "movl %%edx,%%eax\n"
  25. 187 "1:"
  26. 188     :"=a" (__res)
  27. 189     :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),    -->"0"(0)就是将第0个寄存器也就是eax赋值为0
  28. 190     "D" (mem_map+PAGING_PAGES-1)                  -->mem_map+PAGING_PAGES-1放在edi中
  29. 191     :"dx");
  30. 192    if (__res >= HIGH_MEMORY)
  31. 193       goto repeat;
  32. 194    if (!__res && swap_out())
  33. 195       goto repeat;
  34. 196    return __res;
  35. 197 }
cld,清方向标志,即(DF)=0,地址从低到高
std,置方向标志1,DF=1,地址从高到低

上图出自《Linux内核完全注释V3.0.pdf》P658
3.2 将物理页面映射到虚拟地址上
  1. 168 /*
  2. 169 * This function puts a page in memory at the wanted address.
  3. 170 * It returns the physical address of the page gotten, 0 if
  4. 171 * out of memory (either when trying to access page-table or
  5. 172 * page.)
  6. 173 */
  7. 174 static unsigned long put_page(unsigned long page,unsigned long address)
  8. 175 {
  9. 176     unsigned long tmp, *page_table;
  10. 177
  11. 178     /* NOTE !!! This uses the fact that _pg_dir=0 */
  12. 179      //检查参数,物理地址page要在[1M-16M]范围内,判断page是否正在使用
  13. 180      if (page < LOW_MEM || page >= HIGH_MEMORY)          //page是物理地址,page>1M && page<16M
  14. 181          printk("Trying to put page %p at %p\n",page,address);
  15. 182      if (mem_map[(page-LOW_MEM)>>12] != 1)               //正常情况下这个page在mem_map中是0,即当前未被使
  16. 183           printk("mem_map disagrees with %p at %p\n",page,address);
  17.          //根据线性地址找到页目录表项的地址,正确的表示是:cr3+page_table,但因cr3=0,所以这儿只用page_table表示即可
  18. 184      page_table = (unsigned long *) ((address>>20) & 0xffc);      -->这儿的真正意图是:(address>>22)*4
  19.          //如果页表不存在则分配一页页表,同时将页目录表项映射到页表
  20. 185      if ((*page_table)&1)                                         -->如果页目录表项的P位==1,说明己存
  21. 186           page_table = (unsigned long *) (0xfffff000 & *page_table); -->找到页目录表项对应的页表
  22. 187      else {
  23. 188           if (!(tmp=get_free_page()))                             -->页目录表项所对应的页表不存在,则分配一页内存作为页表
  24. 189               return 0;
  25. 190           *page_table = tmp | 7;                                  -->将页目录表项映射到页表上
  26. 191           page_table = (unsigned long *) tmp;                     -->page_table指向页表
  27. 192      }
  28.         //设置页表项,设置线性地址address对应的页表项,使这个页表项指向物理地址为page,这样page与address对应起来了。
  29. 193      page_table[(address>>12) & 0x3ff] = page | 7;
  30. 194     /* no need for invalidate */
  31. 195      return page;
  32. 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
  1.  49 /*
  2.  50 * Free a page of memory at physical address 'addr'. Used by
  3.  51 * 'free_page_tables()'
  4.  52 */
  5.  53 void free_page(unsigned long addr)
  6.  54 {
  7. //检查参数addr的边界,要在[1M-16M]范围内
  8.  55     if (addr < LOW_MEM) return;
  9.  56     if (addr >= HIGH_MEMORY)
  10.  57         panic("trying to free nonexistent page");
  11. //获取addr在mem_map中的下标,(在mem_map中的下标是物理地址扣除1M内存之后以页为单位的数值)
  12.  58     addr -= LOW_MEM;
  13.  59     addr >>= 12;
  14. //若将物理地址使用数不为0,则减1返回。 否则出错
  15.  60     if (mem_map[addr]--)   return;
  16.  61     mem_map[addr]=0;
  17.  62     panic("trying to free free page");
  18.  63 }


  1.  65 /*
  2.  66 * This function frees a continuos block of page tables, as needed
  3.  67 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
  4.  68 */
  5.  69 int free_page_tables(unsigned long from,unsigned long size)
  6.  70 {
  7.  71     unsigned long *pg_table;
  8.  72     unsigned long * dir, nr;
  9.  73
  10. //检查参数from是否在4M边界处,同时from不能为NULL
  11.  74     if (from & 0x3fffff)
  12.  75        panic("free_page_tables called with wrong alignment");
  13.  76     if (!from)
  14.  77        panic("Trying to free up swapper memory space");
  15. //1个页目录表可以映射4M内存,size长度所占的页目录表项数=size/4M
  16.  78     size = (size + 0x3fffff) >> 22;
  17. //dir是线性地址from所对应的页目录表项的地址,frome>>20=(from>>22)*4
  18.  79     dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
  19.  80     for ( ; size-->0 ; dir++) {
  20.  81        if (!(1 & *dir))          //*dir的P位==0,说是这个页目录表没有使用,也就不用释放
  21.  82          continue;
  22.  83        pg_table = (unsigned long *) (0xfffff000 & *dir);  //由页目录表的内容找到页表的首地址
  23. //清一个页表及其所指向的内存:1024*4096=4M,先释放页表所指向的1024*4K的内存,然后释放页表本身所占的内存
  24.  84        for (nr=0 ; nr<1024 ; nr++) {                   //每个页表1024项
  25.  85            if (*pg_table) {                            //若页表项的内容不为0,
  26.  86               if (1 & *pg_table)                       //且P==1,说明页表项所指的内存己被使用
  27.  87                  free_page(0xfffff000 & *pg_table);    //所以将页表所指向的内存释放掉    
  28.  88               else
  29.  89                swap_free(*pg_table >> 1);
  30.  90            *pg_table = 0;                             //并将页表项的内容清0
  31.  91            }
  32.  92            pg_table++;                                //++是指向下一个页表项
  33.  93        }
  34.  94        free_page(0xfffff000 & *dir);                  //将页表本身的所占的内存释放                
  35.  95        *dir = 0;
  36.  96     }
  37.  97     invalidate();
  38.  98     return 0;
  39.  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.打印数组的内容
  1. (gdb) p /x *mem_map@10
  2. $22 = {0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64}
  3. (gdb) p /x *mem_map@1024
  4. $23 = {0x64 <repeats 896 times>, 0x0 <repeats 128 times>}
2.关于mem_map的地址
  1. a. 查看mem_map的地址
  2. cong@msi:/work/os/linux12/linux12$ nm ./tools/system | grep "mem_map"
  3. 0002bac0 B mem_map
  4. 这个mem_map的地址=0x2bac0=178880=174.6875K

  5. b. 用gdb调试验证一下
  6. (gdb) x /1000xb 0x2bac0     -->用gdb查看指定地址的数据
  7. 0x2bac0 : 0x64 0x64 0x64 0x64 0x64 0x64 0x64 0x64
    0x2bac8 : 0x64 0x64 0x64 0x64 0x64 0x64 0x64 0x64

  8. c.综上所述mem_map是在地址0x2bac0=约174K处,就是一个全局变量嘛,并不是说mem_map首地址为1MB,
  9.   而是说mem_map管理内存是从1M开始的。

  10. d. (gdb) help x
  11. Examine memory: x/FMT ADDRESS.
  12. ADDRESS is an expression for the memory address to examine.
  13. FMT is a repeat count followed by a format letter and a size letter.  -->显示格式用两部分表示
  14. Format letters are:                     -->格式类型 
  15.     o(octal),      x(hex),      d(decimal),      u(unsigned decimal)    t(binary), 
  16.     f(float),     a(address),     i(instruction),     c(char),     s(string)    z(hex, zero padded on the left).
  17. Size letters are:                        -->长度类型 
  18.     b(byte),     h(halfword),      w(word),     g(giant, 8 bytes).
  19. 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,存管的结束

阅读(1722) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~