linux内存管理解析1----linux物理,线性内存布局及页表的初始化

主要议题:linux

1分页,分段模式及实模式ios

2Linux分页 编程

3linux内存线性地址空间布局及物理内存空间布局数组

4linux页表初始化及代码解析数据结构


1.1.1内存寻址和保护模式
在X86平台上,内存控制单元经过分段单元电路把逻辑地址转换为线性地址,又经过分页单元把线性地址转换为物理地址。
 




    一个逻辑地址由段标识符和段内偏移地址组成。段标示符是一个16位长度的字段,称为段选择符,而偏移地址是32位的字段。app


 
     通常用段寄存器来保存段选择符,如CS,DS,ES,SS等,CS段选择符中用RPL来表示CPU当前的特权级别,0表示工做在内核态,3标示工做在用户态。每一个段由一个8个字节的描述符进行管理,段描述符表放在GDT或者LDT中,一般只定义一个GDT,而每一个进程除了GDT中的段外还须要建立附加的段,就能够有本身的LDT段,一般GDT段存放在GDTR控制寄存器中。


每当一个段选择符被加入到段寄存器时,段描述符就被自动加载到非编程寄存器中.
 


实模式因为是由8086/8088发展而来所以他更像是一个运行单片机的简单模式,计算机启动后首先进入的就是实模式,经过8086/8088只有20根 地址线因此它的寻址范围只有2的20次幂,即1M。内存的访问方式就是咱们熟悉的seg:offset逻辑地址方式,例如咱们给出地址逻辑地址它将在 cpu内转换为20的物理地址,即将seg左移4位再加上offset值。例如地址1000h:5678h,则物理地址为 10000h+5678h=15678h。实模式在后续的cpu中被保留了下来,但实模式的局限性是很明显的,因为使用seg:offset逻辑地址只能 访问1M多一点的内存空间,在拥有32根地址线的cpu中访问1M以上的空间则变得很困难。并且随着计算机的不断发展实模式的工做方式愈来愈不能知足计算机对资源(存储资源和cpu资源等等)的管理,由此产生了新的管理方式——保护模式。
存储方式主要体如今内存访问方式上,因为兼容和IA32框架的限制,保护模式在内存访问上延用了实模式下的seg:offset的形式(即:逻辑地址), 其实seg:offset的形式在保护模式下只是一个躯壳,内部的存储方式与实模式大相径庭。在保护模式下逻辑地址并非直接转换为物理地址,而是将逻辑 地址首先转换为线性地址,再将线性地址转换为物理地址。


1.1.2linux分段:
运行在用户态的全部linux进程都使用同一对相同的段对指令和数据寻址,这两个段就是所谓的用户代码段和用户数据段,相似的,运行在内核态的全部linux进程都使用一对相同的段进行指令和数据的寻址:分别叫作内核代码段和内核数据段。从下图中能够看出linux下逻辑地址和线性地址实际上是一致的。框架


 
  每一个处理器都有一个gdtr的寄存器,全部的gdt都存放在cpu_gdt_table数组里面,而全部GDT的地址和他们的大小都被存放在cpu_gdt_descr数组中。


1.1.3linux分页:
  在cpu中经过cr3寄存器来切换对应的页表。
  下面是线性地址和页表之间的关系,反应了如何从一个线性地址找到一个物理页面,并定位到相关字节。这个表反应的是32位86x86的映射机制:函数


 
   对于64位cpu的页表管理,通常使用三级或者四级页表,X86_64使用的是四级页表,几级页表主要是根据CPU硬件规格来制定的。
   在linux内核中,统一使用四级页表的数据结构来描述cpu的页表结构,以达到代码的统一。请注意,这里仅仅是用了四级页表来进行描述cpu的页表结构,不表明硬件上就是四级页表,这里是逻辑上的四级。好比,32位的X86是两级页表,它要用四级页表来表示的话,页上级和页中间目录的位数就是为0,在实际的代码中对应的页上级目录和页中间目录都只有一项,其地址和其所属的页全局目录的项是同样的.......oop


 
