Linux页表机制分析.docx

上传人:b****3 文档编号:4852895 上传时间:2022-12-10 格式:DOCX 页数:30 大小:261.74KB
下载 相关 举报
Linux页表机制分析.docx_第1页
第1页 / 共30页
Linux页表机制分析.docx_第2页
第2页 / 共30页
Linux页表机制分析.docx_第3页
第3页 / 共30页
Linux页表机制分析.docx_第4页
第4页 / 共30页
Linux页表机制分析.docx_第5页
第5页 / 共30页
点击查看更多>>
下载资源
资源描述

Linux页表机制分析.docx

《Linux页表机制分析.docx》由会员分享,可在线阅读,更多相关《Linux页表机制分析.docx(30页珍藏版)》请在冰豆网上搜索。

Linux页表机制分析.docx

Linux页表机制分析

12. 页表机制

12.1. 引言

在Linux系统中,存在以下三种地址:

∙逻辑地址:

它被包含在机器指令中用来指定一个操作数或一条指令的地址。

每一个逻辑地址都由一个段(Segment)和偏移量(Offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。

∙线性地址(虚拟地址):

一个32位无符号整数,可以用来表示高达4GB的地址。

线性地址通常用十六进制数字表示,值的范围为[0x00000000,0xffffffff)。

∙物理地址:

用于内存芯片级内存单元寻址。

它们与从CPU的地址引脚发送到内存总线上的电信号相对应。

物理地址由32位或36位无符号整数表示。

内存控制单元(MMU)通过一种称为分段单元的硬件电路把一个逻辑地址转换成线性地址;称为分页单元的硬件电路把线性地址转换成一个物理地址。

有些MMU没有分页单元,或者禁止使能分页单元,比如x86的实模式,那么就只有分段单元,那么经过分段单元转换后的地址就是物理地址。

有些MMU没有分段单元,大多数RISC架构的CPU就是如此,此时段基址相当于0,而代码中的偏移地址就是线性地址,所有Linux下逻辑地址和线性地址是一致的。

如下图所示:

图 60. 地址转换

Linux中以非常有限的方式使用分段。

运行在用户态的所有Linux进程都使用一对相同的段来对指令和数据寻址,它们的段基址分别是__USER_CS和__USER_DS。

与此同时,运行在内核态的所有Linux进程(内核线程)都使用一对相同的段对指令和数据寻址,它们的段基址分别__KERNEL_CS和__KERNEL_DS。

分段可以给每个进程分配不同的线性地址空间,分页可以把同一线性地址空间映射到不同的物理空间。

与分段相比,Linux更喜欢分页方式,因为:

∙当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一簇线性地址。

∙为了兼容绝大多数的CPU,RISC体系架构对分段的支持很有限,比如ARM架构的CPU中的MMU单元通常只支持分页,而不支持分段。

分页使得不同的虚拟内存页可以转入同一物理页框。

于此同时分页机制可以实现对每个页面的访问控制,这是在平衡内存使用效率和地址转换效率之间做出的选择。

如果4G的虚拟空间,每一个字节都要使用一个数据结构来记录它的访问控制信息,那么显然是不可能的。

如果把4G的虚拟空间以4K(为什么是4K大小?

这是由于Linux中的可执行文件中的代码段和数据段等都是相对于4K对齐的)大小分割成若干个不同的页面,那么每个页面需要一个数据结构进行控制,只需要1M的内存来存储。

但是由于每一个用户进程都有自己的独立空间,所以每一个进程都需要一个1M的内存来存储页表信息,这依然是对系统内存的浪费,采用两级甚至多级分页是一种不错的解决方案。

另外有些处理器采用64位体系架构,此时两级也不合适了,所以Linux使用三级页表。

∙页全局目录(PageGlobalDirectory),即pgd,是多级页表的抽象最高层。

每一级的页表都处理不同大小的内存。

每项都指向一个更小目录的低级表,因此pgd就是一个页表目录。

当代码遍历这个结构时(有些驱动程序就要这样做),就称为是在遍历页表。

∙页中间目录(PageMiddleDirectory),即pmd,是页表的中间层。

在x86架构上,pmd在硬件中并不存在,但是在内核代码中它是与pgd合并在一起的。

∙页表条目(PageTableEntry),即pte,是页表的最低层,它直接处理页,该值包含某页的物理地址,还包含了说明该条目是否有效及相关页是否在物理内存中的位。

12.2. 一级页表

三级页表由不同的的数据结构表示,它们分别是pgd_t,pmd_t和pte_t。

注意到它们均被定义为unsignedlong类型,也即大小为4bytes,32bits。

arch/arm/include/asm/page.h

typedefunsignedlongpte_t;

typedefunsignedlongpmd_t;

typedefunsignedlongpgd_t[2];

typedefunsignedlongpgprot_t;

以下是页表操作相关的宏定义。

#definepte_val(x)(x)

#definepmd_val(x)(x)

#definepgd_val(x)((x)[0])

#definepgprot_val(x)(x)

#define__pte(x)(x)

#define__pmd(x)(x)

#define__pgprot(x)(x)

任何一个用户进程都有自己的页表,与此同时,内核本身就是一个名为init_task的0号进程,每一个进程都有一个mm_struct结构管理进程的内存空间,init_mm是内核的mm_struct。

在系统引导阶段,首先通过__create_page_tables在内核代码的起始处_stext向低地址方向预留16K,用于一级页表(主内存页表)的存放,每个进程的页表都通过mm_struct中的pgd描述符进行引用。

内核页表被定义在swapper_pg_dir。

arch/arm/kernel/init_task.c

#defineINIT_MM(name)\

{\

.mm_rb=RB_ROOT,\

.pgd=swapper_pg_dir,\

.mm_users=ATOMIC_INIT

(2),\

.mm_count=ATOMIC_INIT

(1),\

.mmap_sem=__RWSEM_INITIALIZER(name.mmap_sem),\

.page_table_lock=__SPIN_LOCK_UNLOCKED(name.page_table_lock),\

.mmlist=LIST_HEAD_INIT(name.mmlist),\

.cpu_vm_mask=CPU_MASK_ALL,\

}

structmm_structinit_mm=INIT_MM(init_mm);

swapper_pg_dir在head.S中被定义为PAGE_OFFSET向上偏移TEXT_OFFSET。

TEXT_OFFSET代表内核代码段的相对于PAGE_OFFSET的偏移。

KERNEL_RAM_VADDR的值与_stext的值相同,代表了内核代码的起始地址。

swapper_pg_dir为KERNEL_RAM_VADDR-0x4000,也即向低地址方向偏移了16K。

arch/arm/Makefile

textofs-y:

=0x00008000

......

TEXT_OFFSET:

=$(textofs-y)

特定系统架构的Makefile中通过textofs-y定义了内核起始代码相对于PAGE_OFFSET的偏移。

arch/arm/kernel/head.S

#defineKERNEL_RAM_VADDR(PAGE_OFFSET+TEXT_OFFSET)

......

.globlswapper_pg_dir

.equswapper_pg_dir,KERNEL_RAM_VADDR-0x4000

ARMLinux中的主内存页表,使用段表。

每个页表映射1M的内存大小,由于16K/4*1M=4G,这16K的主页表空间正好映射4G的虚拟空间。

内核页表机制在系统启动过程中的paging_init函数中使能,其中对内核主页表的初始化等操作均是通过init_mm.pgd的引用来进行的。

在系统执行paging_init之前,系统的地址空间如下图所示:

图 61. 内核RAM布局

图中的黄色部分就是内核0号进程的主页表。

arch/arm/mm/mmu.c

void__initpaging_init(structmeminfo*mi,structmachine_desc*mdesc)

{

void*zero_page;

build_mem_type_table();

sanity_check_meminfo(mi);

prepare_page_table(mi);

bootmem_init(mi);

devicemaps_init(mdesc);

top_pmd=pmd_off_k(0xffff0000);

zero_page=alloc_bootmem_low_pages(PAGE_SIZE);

memzero(zero_page,PAGE_SIZE);

empty_zero_page=virt_to_page(zero_page);

flush_dcache_page(empty_zero_page);

}

图 62. ARM内存主页表初始化

paging_init依次完成了以下工作:

∙调用prepare_page_table初始化虚拟地址[0,PAGE_OFFSET]和[mi->bank[0].start+mi->bank[0].size,VMALLOC_END]所对应的主页表项,所有表项均初始化为0。

这里保留了内核代码区,主页表区以及Bootmem机制中的位图映射区对应的主页表。

这是为了保证内核代码的执行以及对主页表区和位图区的访问。

如果只有一个内存bank,那么mi->bank[0].start+mi->bank[0].size的值和high_memory保持一致,它是当前bank进行物理内存一一映射后的虚拟地址。

对于一个内存为256M的系统来说,它只有一个bank,经过prepare_page_table处理后的内存如上图所示。

∙接着在bootmem_init函数将通过bootmem_init_node对每一个内存bank的页表进行值的填充。

bootmem_init_node将通过map_memory_bank间接调用create_mapping,最终由该函数创建页表。

∙通过devicemaps_init初始化设备I/O对应的相关页表。

∙最后创建0页表,并在Dcache中清空0页表的缓存信息。

图 63. 页表创建函数调用

12.3. ARM内存访问

当ARM要访问内存RAM时,MMU首先查找TLB中的虚拟地址表,如果ARM的结构支持分开的地址TLB和指令TLB,那么它用:

∙取指令使用指令TLB

∙其它的所有访问类别用数据TLB

指令TLB和数据TLB在ARMv6架构的MMU中被分别称为指令MicroTLB和数据MicroTLB。

如果没有命中MicroTLB,那么将查询主TLB,此时不区分指令和数据TLB。

如果TLB中没有虚拟地址的入口,则转换表遍历硬件从存在主存储器中的转换表中获取转换页表项,它包含了物理地址或者二级页表地址和访问权限,一旦取到,这些信息将被放在TLB中,它会放在一个没有使用的入口处或覆盖一个已有的入口。

一旦为存储器访问的TLB的入口被拿到,这些信息将被用于:

∙C(高速缓存)和B(缓冲)位被用来控制高速缓存和写缓冲,并决定是否高速缓存。

∙首先检查域位,然后检查访问权限位用来控制访问是否被允许。

如果不允许,则MMU将向ARM处理器发送一个存储器异常;否则访问将被允许进行。

∙对没有或者禁止高速缓存的系统(包括在没有高速缓存系统中的所有存储器访问),物理地址将被用作主存储器访问的地址。

图 64. 高速缓存的MMU存储器系统

12.4. ARMMMU页表

在ARMv6的MMU机制中,提供了两种格式的页表描述符:

∙兼容ARMv4和ARMv5MMU机制的页表描述符。

这种描述符可以对64K大页面和4K小页面再进一步细分为子页面。

∙ARMv6特有的MMU页表描述符,这种页表描述符内增加了额外的特定比特位:

Not-Global(nG),Shared(S),Execute-Never(XN)和扩展的访问控制位APX。

图 65. 向前兼容的一级页表描述符格式

图 66. 向前兼容的二级页表描述符格式

Linux使用ARMv6特有的MMU页表描述符格式,它们的标志位描述如下:

图 67. ARMv6一级页表描述符格式

表 20. ARMv6一级页表描述符比特位含义

标志

含义

b[1:

0]

类型

指示页表类型:

b00错误项;b11保留;b01粗页表,它指向二级页表基址。

b10:

1MB大小段页表(b[18]置0)或16M大小超级段页表(b[18]置1)

b[2]

B[a]

写缓冲使能[b]

b[4]

Execute-Never(XN)

禁止执行标志:

1,禁止执行;0:

可执行

b[5:

8]

域(domain)

指明所属16个域的哪个域,访问权限由CP15的c3寄存器据定

b[9]

P(ECCEnable)

ECC使能标志,1:

该页表映射区使能ECC校验[c]

b[10:

11]

AP(AccessPermissions)

访问权限位,具体见访问权限列表

b[12:

14]

TEX(TypeExtensionField)

扩展类型,与B,C标志协同控制内存访问类型

bit[15]

APX(AccessPermissionsExtensionBit)

扩展访问权限位

bit[16]

S(Shared)

共享访问

bit[17]

nG(Not-Global)

全局访问

bit[18]

0/1

段页表和超级段页表开关

bit[19]

NS

 

[a]高速缓存和写缓存的引入是基于如下事实,即处理器速度远远高于存储器访问速度;如果存储器访问成为系统性能的瓶颈,则处理器再快也是浪费,因为处理器需要耗费大量的时间在等待存储器上面。

高速缓存正是用来解决这个问题,它可以存储最近常用的代码和数据,以最快的速度提供给CPU处理(CPU访问Cache不需要等待)。

[b]SBZ意味置0,该位在粗页表中置0。

[c]ARM1176JZF-S处理器不支持该标志位。

Linux在ARM体系架构的Hardwarepagetable头文件中通过宏定义了这些位。

arch/arm/include/asm/pgtable-hwdef.h

/*

*Hardwarepagetabledefinitions.

*

*+Level1descriptor(PMD)

*-common

*/

#definePMD_TYPE_MASK(3<<0)//获取一级页表类型的掩码,它取bit[0:

1]

