在Linux系统中,存在以下三种地址:
内存控制单元(MMU)通过一种称为分段单元的硬件电路把一个逻辑地址转换成线性地址;称为分页单元的硬件电路把线性地址转换成一个物理地址。有些MMU没有分页单元,或者禁止使能分页单元,比如x86的实模式,那么就只有分段单元,那么经过分段单元转换后的地址就是物理地址。有些MMU没有分段单元,大多数RISC架构的CPU就是如此,此时段基址相当于0,而代码中的偏移地址就是线性地址,所有Linux下逻辑地址和线性地址是一致的。如下图所示:
分段可以给每个进程分配不同的线性地址空间,分页可以把同一线性地址空间映射到不同的物理空间。与分段相比,Linux更喜欢分页方式,因为:
分页使得不同的虚拟内存页可以转入同一物理页框。于此同时分页机制可以实现对每个页面的访问控制,这是在平衡内存使用效率和地址转换效率之间做出的选择。如果4G的虚拟空间,每一个字节都要使用一个数据结构来记录它的访问控制信息,那么显然是不可能的。如果把4G的虚拟空间以4K(为什么是4K大小?这是由于Linux中的可执行文件中的代码段和数据段等都是相对于4K对齐的)大小分割成若干个不同的页面,那么每个页面需要一个数据结构进行控制,只需要1M的内存来存储。但是由于每一个用户进程都有自己的独立空间,所以每一个进程都需要一个1M的内存来存储页表信息,这依然是对系统内存的浪费,采用两级甚至多级分页是一种不错的解决方案。另外有些处理器采用64位体系架构,此时两级也不合适了,所以Linux使用三级页表。
三级页表由不同的的数据结构表示,它们分别是pgd_t,pmd_t和pte_t。注意到它们均被定义为unsigned long类型,也即大小为4bytes,32bits。 arch/arm/include/asm/page.h typedef unsigned long pte_t; typedef unsigned long pmd_t; typedef unsigned long pgd_t[2]; typedef unsigned long pgprot_t; 以下是页表操作相关的宏定义。 #define pte_val(x) (x) #define pmd_val(x) (x) #define pgd_val(x) ((x)[0]) #define pgprot_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 #define INIT_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, } struct mm_struct init_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 #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) ...... .globl swapper_pg_dir .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000 ARM Linux中的主内存页表,使用段表。每个页表映射1M的内存大小,由于16K / 4 * 1M = 4G,这16K的主页表空间正好映射4G的虚拟空间。内核页表机制在系统启动过程中的paging_init函数中使能,其中对内核主页表的初始化等操作均是通过init_mm.pgd的引用来进行的。在系统执行paging_init之前,系统的地址空间如下图所示:
arch/arm/mm/mmu.c void __init paging_init(struct meminfo *mi, struct machine_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); }
paging_init依次完成了以下工作:
当ARM要访问内存RAM时,MMU首先查找TLB中的虚拟地址表,如果ARM的结构支持分开的地址TLB和指令TLB,那么它用:
指令TLB和数据TLB在ARMv6架构的MMU中被分别称为指令MicroTLB和数据MicroTLB。如果没有命中MicroTLB,那么将查询主TLB,此时不区分指令和数据TLB。 如果TLB中没有虚拟地址的入口,则转换表遍历硬件从存在主存储器中的转换表中获取转换页表项,它包含了物理地址或者二级页表地址和访问权限,一旦取到,这些信息将被放在TLB中,它会放在一个没有使用的入口处或覆盖一个已有的入口。一旦为存储器访问的TLB 的入口被拿到,这些信息将被用于:
在ARMv6的MMU机制中,提供了两种格式的页表描述符:
Linux使用ARMv6特有的MMU页表描述符格式,它们的标志位描述如下: 表 20. ARMv6一级页表描述符比特位含义
Linux 在ARM体系架构的Hardware page table头文件中通过宏定义了这些位。 arch/arm/include/asm/pgtable-hwdef.h /* * Hardware page table definitions. * * + Level 1 descriptor (PMD) * - common */ #define PMD_TYPE_MASK (3 << 0) // 获取一级页表类型的掩码,它取bit[0:1] #define PMD_TYPE_FAULT (0 << 0) // 置bit[0:1]为b00,错误项 #define PMD_TYPE_TABLE (1 << 0) // 置bit[0:1]为b01,粗页表 #define PMD_TYPE_SECT (2 << 0) // 置bit[0:1]为b10,段页表 #define PMD_BIT4 (1 << 4) // 定义bit[4],禁止执行标志位 #define PMD_DOMAIN(x) ((x) << 5) // 获取域标志位b[5:8] #define PMD_PROTECTION (1 << 9) // b[9]ECC使能标志以上定义了一级页表的相关标志位。Linux使用段页表作为一级页表,粗页表作为二级页表的基址页表。段页表的标志位定义如下: #define PMD_SECT_BUFFERABLE (1 << 2) #define PMD_SECT_CACHEABLE (1 << 3) #define PMD_SECT_XN (1 << 4) /* v6 */ #define PMD_SECT_AP_WRITE (1 << 10) #define PMD_SECT_AP_READ (1 << 11) #define PMD_SECT_TEX(x) ((x) << 12) /* v5 */ #define PMD_SECT_APX (1 << 15) /* v6 */ #define PMD_SECT_S (1 << 16) /* v6 */ #define PMD_SECT_nG (1 << 17) /* v6 */ #define PMD_SECT_SUPER (1 << 18) /* v6 */ 二级页表相同标志位的含义与一级页表相同,这里不再单独列出。注意它的b[1]为1时,b[0]表示禁止执行标志。Linux对二级页表中的标志位定义如下: /* * + Level 2 descriptor (PTE) * - common */ #define PTE_TYPE_MASK (3 << 0) // 获取二级页表类型的掩码,它取bit[0:1] #define PTE_TYPE_FAULT (0 << 0) // 置bit[0:1]为b00,错误项 #define PTE_TYPE_LARGE (1 << 0) // 置bit[0:1]为b01,大页表(64K) #define PTE_TYPE_SMALL (2 << 0) // 置bit[0:1]为b10,扩展小页表(4K) #define PTE_TYPE_EXT (3 << 0) // 使能禁止执行标志的扩展小页表(4K) #define PTE_BUFFERABLE (1 << 2) // B标志 #define PTE_CACHEABLE (1 << 3) // C标志Linux二级页表使用扩展小页表,这样每个二级页表可以表示通常的1个页面大小(4K)。Linux对二级页表标志位的定义如下: /* * - extended small page/tiny page */ #define PTE_EXT_XN (1 << 0) /* v6 */ #define PTE_EXT_AP_MASK (3 << 4) #define PTE_EXT_AP0 (1 << 4) #define PTE_EXT_AP1 (2 << 4) #define PTE_EXT_AP_UNO_SRO (0 << 4) #define PTE_EXT_AP_UNO_SRW (PTE_EXT_AP0) #define PTE_EXT_AP_URO_SRW (PTE_EXT_AP1) #define PTE_EXT_AP_URW_SRW (PTE_EXT_AP1|PTE_EXT_AP0) #define PTE_EXT_TEX(x) ((x) << 6) /* v5 */ #define PTE_EXT_APX (1 << 9) /* v6 */ #define PTE_EXT_COHERENT (1 << 9) /* XScale3 */ #define PTE_EXT_SHARED (1 << 10) /* v6 */ #define PTE_EXT_NG (1 << 11) /* v6 */以上两种页表转换机制由CP15协处理器的控制寄存器c1中的bit23来选择。bit23为0时为第一种机制,否则为第二种。在CPU初始化后该位的默认值为0。Linux在系统引导时会设置MMU的控制寄存器的相关位,其中把bit23设置为1,所以Linux在ARMv6体系架构上采用的是ARMv6 MMU页表转换机制。 arch/arm/mm/proc-v6.S __v6_setup: ...... adr r5, v6_crval ldmia r5, {r5, r6} mrc p15, 0, r0, c1, c0, 0 @ read control register bic r0, r0, r5 @ clear bits them orr r0, r0, r6 @ set them mov pc, lr @ return to head.S:__ret /* * V X F I D LR * .... ...E PUI. .T.T 4RVI ZFRS BLDP WCAM * rrrr rrrx xxx0 0101 xxxx xxxx x111 xxxx < forced * 0 110 0011 1.00 .111 1101 < we want */ .type v6_crval, #object v6_crval: crval clear=0x01e0fb7f, mmuset=0x00c0387d, ucset=0x00c0187c注意到v6_crval定义了三个常量,首先mrc指令读取c1到r0,然后清除clear常量指定的比特位,然后设置mmuset指定的比特位,其中bit23为1。在mov pc, lr跳转后将执行定义在head.S中的__enable_mmu函数,在进一步调节其它的比特位后最终将把r0中的值写回c1寄存器。
在谈到create_mapping之前,必须说明一下Linux是如何实现对页面的访问控制的。它定义了一个类型为struct mem_type的局部静态数组。根据不同的映射类型,它定义了不同的访问权限,它通过md参数中的type成员传递给create_mapping。
arch/arm/include/asm/io.h /* * Architecture ioremap implementation. */ #define MT_DEVICE 0 #define MT_DEVICE_NONSHARED 1 #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3 arch/arm/include/asm/mach/map.h /* types 0-3 are defined in asm/io.h */ #define MT_UNCACHED 4 #define MT_CACHECLEAN 5 #define MT_MINICLEAN 6 #define MT_LOW_VECTORS 7 #define MT_HIGH_VECTORS 8 #define MT_MEMORY 9 #define MT_ROM 10系统中定义了多个映射类型,最常用的是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 struct mem_type { unsigned int prot_pte; unsigned int prot_l1; unsigned int prot_sect; unsigned int domain; };尽管Linux在多数系统上实现或者模拟了3级页表,但是在ARM Linux上它只实现了主页表和两级页表。主页表通过ARM CPU的段表实现,段表中的每个页表项管理1M的内存,虚拟地址只需要一次转换既可以得到物理地址,它通常存放在swapper_pg_dir开始的16K区域内。两级页表只有在被映射的物理内存块不满足1M的情况下才被使用,此时它由L1和L2组成。
arch/arm/include/asm/domain.h #define DOMAIN_KERNEL 0 #define DOMAIN_TABLE 0 #define DOMAIN_USER 1 #define DOMAIN_IO 2ARM Linux 中只是用了16个域中的三个域D0-D2。它们由上面的宏来定义,在系统引导时初始化MMU的过程中将对这三个域设置域访问权限。以下是内存空间和域的对应表: 表 21. 内存空间和域的对应表
在ARM处理器中,MMU中的每个域的访问权限分别由CP15的C3寄存器中的两位来设定,c3寄存器的小为32bits,刚好可以设置16个域的访问权限。下表列出了域的访问控制字段不同取值及含义: 表 22. ARM内存访问控制字
Linux定义了其中可以使用的三种域控制: arch/arm/include/asm/domain.h #define DOMAIN_NOACCESS 0 #define DOMAIN_CLIENT 1 #define DOMAIN_MANAGER 3Linux在系统引导设置MMU时初始化c3寄存器来实现对内存域的访问控制。其中对DOMAIN_USER,DOMAIN_KERNEL和DOMAIN_TABLE均设置DOMAIN_MANAGER权限;对DOMAIN_IO设置DOMAIN_CLIENT权限。如果此时读取c3寄存器,它的值应该是0x1f。 arch/arm/include/asm/domain.h #define domain_val(dom,type) ((type) << (2*(dom))) arch/arm/kernel/head.S ...... mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | domain_val(DOMAIN_IO, DOMAIN_CLIENT)) mcr p15, 0, r5, c3, c0, 0 @ load domain access register mcr p15, 0, r4, c2, c0, 0 @ load page table pointer b __turn_mmu_on ENDPROC(__enable_mmu)在系统的引导过程中对这3个域的访问控制位并不是一成不变的,它提供了一个名为modify_domain的宏来修改域访问控制位。系统在setup_arch中调用early_trap_init后,DOMAIN_USER的权限位将被设置成DOMAIN_CLIENT,此时它的值应该是0x17。 arch/arm/include/asm/domain.h #define set_domain(x) do { __asm__ __volatile__( "mcr p15, 0, %0, c3, c0 @ set domain" : : "r" (x)); isb(); } while (0) #define modify_domain(dom,type) do { struct thread_info *thread = current_thread_info(); unsigned int domain = thread->cpu_domain; domain &= ~domain_val(dom, DOMAIN_MANAGER); thread->cpu_domain = domain | domain_val(dom, type); set_domain(thread->cpu_domain); } while (0)访问权限由CP15的c1控制寄存器中的R和S位以及页表项中的访问权限控制位AP[0:1]以及访问权限扩展位APX来确定,通过R和S的组合控制方式在第一项中说明,并且已不被推荐使用。具体说明如下表所示。 表 23. MMU中存储访问权限控制[8]
static struct mem_type mem_types[] = { ...... [MT_MEMORY] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, ...... };对于MT_MEMORY的内存映射类型,依据段页表的各位功能,定义了如下的宏,显然PMD_TYPE_SECT定义了段页表类型0b10,PMD_SECT_AP_WRITE和PMD_SECT_AP_READ则对应AP[0]和AP[1]访问权限控制位。根据表 23 “MMU中存储访问权限控制”,在使用AP[0:1]进行权限控制时,CP15中的C1寄存器中的S和R标志位不影响权限,根据AP权限位的意义,并不能直接根据宏的后缀名得出是对读还是写的控制。 arch/arm/include/asm/pgtable-hwdef.h #define PMD_TYPE_TABLE (1 << 0) #define PMD_TYPE_SECT (2 << 0) #define PMD_BIT4 (1 << 4) #define PMD_DOMAIN(x) ((x) << 5) ...... #define PMD_SECT_AP_WRITE (1 << 10) #define PMD_SECT_AP_READ (1 << 11)一个描述了当前系统mem_types描述的所有内存映射类型权限控制的列表如下所示: 表 24. ARM Linux内存映射权限控制[9]
build_mem_type_table函数在paging_init执行的开始被调用,它将根据当前CPU的特性进一步调整这些访问权限位。
create_mapping是完成页表创的核心函数。它的声明如下:
arch/arm/mm/mmu.c void __init create_mapping(struct map_desc *md); create_mapping只有一个类型为struct map_desc的参数。这是一个非常简单的参数,但是包含了创建页表相关的所有信息。
arch/arm/include/asm/mach/map.h struct map_desc { unsigned long virtual; unsigned long pfn; unsigned long length; unsigned int type; }; 在Bootmem机制应用中有提到,系统中所有的内存块都在启动时被注册到meminfo中以struct membank类型的数组形式存在。map_memory_bank的作用就是将以struct membank类型的内存节点转换为struct map_desc类型然后传递给create_mapping。 struct meminfo { .nr_banks = 1; bank[8] = { { .start = 0x50000000; .size = 0x10000000; .node = 0; }; ... } }; 对于只有一个大小为256M物理RAM内存的系统来说,如果它具有以上的struct membank类型的内存信息,那么create_mapping得到的参数md如下所示: map_desc { .virtual = 0xc0000000; .pfn = 0x50000; .length = 0x10000000; .type = MT_MEMORY; }; [MT_MEMORY] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, } create_mapping依次完成了以下工作:
pgd_offset_k宏将一个0-4G范围内的虚拟地址转换为内核进程主页表中的对应页表项所在的地址。它首先根据pgd_index计算该虚拟地址对应的页表项在主页表中的索引值这里需要注意PGDIR_SHIFT的值为21,而非20,所以它的偏移是取2M大小区块的索引,这是由于pgd_t的类型为两个长整形的元素。然后根据索引值和内核进程中的init_mm.pgd取得页表项地址。 arch/arm/include/asm/pgtable.h /* to find an entry in a page-table-directory */ #define pgd_index(addr) ((addr) >> PGDIR_SHIFT) #define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr)) /* to find an entry in a kernel page-table-directory */ #define pgd_offset_k(addr) pgd_offset(&init_mm, addr) create_mapping在本质上是对传入参数的检查,并为调用alloc_init_section准备四要素:页表地址,虚拟地址的起止地址和物理地址的起始地址。Linux对create_mapping的调用除了在arch/arm/mm/init.c中通过map_memory_bank初始化主内存页面映射外,对其的调用均集中在arch/arm/mm/mmu.c中,其中iotable_init封装create_mapping用于特定机器架构的设备I/O映射。 页表创建函数调用图中给出了alloc_init_section在页表创建中所处的实现位置,本质上它是与alloc_init_pte并行的函数,alloc_init_section被用来创建段页表(主页表),alloc_init_pte则用来在被映射区长度小于1M时创建2级页表。 static void __init alloc_init_section(pgd_t *pgd, unsigned long addr, unsigned long end, unsigned long phys, const struct mem_type *type);
alloc_init_section依次完成了以下工作:
alloc_init_pte在初始化非主RAM中起到重要的作用,它尝试创建二级页表。二级页表的L1实际上还是存在于主页表中,只不过此时的主页表项不再是物理地址,而是二级页表,或者称为中间页表(PMD)。
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr, unsigned long end, unsigned long pfn, const struct mem_type *type);
alloc_init_pte依次完成以下工作:
表 25. 页表计算
对__pmd_populate内联函数的说明如下:
#define PTRS_PER_PTE 512 #define pmd_val(x) (x) static inline pte_t *pmd_page_vaddr(pmd_t pmd) { unsigned long ptr; ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1); ptr += PTRS_PER_PTE * sizeof(void *); return __va(ptr); } set_pte_ext用来填充硬件PTE页表。在create_mapping中被调用,通过一个循环,被传入的物理页帧和大小以PAGE_SIZE步进,进行二级页表的计算和填充。 do { set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0); pfn++; } while (pte++, addr += PAGE_SIZE, addr != end); 它通过调用特定系统的pte函数完成,对于ARMv6来说,定义如下: arch/arm/include/asm/pgtable.h #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext) #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext) arch/arm/include/asm/cpu-single.h #define cpu_set_pte_ext __cpu_fn(CPU_NAME,_set_pte_ext) arch/arm/mm/proc-v6.S ENTRY(cpu_v6_set_pte_ext) #ifdef CONFIG_MMU armv6_set_pte_ext cpu_v6 #endif mov pc, lr cpu_v6_set_pte_ext函数中引用了armv6_set_pte_ext宏,并传入cpu_v6参数。该宏定义如下: arch/arm/mm/proc-macros.S .macro armv6_set_pte_ext pfx str r1, [r0], #-2048 @ linux version 根据ATPCS规则,C语言函数在调用汇编语言时,分别通过r0-r2来依次传递参数。所以这里的r0代表的是pte参数,也即二级页表的地址;r1为通过pfn_pte计算出的二级页表项;r2为0。这里的str指令首先将人r1存入r0所指向的地址,也即填充二级页表项,然后将r0的值减去2048,相当于下移了2048 / 4 = 512项。这里给出一个一二级页表的全图:
arch/arm/include/asm/pgtable-hwdef.h #define PTE_TYPE_MASK (3 << 0) #define PTE_EXT_AP0 (1 << 4) bic r3, r1, #0x000003fc bic r3, r3, #PTE_TYPE_MASK orr r3, r3, r2 orr r3, r3, #PTE_EXT_AP0 | 2 bic位清除指令首先将r1中0x3fc对应的位清0,然后对PTE_TYPE_MASK指定的最后两位清0,也即对0x3ff指定的最后11位清零,对于值为0x5074034b的r1来说,存入r3的值为0x50740000。orr按位逻辑或指令通过将r3中的值与r2位或操作放入r3,由于r2的值为0,所以r3的值此时保持不变。最后的orr将r3的值加上PTE_EXT_AP0权限位,或上2是为了指定当前是小页表(4K),此时r3的值为0x50740012。 arch/arm/include/asm/pgtable.h #define L_PTE_MT_MASK (0x0f << 2) adr ip, \pfx\()_mt_table and r2, r1, #L_PTE_MT_MASK ldr r2, [ip, r2] adr伪指令将cpu_v6_mt_table的地址装入ip寄存器,然后取r1中0x5074034b的L_PTE_MT_MASK位作为索引值,这里为8。由于表中每一项的大小为4字节,所以[ip, 8]对应表中的第3项,也即L_PTE_MT_WRITETHROUGH。 arch/arm/include/asm/pgtable.h #define L_PTE_MT_WRITETHROUGH (0x02 << 2) /* 0010 */ /* * The ARMv6 and ARMv7 set_pte_ext translation function. * * Permission translation: * YUWD APX AP1 AP0 SVC User * 0xxx 0 0 0 no acc no acc * 100x 1 0 1 r/o no acc * 10x0 1 0 1 r/o no acc * 1011 0 0 1 r/w no acc * 110x 0 1 0 r/w r/o * 11x0 0 1 0 r/w r/o * 1111 0 1 1 r/w r/w */ .macro armv6_mt_table pfx \pfx\()_mt_table: .long 0x00 @ L_PTE_MT_UNCACHED .long PTE_EXT_TEX(1) @ L_PTE_MT_BUFFERABLE .long PTE_CACHEABLE @ L_PTE_MT_WRITETHROUGH .long PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEBACK .long PTE_BUFFERABLE @ L_PTE_MT_DEV_SHARED .long 0x00 @ unused .long 0x00 @ L_PTE_MT_MINICACHE (not present) .long PTE_EXT_TEX(1) | PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEALLOC .long 0x00 @ unused .long PTE_EXT_TEX(1) @ L_PTE_MT_DEV_WC .long 0x00 @ unused .long PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_DEV_CACHED .long PTE_EXT_TEX(2) @ L_PTE_MT_DEV_NONSHARED .long 0x00 @ unused .long 0x00 @ unused .long 0x00 @ unused .endm 首先测试二级页表项0x5074034b中的L_PTE_WRITE和L_PTE_DIRTY标志位,如果设置了L_PTE_WRITE,但没有L_PTE_DIRTY,那么设置那么设置PTE_EXT_APX到r3代表的硬件二级页表项0x50740021中。显然这里不会设置该位。 arch/arm/include/asm/pgtable.h #define L_PTE_DIRTY (1 << 6) #define L_PTE_WRITE (1 << 7) arch/arm/include/asm/pgtable-hwdef.h #define PTE_EXT_APX (1 << 9) /* v6 */ tst r1, #L_PTE_WRITE tstne r1, #L_PTE_DIRTY orreq r3, r3, #PTE_EXT_APX 如果Linux版本的二级页表项设置了L_PTE_USER标志,r3被置PTE_EXT_AP1。如果r3包含PTE_EXT_APX标志,那么同时清除PTE_EXT_APX和 PTE_EXT_AP0。 #define L_PTE_USER (1 << 8) tst r1, #L_PTE_USER orrne r3, r3, #PTE_EXT_AP1 tstne r3, #PTE_EXT_APX bicne r3, r3, #PTE_EXT_APX | PTE_EXT_AP0 如果r1没有L_PTE_EXEC标志,则设置PTE_EXT_XN。 tst r1, #L_PTE_EXEC orreq r3, r3, #PTE_EXT_XN orr r3, r3, r2 然后再加上L_PTE_MT_WRITETHROUGH标志。然后根据L_PTE_YOUNG标志,确定是否加上L_PTE_PRESENT标志。 tst r1, #L_PTE_YOUNG tstne r1, #L_PTE_PRESENT moveq r3, #0 str r3, [r0] mcr p15, 0, r0, c7, c10, 1 @ flush_pte .endm str指令将最终的硬件PTE页表值存放到低地址的二级页表中。所以硬件使用的二级页表总是位于低地址处,而高地址处的512项PTE是留给Linux自己使用的。
|
|