linux的物理内存

linux系统对于物理内存管理主要有下面3次探测的过程: 

1:第一次探测 Arch/i386/boot/setup.S

在这里使用了三种方法,通过调用0x15 中断:
E820H 得到memory map。
E801H 得到大小。
88H 0-64M.

 

2:第二次探测:
start_kernel() -> setup_arch()
这里主要是对物理内存的初始化,建立zone区。并且初始化page。
start_kernel() -> setup_arch() -> setup_memory_region()
把bios 里的memory map 拷贝到 e820 这个全局变量里。会在屏幕上显示
BIOS-provided physical RAM map:
BIOS-e820: 0000000000000000 – 000000000009FC00 (usable)
BIOS-e820: 0000000000100000-0000000002000000 (usable)

上面参数的含义:BIOS-e820 说的是通过e820 来读取成功的数据,第一个代表起始地址,后面接着的是大小。usable 说明内存可用。还有 reserved, ACPI data, ACPI NVS 等。第二条说明起始地址为0x100000 = 1M,大小是0x2000000 = 32M 内存。
start_kernel() -> setup_arch() -> setup_memory ()


start_kernel() -> setup_arch() -> setup_memory ()->find_max_pfn()
通过e820计算出最大可用页面。
e820.map[1].addr = 0x100000 e820.map[1].size = 0x1f00000,那么start =PFN_UP(0x100000) = (0x100000+0x1000(4096 = 1>>12)-1)>>12 =0x100FFF>>12=0x100, end = PFN_DOWN(0x2000000) = 0x2000000 >> 12 =0x2000 = 8192,也就是max_pfn = 0x2000 就是最大的页面。


start_kernel() -> setup_arch() -> setup_memory ()->find_max_low_pfn(),主要是高端内存的限制。max_low_pfn = max_pfn 不能大于896M

PFN_DOWN((-0xC0000000 –0x8000000(128<<20)) = (0x38000000 = 896M限制)) = 0x38000