#definePMD_TYPE_FAULT(0<<0)//置bit[0:

1]为b00,错误项

#definePMD_TYPE_TABLE(1<<0)//置bit[0:

1]为b01,粗页表

#definePMD_TYPE_SECT(2<<0)//置bit[0:

1]为b10,段页表

#definePMD_BIT4(1<<4)//定义bit[4],禁止执行标志位

#definePMD_DOMAIN(x)((x)<<5)//获取域标志位b[5:

8]

#definePMD_PROTECTION(1<<9)//b[9]ECC使能标志

以上定义了一级页表的相关标志位。

Linux使用段页表作为一级页表,粗页表作为二级页表的基址页表。

段页表的标志位定义如下:

#definePMD_SECT_BUFFERABLE(1<<2)

#definePMD_SECT_CACHEABLE(1<<3)

#definePMD_SECT_XN(1<<4)/*v6*/

#definePMD_SECT_AP_WRITE(1<<10)

#definePMD_SECT_AP_READ(1<<11)

#definePMD_SECT_TEX(x)((x)<<12)/*v5*/

#definePMD_SECT_APX(1<<15)/*v6*/

#definePMD_SECT_S(1<<16)/*v6*/

#definePMD_SECT_nG(1<<17)/*v6*/

#definePMD_SECT_SUPER(1<<18)/*v6*/

