zoukankan      html  css  js  c++  java
  • Linux内存管理 (2)页表的映射过程

     专题:Linux内存管理专题

     关键词:swapper_pd_dir、ARM PGD/PTE、Linux PGD/PTE、pgd_offset_k

    Linux下的页表映射分为两种,一是Linux自身的页表映射,另一种是ARM32 MMU硬件的映射。

    1. ARM32页表映射

     由于ARM32和Linux内核维护的页表项有所不同,所以维护了两套PTE。

    PGD存放在swapper_pd_dir中,一个PGD目录项其实包含了两份ARM32 PGD。

    所以再分配PTE的时候,共分配了1024个PTE,512个给Linux OS维护用;512个给ARM32 MMU用,对应两个PGD的页表数目。

    由于Linux OS和ARM32的PTE紧邻,所以两者的转换也方便进行。

    1.1 ARM32处理器查询页表

    32bit的Linux采用三级映射:PGD-->PMD-->PTE,64bit的Linux采用四级映射:PGD-->PUD-->PMD-->PTE,多了个PUD。

    缩写是PGD:Page Global Directory、PUD:Page Upper Directory、PMD:Page Middle Directory、PTE:Page Table Entry。

    在ARM32 Linux采用两层映射,省略了PMD,除非在定义了CONFIG_ARM_LPAE才会使用3级映射。

    在ARM32架构中,可以按段(section)来映射,这是采用单层映射模式

    使用页面映射需要两层映射结构,页面可以是64KB或4KB大小。

    1.1.1 ARM32架构MMU4KB页面映射过程

    如果采用页表映射的方式,段映射表就变成一级映射表(Linux中称为PGD),其页表项提供的不再是物理地址,而是二级页表的基地址。

    32位虚拟地址的高12位(bit[31:20])作为访问一级页表的索引值,找到相应的表项,每个表项指向一个二级页表。

    以虚拟地址的次8位(bit[19:12])作为访问二级页表的索引值,得到相应的页表项,从这个页表项中找到20位的物理页面地址。

    最后将这20位物理页面地址和虚拟地址的低12位拼凑在一起,得到最终的32位物理地址。

    这个过程在ARM32架构中由MMU硬件完成,软件不需要接入。

    ARM32架构MMU页表映射过程

    1.1.2 ARMv7-AR中关于Short Descriptor映射概览图

    关于4K页表的映射过程在ARMv7-AR用户架构手册有关介绍。

    一个地址映射的概览图,32位虚拟地址从TTBR1中找到First-level table地址,然后取虚拟地址VA[31:20]作为序号找到Second-level table地址。

    取虚拟地址VA[19:12]作为序号找到Page地址。

    规格书中Small Page映射过程

    Figure B3-11 Small page address translation是映射的细节:

    1.2 Linux页表映射相关数据结构

    我们知道在map_lowmem()使用create_mapping()创建页表映射,这个函数的参数结构是struct map_desc。

    下面来研究它的相关结构,有助于理解内核是如何处理页表映射的。

    archarmincludeasmmachmap.h:
    
    struct map_desc {
        unsigned long virtual;------虚拟地址起始地址
        unsigned long pfn;----------物理地址开始页帧号
        unsigned long length;-------内存空间大小
        unsigned int type;----------mem_types中的序号
    };

    map_desc中的type指向类型为struct mem_type的mem_types数组:

    archarmmmmm.h:
    struct mem_type {
        pteval_t prot_pte;------------PTE属性
        pteval_t prot_pte_s2;---------定义CONFIG_ARM_LPAE才有效
        pmdval_t prot_l1;-------------PMD属性
        pmdval_t prot_sect;-----------Section类型映射
        unsigned int domain;----------定义ARM中不同的域
    };
    
    archarmmmmmu.c:
    static struct mem_type mem_types[] = {
    ...
        [MT_MEMORY_RWX] = {
            .prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY,----------------------注意这里都是L_PTE_*类型,需要在写入MMU对应PTE时进行转换。
            .prot_l1   = PMD_TYPE_TABLE,
            .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
            .domain    = DOMAIN_KERNEL,
        },
        [MT_MEMORY_RW] = {
            .prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
                     L_PTE_XN,
            .prot_l1   = PMD_TYPE_TABLE,
            .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
            .domain    = DOMAIN_KERNEL,
        },
    ...
    }

    下面重点关注Page Table类型的一级页表和二级页表的细节,以及Linux内核中的定义:

     ARM32中PGD定义

     下面是First-level descriptor详细说明:

    /*
     * Hardware page table definitions.
     *
     * + Level 1 descriptor (PMD)
     *   - common
     */
    #define PMD_TYPE_MASK        (_AT(pmdval_t, 3) << 0)---------------------------01对应PageTable
    #define PMD_TYPE_FAULT        (_AT(pmdval_t, 0) << 0)
    #define PMD_TYPE_TABLE        (_AT(pmdval_t, 1) << 0)
    #define PMD_TYPE_SECT        (_AT(pmdval_t, 2) << 0)
    #define PMD_PXNTABLE        (_AT(pmdval_t, 1) << 2)     /* v7 */
    #define PMD_BIT4        (_AT(pmdval_t, 1) << 4)
    #define PMD_DOMAIN(x)        (_AT(pmdval_t, (x)) << 5)
    #define PMD_PROTECTION        (_AT(pmdval_t, 1) << 9)        /* v5 */

     ARM32中PTE定义

    下面是Second-level descriptor详细说明:

    /*
     * + Level 2 descriptor (PTE)
     *   - common
     */
    #define PTE_TYPE_MASK        (_AT(pteval_t, 3) << 0)
    #define PTE_TYPE_FAULT        (_AT(pteval_t, 0) << 0)
    #define PTE_TYPE_LARGE        (_AT(pteval_t, 1) << 0)
    #define PTE_TYPE_SMALL        (_AT(pteval_t, 2) << 0)
    #define PTE_TYPE_EXT        (_AT(pteval_t, 3) << 0)        /* v5 */
    #define PTE_BUFFERABLE        (_AT(pteval_t, 1) << 2)
    #define PTE_CACHEABLE        (_AT(pteval_t, 1) << 3)
    
    /*
     *   - extended small page/tiny page
     */
    #define PTE_EXT_XN        (_AT(pteval_t, 1) << 0)        /* v6 */
    #define PTE_EXT_AP_MASK        (_AT(pteval_t, 3) << 4)
    #define PTE_EXT_AP0        (_AT(pteval_t, 1) << 4)
    #define PTE_EXT_AP1        (_AT(pteval_t, 2) << 4)
    #define PTE_EXT_AP_UNO_SRO    (_AT(pteval_t, 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)        (_AT(pteval_t, (x)) << 6)    /* v5 */
    #define PTE_EXT_APX        (_AT(pteval_t, 1) << 9)        /* v6 */
    #define PTE_EXT_COHERENT    (_AT(pteval_t, 1) << 9)        /* XScale3 */
    #define PTE_EXT_SHARED        (_AT(pteval_t, 1) << 10)    /* v6 */
    #define PTE_EXT_NG        (_AT(pteval_t, 1) << 11)    /* v6 */

     Linux中PTE定义

    由于Linux对于PTE的定义和ARM硬件不一致,下面的L_开头的定义都是针对Linux的,L_MT开头的是bit[5:2]表示的内存类型。

    /*
     * "Linux" PTE definitions.
     *
     * We keep two sets of PTEs - the hardware and the linux version.
     * This allows greater flexibility in the way we map the Linux bits
     * onto the hardware tables, and allows us to have YOUNG and DIRTY
     * bits.
     *
     * The PTE table pointer refers to the hardware entries; the "Linux"
     * entries are stored 1024 bytes below.
     */
    #define L_PTE_VALID        (_AT(pteval_t, 1) << 0)        /* Valid */
    #define L_PTE_PRESENT        (_AT(pteval_t, 1) << 0)
    #define L_PTE_YOUNG        (_AT(pteval_t, 1) << 1)
    #define L_PTE_DIRTY        (_AT(pteval_t, 1) << 6)
    #define L_PTE_RDONLY        (_AT(pteval_t, 1) << 7)
    #define L_PTE_USER        (_AT(pteval_t, 1) << 8)
    #define L_PTE_XN        (_AT(pteval_t, 1) << 9)
    #define L_PTE_SHARED        (_AT(pteval_t, 1) << 10)    /* shared(v6), coherent(xsc3) */
    #define L_PTE_NONE        (_AT(pteval_t, 1) << 11)
    
    /*
     * These are the memory types, defined to be compatible with
     * pre-ARMv6 CPUs cacheable and bufferable bits:   XXCB
     */
    #define L_PTE_MT_UNCACHED    (_AT(pteval_t, 0x00) << 2)    /* 0000 */
    #define L_PTE_MT_BUFFERABLE    (_AT(pteval_t, 0x01) << 2)    /* 0001 */
    #define L_PTE_MT_WRITETHROUGH    (_AT(pteval_t, 0x02) << 2)    /* 0010 */
    #define L_PTE_MT_WRITEBACK    (_AT(pteval_t, 0x03) << 2)    /* 0011 */
    #define L_PTE_MT_MINICACHE    (_AT(pteval_t, 0x06) << 2)    /* 0110 (sa1100, xscale) */
    #define L_PTE_MT_WRITEALLOC    (_AT(pteval_t, 0x07) << 2)    /* 0111 */
    #define L_PTE_MT_DEV_SHARED    (_AT(pteval_t, 0x04) << 2)    /* 0100 */
    #define L_PTE_MT_DEV_NONSHARED    (_AT(pteval_t, 0x0c) << 2)    /* 1100 */
    #define L_PTE_MT_DEV_WC        (_AT(pteval_t, 0x09) << 2)    /* 1001 */
    #define L_PTE_MT_DEV_CACHED    (_AT(pteval_t, 0x0b) << 2)    /* 1011 */
    #define L_PTE_MT_VECTORS    (_AT(pteval_t, 0x0f) << 2)    /* 1111 */
    #define L_PTE_MT_MASK        (_AT(pteval_t, 0x0f) << 2)

    ARM PMD描述符bit[8:5]用于描述Domain,但ARM Linux只定义使用三个:

    #define DOMAIN_KERNEL    2---------用于内核空间
    #define DOMAIN_TABLE    2
    #define DOMAIN_USER    1-----------用于用户空间
    #define DOMAIN_IO    0-------------用于I/O地址域

    1.3 设置PGD页面目录

    create_mapping的参数是struct map_desc类型,用于描述一个虚拟地址区域线性映射到物理区域。基于这块区域创建PGD/PTE。

    static void __init create_mapping(struct map_desc *md)
    {
        unsigned long addr, length, end;
        phys_addr_t phys;
        const struct mem_type *type;
        pgd_t *pgd;
    ...
        type = &mem_types[md->type];------------------------------找到对应的struct mem_type
    ...
        addr = md->virtual & PAGE_MASK;---------------------------对齐到页
        phys = __pfn_to_phys(md->pfn);----------------------------页到物理地址转换
        length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
    ...
    pgd
    = pgd_offset_k(addr);---------------------------------根据addr找到对应虚拟地址对应的pgd地址 end = addr + length; do { unsigned long next = pgd_addr_end(addr, end); alloc_init_pud(pgd, addr, next, phys, type);----------初始化下一级页表 phys += next - addr; addr = next; } while (pgd++, addr != end);-----------------------------遍历区间地址,步长是PGDIR_SIZE,即2MB大小的空间。 }

    这里面有三个地方需要解释:

    pgd_offset_k

    将虚拟地址进行转换得到PMD的指针。

    #define PGDIR_SHIFT 21
    
    /* 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))
    
    
    #define pgd_offset_k(addr)    pgd_offset(&init_mm, addr)
    
    struct mm_struct init_mm = {
        .mm_rb        = RB_ROOT,
        .pgd        = swapper_pg_dir,
        .mm_users    = ATOMIC_INIT(2),
        .mm_count    = ATOMIC_INIT(1),
        .mmap_sem    = __RWSEM_INITIALIZER(init_mm.mmap_sem),
        .page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
        .mmlist        = LIST_HEAD_INIT(init_mm.mmlist),
        INIT_MM_CONTEXT(init_mm)
    };

    由虚拟内存布局图中swapper_pg_dir可知,大小为16KB,里面有详细的解释。init_mm.pgd指向swapper_pg_dir。

    pgd_addr_end

    includeasm-genericpgtable.h:
    
    #define pgd_addr_end(addr, end)                        
    ({    unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;    
        (__boundary - 1 < (end) - 1)? __boundary: (end);        
    })
    
    archarmincludeasmpgtable-2level.h:
    /*
     * PMD_SHIFT determines the size of the area a second-level page table can map
     * PGDIR_SHIFT determines what a third-level page table entry can map
     */
    #define PMD_SHIFT        21
    #define PGDIR_SHIFT        21
    
    #define PMD_SIZE        (1UL << PMD_SHIFT)
    #define PMD_MASK        (~(PMD_SIZE-1))
    #define PGDIR_SIZE        (1UL << PGDIR_SHIFT)
    #define PGDIR_MASK        (~(PGDIR_SIZE-1))

    由于PGDIR_SHIFT为21,所以一个PGD页表目录对应2MB大小的空间,即[addr, addr+PGDIR_SIZE)。所以PGD的数目为2^11,2028个。整个PGD页表占用空间为2048*4B=8KB。

    这和ARM硬件的4096 PGD不一致。这里涉及到Linux实现技巧,在创建PTE中进行分析。

    所以此处按照2MB步长,遍历[virtual, virtual+length)空间创建PDG页表和PTE

    alloc_init_pte

    由于ARM-Linux采用两级页表映射,跳过PUD/PMD,直接到alloc_init_pte创建PTE。

    alloc_init_pud-->alloc_init_pmd-->alloc_init_pte
    
    archarmmmmmu.c:
    static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,------------这里的pmd=pud=pgd。
                      unsigned long end, unsigned long pfn,
                      const struct mem_type *type)
    {
        pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);------------------使用prot_l1作为参数,创建PGD页表目录,返回addr对应的pte地址。
        do {
            set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);---------调用体系结构相关汇编,配置PTE。
            pfn++;
        } while (pte++, addr += PAGE_SIZE, addr != end);-------------------------遍历[addr, end)区间内存,以PAGE_SIZE为步长。
    }

    下面看看如何分配PGD页表目录:

    static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
    {
        if (pmd_none(*pmd)) {---------------------------------------------------如果PGD的内容为空,即PTE还没有创建,择取建立页面。
            pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);-------分配512+512个PTE页表项
            __pmd_populate(pmd, __pa(pte), prot);-------------------------------生成pmd页表目录,并刷入RAM
        }
        BUG_ON(pmd_bad(*pmd));
        return pte_offset_kernel(pmd, addr);------------------------------------返回当前addr对应的PTE地址
    }
    
    early_alloc-->early_alloc_aligned:
    static void __init *early_alloc_aligned(unsigned long sz, unsigned long align)
    {
        void *ptr = __va(memblock_alloc(sz, align));-------------------------------------基于memblock进行分配,这里分配4096B,刚好是一页大小。
        memset(ptr, 0, sz);
        return ptr;
    }

    所以存放PGD需要的空间通过memblock进行申请,PTE_HWTABLE_OFF和PTE_HWTABLE_SIZE都为512,所以一个1024个PTE。

    下面是early_pte_alloc分配的空间示意图:前面512个表项是给Linux OS使用的,后512个表项是给ARM硬件MMU用的。

    Linux内核PGD/PTE映射关系

    static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
                      pmdval_t prot)
    {
        pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;------------------生成pmdp[0]的内容
        pmdp[0] = __pmd(pmdval);
    #ifndef CONFIG_ARM_LPAE
        pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));---------------------生成紧邻的pmdp[1]的内容
    #endif
        flush_pmd_entry(pmdp);---------------------------------------------将pmdp两个刷入到RAM中
    }

     Linux的PGD页表目录和ARM32不同,总数和ARM32是一样的。

     

    在arm_mm_memblock_reserve中,通过swapper_pg_dir可以知道其大小为16KB。

    就来看看SWAPPER_PG_DIR_SIZE,一共2048个PGD,但是每个PGD包含了两个相邻的PGD页面目录项。

    
    

    typedef pmdval_t pgd_t[2];---------------------------------------------8字节

    #define SWAPPER_PG_DIR_SIZE (PTRS_PER_PGD * sizeof(pgd_t))-------------2048*8B=16KB


    /*
    * Reserve the special regions of memory */ void __init arm_mm_memblock_reserve(void) { /* * Reserve the page tables. These are already in use, * and can only be in node 0. */ memblock_reserve(__pa(swapper_pg_dir), SWAPPER_PG_DIR_SIZE); ... }

      

    1.4 设置PTE表项

    要理解是如何设置PTE表项,就需要参照B3.3.1 Translation table entry formants中关于Second-level descriptors的描述。

     

     

      

    archarmincludeasmpgtable-2level.h:
    #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)
    
    archarmincludeasmglue-proc.h:
    #ifndef MULTI_CPU
    ...
    #define cpu_set_pte_ext            __glue(CPU_NAME,_set_pte_ext)
    ...
    #endif
    archarmmmproc-v7-2level.S: /* * cpu_v7_set_pte_ext(ptep, pte) * * Set a level 2 translation table entry. * * - ptep - pointer to level 2 translation table entry----------放入r0 * (hardware version is stored at +2048 bytes) * - pte - PTE value to store----------------------------------放入r1 * - ext - value for extended PTE bits------------------------放入r2 */ ENTRY(cpu_v7_set_pte_ext) #ifdef CONFIG_MMU str r1, [r0] @ linux version----------将r1的值存入r0地址的内存中 bic r3, r1, #0x000003f0--------------------------清除r1的bit[9:4],存入r3 bic r3, r3, #PTE_TYPE_MASK-----------------------PTE_TYPE_MASK为0x03,记清除低2位 orr r3, r3, r2-----------------------------------r3与r2或,存入r3 orr r3, r3, #PTE_EXT_AP0 | 2---------------------这里将bit1和bit4置位,所以是Small page。 tst r1, #1 << 4----------------------------------判断r1的bit4是否为0 orrne r3, r3, #PTE_EXT_TEX(1)--------------------设置TEX为1 eor r1, r1, #L_PTE_DIRTY tst r1, #L_PTE_RDONLY | L_PTE_DIRTY orrne r3, r3, #PTE_EXT_APX-----------------------设置AP[2] tst r1, #L_PTE_USER orrne r3, r3, #PTE_EXT_AP1-----------------------设置AP[1] tst r1, #L_PTE_XN orrne r3, r3, #PTE_EXT_XN------------------------设置XN位 tst r1, #L_PTE_YOUNG tstne r1, #L_PTE_VALID eorne r1, r1, #L_PTE_NONE tstne r1, #L_PTE_NONE moveq r3, #0 ARM( str r3, [r0, #2048]! )---------------------并没有写入r0,而是写入r0+2048Bytes的偏移。 THUMB( add r0, r0, #2048 ) THUMB( str r3, [r0] ) ALT_SMP(W(nop)) ALT_UP (mcr p15, 0, r0, c7, c10, 1) @ flush_pte #endif bx lr ENDPROC(cpu_v7_set_pte_ext)
  • 相关阅读:
    通过反射将一个java对象的属性值转换为一个Map
    反射中的 Method 的 getReadMethod 与 getWriteMethod 使用 【获取一个对象的所有属性字段名称和其对应的值】
    maven项目无法导入Oracle的jdbc连接jar包【我】
    史上最全最详细JNDI数据源配置说明
    启动eclipse导致Tomcat的配置文件重置
    各种集合key,value能否为null
    CentOS系统使用yum安装配置MariaDB数据库
    Unity导出webPlayer并且部署到IIS
    unity web项目发布服务器Data file is corrupt (not a Unity W
    Unity3d Web Player 的server端联网配置
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/8087022.html
Copyright © 2011-2022 走看看