start_kernel() -> setup_arch() -> setup_memory ()-> init_bootmem()
引导内存分配只使用在引导过程中,为内核提供保留分配页,并且建立内存的位图。比如_end 为0xc02e5f18,start_pfn = PFN_UP(__pa(&end)) = 0x2e6,所以
start_pfn 指的是内核结束的下一个页面((_pa(&_end) >> 12 =0x2e5 ,那么
init_bootmen(start_pfn,max_low_pfn) = init_bootmen(0x2e6,0x2000)
start_kernel() -> setup_arch() -> setup_memory ()->init_bootmem()->init_bootmem_core()


init_bootmen(0x2e6,0x2000) = init_bootmem_core(&contig_page_data,0x2e6,0,0x2000),mapsize就是建立内存位图大小需要多少字节 1024=(8192-0+7)/8,1个位代表着4K的一个页面。
bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT) =
phys_to_virt(0x2e6<<12) = phys_to_virt(0x2e6000) = 0xc02e6000
bdata->node_boot_start = (start << PAGE_SHIFT) = 0
bdata->node_low_pfn = end = 8192
把0xc02e6000 开始0xff 填充1024 个字节 0xc02e6400,这样就代表着所有内
存不可用。
start_kernel() -> setup_arch() -> setup_memory ()-> register_bootmem_low_pages
e()
根据e820和内存位图来标识位图那些内存可用。
start_kernel() -> setup_arch() -> setup_memory ()-> register_bootmem_low_pages
e()->free_bootmem()->free_bootmem_core()
第一次调用 free_bootmem(PFN_PHYS(curr_pfn), PFN_PHYS(size)) =
free_bootmem(PFN_PHYS( 0), PFN_PHYS(159))= free_bootmem(0, 0x9F000))
free_bootmem_core(contig_page_data.bdata, 0, 0x9f000)
eidx代表这块内存共占用多少页面
eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE = (0+0x9f000-0)/0x1000=0x9f
end = (addr + size)/PAGE_SIZE=(0+0x9f000)/0x1000=0x9f
start = (addr + PAGE_SIZE-1) / PAGE_SIZE = (0+0x1000-1)/0x1000=0
sidx = start - (bdata->node_boot_start/PAGE_SIZE)=0-(0/0x1000)=0
每一次循环清1 位,第一次循环0x9f次清到0x9f /8= 0x13,所以第一次清到
了0xc02e6013的第7位159%8 = 7,所以0xc02e6013的数据应该是0x10000000,
第二次free_bootmem_core(contig_page_data.bdata, 0x100000, 0x 1F00000)
eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE = 0x2000
end = (addr + size)/PAGE_SIZE = 0x2000
start = (addr + PAGE_SIZE-1) / PAGE_SIZE = 0x100
sidx = start - (bdata->node_boot_start/PAGE_SIZE) = 0x100
这次从0xc02e6020 ( 0x100/8 ) 开始清0x2000-0x100 次, 清到
0xc02e6400(0xc02e6020+0x1f00/8)
通过free_bootmem的操作,我们已经把内存的位图标识出来。
start_kernel() -> setup_arch() -> setup_memory ()->reserve_bootmem()
保留内存,说明这部分不能用于动态分配。
reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +
bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY)) =
reserve_bootmem(0x100000, (PFN_PHYS(2e6) +1024 + 4096-1) - (0x100000))=
reserve_bootmem(0x100000, 1E73FF)
保留内核开始1M 到建立的内存位图的结束地方,因为位图是接着内核的下
一个页面存放的,所以一起保留,对于+ PAGE_SIZE-1 操作就是位图的结束
位置也与内存边界对齐。重新改写内存位图之后对于我们的情况是
0xc02e6020(1M)到0xc02e605C 都改成了1(保留)0xc02e605D还是0
reserve_bootmem(0,PAGE_SIZE)= reserve_bootmem(0,4096)
保留物理内存的第0 页内存。0x0-0x4096
start_kernel() -> setup_arch() -> paging_init()
当我们上面的物理内存都完成时,我们就要对所有内存进行管理,需要重新
建立页面映射。
start_kernel() -> setup_arch() -> paging_init()->pagetable_init()
我们根据已有的内存信息,重新修改页面表
end = (unsigned long)__va(max_low_pfn*PAGE_SIZE) = 0xC2000000
pgd_base = swapper_pg_dir = 0xC0101000
i = __pgd_offset(PAGE_OFFSET) = (0xC0000000>>22)&(1024-1)=768
pgd = pgd_base + i= 0xC0101C00
pgd就是内核模式的第一个页目录
第一个for循环就是说从768开始到1024结束,也就是swapper_pg_dir结束,
这部分都是内核的页目录,也就是我们要修改的页目录。
第二个for循环用于中间页表,对我们2 层i386 体系无效。
alloc_bootmem_low_pages(PAGE_SIZE) = __alloc_bootmem(0x1000,0x1000,0)
->__alloc_bootmem_core(pgdat->bdata, 0x1000,0x1000,0)
eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT) =
8192-(0>>12) = 8192 代表着这个区域可用的页面,实际上就是整个内存。
我们以前保留过第0 页面的内存,所以我们分配内存就是从第一页面开始,
重新写内存位图将已分配的页面做保留标记。这次运行后我们分配了一个页
面(0x4096),开始地址为0xc0001000,与此同时,0xc02e6000 = 0x3 = 0011。
第三个for循环用于写页表,写满这个我们新分配的页表。
第一次循环的时候pte = 0xc0001000;vaddr = 0xc0000000;
*pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL) =
mk_pte_phys(0, MAKE_GLOBAL(_PAGE_PRESENT | _PAGE_RW |
_PAGE_DIRTY | _PAGE_ACCESSED) =
mk_pte_phys(0, MAKE_GLOBAL(0x001 | 0x002 | 0x040 | 0x020) =
mk_pte_phys(0, ( MAKE_GLOBAL(0x001 | 0x002 | 0x040 | 0x020) = 0x63)) =
mk_pte_phys(0, pgprot_t __ret = __pgprot(0x63)) = mk_pte_phys(0, __ret) =
__mk_pte((0) >> 12, __ret) = __pte(((0)<<12)| pgprot_val(__ret)) = __pte(0x63)
= 0x00000063
第二次运行时 vaddr = 0xc0001000;
*pte = 0x00001063
当pmd = 0xc0101c00 时
set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte_base))) =
(pmd,__pmd((_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED |
_PAGE_DIRTY) + 0x00001000) = (pmd,__pmd(0x001 | 0x002 | 0x020 | 0x040
+0x00001000) = set_pmd(pmd,__pmd( 0x00001063)
这就是结果0xc0101c00=0x00001063下一次pmd是0xc0101c04=0x00002063
对于我们现在的状态,我们是32M 内存,end = 0xC2000000,所以只映射了8
项,0xC0101C1C=0x00008063
现在要建立专用区的页表了,专用区是位于虚拟地址为0xfffff000 往回的一
个区域。Enum fixed_addresses 描述了专用区的功能
vaddr=__fix_to_virt(__end_of_fixed_addresses-1)&PMD_MASK= 0xFFC00000
这就是专用区起始地址所在的中级页目录边界地址
fixrange_init(vaddr, 0, pgd_base) = fixrange_init(0xffc00000, 0, 0xc0101000)
我们要在里面建立专用区的页面。
0xffc00000 处于页目录的1023 映射的位置(最后一项),所以又申请了一个
页面0xc0009000,(没有作为页表使用)。因为这个页面是接着我们以前的目
录页面申请的,所以这个页面与目录页面在物理上是连续的,但在虚拟地址
却差很大。0xC0101FFC = 0x00009067,这就是目录映射了。
当我们运行完时我们就把所有的页表及页目录都建立好,通过load_cr3 与
__flush_tlb_all重新刷新分页机制。
start_kernel() -> setup_arch() -> paging_init()->zone_sizes_init()
根据内存位图开始建立分区。
max_dma = virt_to_phys((char*) MAX_DMA_ADDRESS) >> PAGE_SHIFT =
virt_to_phys(0xC1000000) >> 12 = 0x1000
对于我们32M内存来说,zones_size = { 4096, 4096, 0 }
start_kernel() -> setup_arch() -> paging_init()->zone_sizes_init()->free_area_init()
建立分区数据结构:标记所有页面保留,标记所有内存队列空,清除内存位
图。(需要详细)
map_size = (totalpages + 1)*sizeof(struct page) = 0xE000 根据page结构看
8192 个页面需要多大空间。
lmem_map =alloc_bootmem_node(pgdat, 0xE000) =
__alloc_bootmem_node(pgdat, 0xE000, 0x10, 0x1000000) = 0xc1000000
lmem_map = (struct page *)(PAGE_OFFSET +
MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET))
= 0xC0000000+MAP_ALIGN(0x1000000) = 0xC0000000+
((((0x1000000) % sizeof(mem_map_t)) == 0) ? (0x1000000) : ((0x1000000) +
sizeof(mem_map_t) - ((0x1000000) % sizeof(mem_map_t))))=
0xC0000000+((8==0)?0x1000000: 0x1000038-8) = 0xC1000030
因为cache需要对齐
接下来循环初始化每个区
zone->zone_mem_map = mem_map + offset; 这个区的mem_map 结构位置
zone->zone_start_paddr = zone_start_paddr; 这个区的开始虚拟地址。

for (i = 0; i < size; i++) {
struct page *page = mem_map + offset + i;

}
初始化这个区的所有page 结构。在page 里所有的页面都是保留的,不能投
入使用。

for (i = 0; ; i++) {
unsigned long bitmap_size;
INIT_LIST_HEAD(&zone->free_area[i].free_list);

}

这是伙伴分配系统的初始化
这就说明了每个区有每个区自己的管理系统。

 

3:第三次探测
start_kernel() ->mem_init()
start_kernel() ->mem_init()->free_pages_init()
start_kernel() ->mem_init()->free_pages_init()->free_all_bootmem_core()
struct page *page = pgdat->node_mem_map; 这就是第一个page.
idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT) =8192-0>>12 = 8192
第一个循环就是根据我们以前建立的内存位图来初始化Page。
start_kernel() ->mem_init()->free_pages_init()->free_all_bootmem_core()->__free_page()->__free_pages()->__free_pages_ok()
释放内存页。

第二个循环就是释放内存位图本身的内存。
执行完后以后的内存操作就可以使用Page 了,第三阶段的内存初始化完毕,还
有一部分零散的内存操作会在别的部分讨论,至此,物理内存管理结束。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值