图 68. ARMv6二级页表基址格式

二级页表相同标志位的含义与一级页表相同,这里不再单独列出。

注意它的b[1]为1时,b[0]表示禁止执行标志。

Linux对二级页表中的标志位定义如下:

/*

*+Level2descriptor(PTE)

*-common

*/

#definePTE_TYPE_MASK(3<<0)//获取二级页表类型的掩码,它取bit[0:

1]

#definePTE_TYPE_FAULT(0<<0)//置bit[0:

1]为b00,错误项

#definePTE_TYPE_LARGE(1<<0)//置bit[0:

1]为b01,大页表(64K)

#definePTE_TYPE_SMALL(2<<0)//置bit[0:

1]为b10,扩展小页表(4K)

#definePTE_TYPE_EXT(3<<0)//使能禁止执行标志的扩展小页表(4K)

#definePTE_BUFFERABLE(1<<2)//B标志

#definePTE_CACHEABLE(1<<3)//C标志

Linux二级页表使用扩展小页表,这样每个二级页表可以表示通常的1个页面大小(4K)。

Linux对二级页表标志位的定义如下:

/*

*-extendedsmallpage/tinypage

*/

#definePTE_EXT_XN(1<<0)/*v6*/

#definePTE_EXT_AP_MASK(3<<4)