1.1.4linux物理内存布局布局


 
其中,不可用页框(页框0)主要是用来存放bios加电自检期间检测到的硬件配置,0x9f~0x100页框即(640K~1M)留给bios例程,用来映射ISA图形卡上的部份内存,_text表示地址0x100000,即1M用来存放内核的代码段,_etext和_edata之间存放的是内核的已初始化的数据,_edata到_eend之间存放的是内核未初始化的数据,从_end到第768个页框会之间映射到对应的内核空间使用,至于768个页框之后的物理页框,要在内核中直接使用的话,必须进行高端内存映射,或使用vmalloc()将他们映射到内核空间的3G+896~~4G的内核线性地址空间。这部分能够配合1.1.4中的linux虚拟内存布局来看。


1.1.5linux虚拟内存布局
 


内核经过内核页全局目录来管理全部的物理内存,因为线形地址前3G空间为用户使用,内核页全局目录前768项(恰好3G)除0、1两项外所有为0,后256项(1G)属于linux内核的地址空间,用来管理全部的物理内存。内核页全局目录在编译时静态地定义为swapper_pg_dir数组,该数组从物理内存地址0x101000处开始存放。


由图可见:
(1) 内核线形地址空间部分从PAGE_OFFSET(一般定义为3G)开始,为了将内核装入内存,从PAGE_OFFSET开始8M线形地址用来映射内核所在的物理内存地址;(此处映射的物理地址是否包含了物理存储布局中的内存中最开始的1M?)
(2)接下来是mem_map数组,mem_map的起始线形地址与体系结构相关,好比对于UMA结构,因为从PAGE_SIZE开始16M线形地址空间对应的16M物理地址空间是DMA区,mem_map数组一般开始于PAGE_SIZE+16M的线形地址;
(3)从PAGE_SIZE开始到VMALLOC_START – VMALLOC_OFFSET的线形地址空间直接映射到物理内存空间(一一对应映射,物理地址=线形地址-PAGE_OFFSET),这段区域的大小和机器实际拥有的物理内存大小有关,这儿VMALLOC_OFFSET在x86上为8M,主要用来防止越界错误;(这一段其实就是对DMA_ZONE和DMA_NORMAL区的物理内存进行直接映射)
(4)在内存比较小的系统上,余下的线形地址空间(还要再减去空白区即VMALLOC_OFFSET)被vmalloc()函数用来把不连续的物理地址空间映射到连续的线形地址空间上,在内存比较大的系统上,vmalloc()使用从VMALLOC_START到VMALLOC_END(也即PKMAP_BASE减去2页的空白页大小PAGE_SIZE)的线形地址空间
(5)此时余下的线形地址空间(还要再减去2页的空白区即VMALLOC_OFFSET)又能够分红2部分:
第一部分从PKMAP_BASE到FIXADDR_START用来由kmap()函数映射高端内存;
第二部分,从FIXADDR_START到FIXADDR_TOP,这是一个固定大小的线形地址空间,(引用:Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time such as the APIC),在x86体系结构上,FIXADDR_TOP被静态定义为0xFFFFE000,此时这个固定大小空间结束于整个线形地址空间最后4K前面,该固定大小空间大小是在编译时计算出来并存储在__FIXADDR_SIZE变量中。


正是因为vmalloc()使用区、kmap()使用区及固定大小区的存在才使ZONE_NORMAL区大小受到限制,因为内核在运行时须要这些函数,所以在线形地址空间中至少要VMALLOC_RESERVE大小的空间。VMALLOC_RESERVE的大小与体系结构相关,在x86上,VMALLOC_RESERVE定义为128M,这就是为何咱们看到ZONE_NORMAL大小一般是16M到896M的缘由。