#definePTE_EXT_AP0(1<<4)

#definePTE_EXT_AP1(2<<4)

#definePTE_EXT_AP_UNO_SRO(0<<4)

#definePTE_EXT_AP_UNO_SRW(PTE_EXT_AP0)

#definePTE_EXT_AP_URO_SRW(PTE_EXT_AP1)

#definePTE_EXT_AP_URW_SRW(PTE_EXT_AP1|PTE_EXT_AP0)

#definePTE_EXT_TEX(x)((x)<<6)/*v5*/

#definePTE_EXT_APX(1<<9)/*v6*/

#definePTE_EXT_COHERENT(1<<9)/*XScale3*/

#definePTE_EXT_SHARED(1<<10)/*v6*/

#definePTE_EXT_NG(1<<11)/*v6*/

以上两种页表转换机制由CP15协处理器的控制寄存器c1中的bit23来选择。

bit23为0时为第一种机制,否则为第二种。

在CPU初始化后该位的默认值为0。

Linux在系统引导时会设置MMU的控制寄存器的相关位,其中把bit23设置为1,所以Linux在ARMv6体系架构上采用的是ARMv6MMU页表转换机制。

arch/arm/mm/proc-v6.S

__v6_setup:

......

adrr5,v6_crval

ldmiar5,{r5,r6}