1.1.6内核页表的初始化过程 主要分为两个阶段:     1第一个阶段,内核须要建立一个有限的地址空间,用来存放内核的代码段,数据段,初始页表,和一些动态数据,这个最小限度地址空间的目的是仅仅能将内核加载进去以及让内核作一些初始化的操做。通常能够认为这个最小限度地址空间大小为8MB。临时业全局目录在swap_pg_dir数组中,临时页表在pg0中存放。 当处于第一个阶段时,cpu尚处于实模式的寻址模式,第一个阶段的目标是让实模式和保护模式下都能对着8MB的内存进行寻址。为此,须要把0x00000000~0x007fffff和0x0c000000~0xc7fffff的线性地址空间映射到0~0x7fffff的物理地址空间。在内核中,用swap_pg_dir来存放临时页全局目录,能够将全部的页全局目录表项清0,而后把0,1,768,769这四项来进行设置,来达到咱们的目的。(将0x00000000~0x007fffff线性地址也须要对应的在页表里面进行设置,应该是为了兼容当前运行实模式的代码,这样在开启了页寻址模式后,经过分段+分页寻址,寻到的物理地址会还是运行在实模式时操做的物理地址) 临时页表由startup_32()来进行初始化,临时的页全局目录是在编译时初始化的。在startup_32()中创建临时页表:     //页表初始化 page_pde_offset = (__PAGE_OFFSET >> 20); movl $pa(__brk_base), %edi //第一张页表的物理地址 movl $pa(swapper_pg_dir), %edx //页目录的物理地址 movl $PTE_IDENT_ATTR, %eax //页目录中项的标识位 10: leal PDE_IDENT_ATTR(%edi),%ecx //PDE_IDENT_ATTR实际上是0x007,这里是为了算出页全//局目录目录项里应该被放入什么值 movl %ecx,(%edx) //存入对应的页全局目录项里面0,1 movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry *///存入对应的页全局目录项、、里面768,769 addl $4,%edx //下一个页表项的地址 movl $1024, %ecx //每一个页表有1024项须要初始化 11: stosl //存到页表里,edi指向的地方 loop 11b //这个循环对每张页表都会循环1024次, edi会自增。 movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp cmpl %ebp,%eax jb 10b     创建完页表,启用保护模式: movl $swapper_pg_dir-__PAGE_OFFSET,%eax movl %eax,%cr3/* set the page table pointer.. */ movl %cr0,%eax orl $0x80000000,%eax movl %eax,%cr0/* ..and set paging (PG) bit */ ljmp $__BOOT_CS,$1f/* Clear prefetch and normalize %eip */ 2第二个阶段,内核充分利用物理内存并适当的创建页表。 内核在启动后须要对内核页表进行初始化(即对应上面的第二阶段),对应代码主要在函数kernel_physical_mappin g_init()中。如下是32位x86内核对于页表进行的初始化代码。 static void __init kernel_physical_mapping_init(pgd_t *pgd_base) { unsigned long pfn; pgd_t *pgd; pmd_t *pmd; pte_t *pte; int pgd_idx, pmd_idx, pte_ofs; //计算linux内核态空间起始地址(3G) 在页全局表中的索引 pgd_idx = pgd_index(PAGE_OFFSET); pgd = pgd_base + pgd_idx; pfn = 0; //每一个pgd对应有1024个表项,每一个表项指向一个页表 for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) { //在二级页表的情形中,pmd和pgd的值是相等的 pmd = one_md_table_init(pgd); if (pfn >= max_low_pfn) continue; //在二级页表中,该PTRS_PER_PMD值为1 for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) { unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET; /* Map with big pages if possible, otherwise create normal page tables. */ if (cpu_has_pse) { unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1; if (is_kernel_text(address) || is_kernel_text(address2)) set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC)); else set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE)); pfn += PTRS_PER_PTE; } else { //该pmd指向该page table pte = one_page_table_init(pmd); //每一个页表有1024个页表项,指向1024个物理页 for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) { //地址为kernel代码区,设置对应页表项,填入 //对应的物理页的地址 if (is_kernel_text(address)) set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC)); else set_pte(pte, pfn_pte(pfn, PAGE_KERNEL)); } } } } } static pte_t * __init one_page_table_init(pmd_t *pmd) { if (pmd_none(*pmd)) { //分配页表 pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE); //设置页表地址到对应的目录项中 set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE)); if (page_table != pte_offset_kernel(pmd, 0)) BUG(); return page_table; } return pte_offset_kernel(pmd, 0); }

相关文章
相关标签/搜索