mrcp15,0,r0,c1,c0,0@readcontrolregister

bicr0,r0,r5@clearbitsthem

orrr0,r0,r6@setthem

movpc,lr@returntohead.S:

__ret

/*

*VXFIDLR

*.......EPUI..T.T4RVIZFRSBLDPWCAM

*rrrrrrrxxxx00101xxxxxxxxx111xxxx

*011000111.00.1111101

*/

.typev6_crval,#object

v6_crval:

crvalclear=0x01e0fb7f,mmuset=0x00c0387d,ucset=0x00c0187c

注意到v6_crval定义了三个常量,首先mrc指令读取c1到r0,然后清除clear常量指定的比特位,然后设置mmuset指定的比特位,其中bit23为1。

在movpc,lr跳转后将执行定义在head.S中的__enable_mmu函数,在进一步调节其它的比特位后最终将把r0中的值写回c1寄存器。

12.5. 页面访问控制

在谈到create_mapping之前,必须说明一下Linux是如何实现对页面的访问控制的。

它定义了一个类型为structmem_type的局部静态数组。

根据不同的映射类型,它定义了不同的访问权限,它通过md参数中的type成员传递给create_mapping。

arch/arm/include/asm/io.h

/*

*Architectureioremapimplementation.

*/

#defineMT_DEVICE0

#defineMT_DEVICE_NONSHARED1

#defineMT_DEVICE_CACHED2

#defineMT_DEVICE_WC3

arch/arm/include/asm/mach/map.h

/*types0-3aredefinedinasm/io.h*/

#defineMT_UNCACHED4

#defineMT_CACHECLEAN5

#defineMT_MINICLEAN6

#defineMT_LOW_VECTORS7

#defineMT_HIGH_VECTORS8

#defineMT_MEMORY9

#defineMT_ROM10

系统中定义了多个映射类型,最常用的是MT_MEMORY,它对应RAM;MT_DEVICE则对应了其他I/O设备,应用于ioremap;MT_ROM对应于ROM;MT_LOW_VECTORS对应0地址开始的向量;MT_HIGH_VECTORS对应高地址开始的向量,它有vector_base宏决定。

arch/arm/mm/mm.h

structmem_type{

unsignedintprot_pte;

unsignedintprot_l1;

unsignedintprot_sect;

unsignedintdomain;

};

尽管Linux在多数系统上实现或者模拟了3级页表,但是在ARMLinux上它只实现了主页表和两级页表。

主页表通过ARMCPU的段表实现,段表中的每个页表项管理1M的内存,虚拟地址只需要一次转换既可以得到物

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 视频讲堂

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1