内存管理-20-页表-1-理论

基于msm-5.4

一、ARM64内存管理

ARM处理器内核的MMU单元包括TLB和页表遍历单元(Table Walk Unit)两个部件。TLB是一个高速缓存,用于缓存页表转换结果,从而减少页表查询时间。一个完整的页表翻译和查找的过程叫做页表查询。页表查询过程由硬件自动完成,但是页表的维护需要由软件来完成。页表查询是个较耗时的过程,理想状态下TLB中应存有页表相关信息,TLB未命中时,MMU才会查询页表,从而得到翻译后的物理地址。页表存储在主存储器中。先访问TLB,若未命中再访问主存储器。

对于多任务操作系统,每个进程都拥有独立的进程地址空间,这些地址空间在虚拟地址空间内是相互隔离的,但是物理地址空间可能映射到同一个页面。进程地址空间又分为用户空间和内核空间。


二、ARM64页表

ARM64支持一阶页表和二阶页表,一阶是VA-->PA,二阶是 VA-->IPA-->PA。ARMv8架构支持4K、16K、64K这三种页面粒度。最大支持4级页表,地址总线位宽最多支持48bit,因此将VA划分为两个空间,每个空间最多支持256TB。

(1) 低位虚拟地址空间位于 0x0000_0000_0000_0000 -- 0x0000_FFFF_FFFF_FFFF. 如果虚拟地址的最高位是0,就使用这个虚拟地址空间,并使用 TTBR0_ELx 来存放页表基地址。

(2) 高位虚拟地址空间位于 0xFFFF_0000_0000_0000 -- 0xFFFF_FFFF_FFFF_FFFF. 如果虚拟地址的最高位是1,就使用这个虚拟地址空间,并使用 TTBR1_ELx 来存放页表基地址。

对于39bit-VA + 48bit-PA 的三级页表映射,两个虚拟地址空间分别为:
0x0000_0000_0000_0000 -- 0x0000_007F_FFFF_FFFF
0xFFFF_FF80_0000_0000 -- 0xFFFF_FFFF_FFFF_FFFF


1. 页表映射

当TLB未命中时,处理器查询页表的过程:先根据虚拟地址最高位bit63位来决定使用TTBR0还是TTBR1作为页表基地址寄存器,页表基地址寄存器中存放着1级页表。这里以48bit虚拟地址,48bit物理地址,3级页表为例,映射关系如下:

对于39bit-VA + 48bit-PA 的三级页表映射, 如下图:


2. L0-L2页表项描述符

每个页表项描述符占8字节,L0-L2页表项内存比较类似:

注: n和m表示L0-L2中变化的部分。

当Bit[0]为1时,表示有效的描述符,为0时表示无效描述符。Bit[1]用来表示类型,为1表示页表类型,该描述符包含指向下一级页表的基地址,为0表示这是一个块类型的页表项,如2M或1G。

在块类型页表项中,bit[47:n]表示最终输出的物理地址。若页面粒度为4K, 在L1页表描述符中n为30,表示1GB大小的连续物理内存。在L2页表项描述符中n为21,用来表示2MB大小的连续物理内存。若页面粒度为16K, 在L2页表描述符中n为25,表示32MB大小的连续物理内存。

在页表类型的页表项描述符中,Bit[47:m]用来保存下一级页表项的基地址,当页面粒度为4K/16K/64K时,m分别为12/14/16.

补充:

(1) 对于39bit虚拟地址+48bit物理地址的section映射,只有两级PGD+PMD,此时PMD页表项从bit12就开始就存储pfn了。


3. L3页表描述符

L3页表描述符有5种页表项,分别如下:

当Bit[0]为1时是有效页表项,为0时是无效页表项。Bit[1]为1时表示页表类型页表项,为0时表示保留页表项。
Bit[11:2]是低位属性,Bit[63:51]是高位属性,属性含义如下:

AttrIndex[2:0] 对应 Bit[4:2], MAIR_ELn寄存器用来表示内存属性,如设备内存(DeviceMemory)、普通内存等。对于软件可以设置8个不同内存属性。常见的内存属性有 DEVICE_nGnRnE、DEVICE_nGnRE、DEVICE_GRE、DEVICE_NC、NORMAL、NORMAL_WT,AttrIndex用来索引不同的内存属性。

NS 对应 Bit[5], 非安全(non-secure)位。当处于安全模式时用于指定访问的内存地址是安全映射的还是非安全映射的。

AP[2:1] 对应 Bit[7:6], 数据访问权限位:
    AP[1] 即A位,表示该内存允许通过用户权限(EL0)和更高权限的异常等级(EL1)来访问。在Linux内核中使用 PTE_USER 宏表示可以在用户态访问该页表。为1表示可以通过EL0以及更高权限的异常等级访问,为0表示不能通过EL0访问,但是可以通过EL1访问。
    AP[2] 即P位,表示只读权限和可读、可写权限。在Linux内核中使用 PTE_REONLY 宏来表示该位。为1表示只读,为0表示可读可写。

SH[1:0] 对应Bit[9:8], 内存共享属性。在Linux内核中使用 PTE_SHARED 宏来表示该位。00表示没有共享,01表示保留,10表示外部可共享,11表示内部可共享。

AF 对应Bit[10], 访问位。Linux内核中使用 PTE_AF 宏来表示该位,当第一次访问页面时硬件会自动设置这个访问位。

nG 对应 Bit[11], 非全局位。Linux内核中使用 PTE_NG 宏来表示该位。该位用于TLB管理,TLB页表项分为全局的和页进程特有的,当设置该位后表示这个页面对应的TLB页表项是进程特有的。

nT 对应 BIt[16], 块类型的页表项。

DBM 对应 Bit[51], 脏位,Linux内核中使用 PTE_DBM 宏来表示该位, 该位表示页面修改过。

连续页面,对应Bit[52], 表示当前页面处于一个连续的物理页面集合中,可以使用单个TLB页表项进行优化,Linux内核中使用 PTE_CONT 宏来表示该位.

PXN 对应 Bit[53], 表示该页面在特权模式下不能执行。Linux内核中使用 PTE_PXN 宏来表示该位.

XN/UXN 对应 Bit[54], XN表示该页面在任何模式下都不能执行,UXN表示该页面在用户模式下不能执行,Linux内核中使用 PTE_UXN 宏来表示该位.

预留 对应 Bit[58:55], 预留给软件使用,软件可以利用这些预留位来实现某些特殊功能,例如,Linux内核使用这些Bit[55]实现了 PTE_DIRTY、Bit[56]实现了 PTE_SPECIAL、Bit[58]实现了 PTE_PROT_NONE。还剩下一个Bit[57]给OEM使用?

PBHA 对应Bit[62:59], 表示与页面相关的硬件属性。


三、Linux内核中的页表

PGD: 页全局目录,对应L0.
PUD: 页上级目录,对应L1.
PMD: 页中间目录,对应L2.
PTE: 页表,对应L3.


1. 页表配置

//msm-5.4/arch/arm64/Kconfig

/* ---- VA ---- */
prompt "Virtual address space size"
    default ARM64_VA_BITS_39 if ARM64_4K_PAGES
    default ARM64_VA_BITS_47 if ARM64_16K_PAGES
    default ARM64_VA_BITS_42 if ARM64_64K_PAGES

config ARM64_VA_BITS_39
    bool "39-bit"
    depends on ARM64_4K_PAGES

config PGTABLE_LEVELS
    int
    default 2 if ARM64_16K_PAGES && ARM64_VA_BITS_36
    default 2 if ARM64_64K_PAGES && ARM64_VA_BITS_42
    default 3 if ARM64_64K_PAGES && (ARM64_VA_BITS_48 || ARM64_VA_BITS_52)
    default 3 if ARM64_4K_PAGES && ARM64_VA_BITS_39
    default 3 if ARM64_16K_PAGES && ARM64_VA_BITS_47
    default 4 if !ARM64_64K_PAGES && ARM64_VA_BITS_48

/* ---- PA ---- */
choice
    prompt "Physical address space size"
    default ARM64_PA_BITS_48
    help
            Choose the maximum physical address range that the kernel will support.

config ARM64_PA_BITS
    int
    default 48 if ARM64_PA_BITS_48
    default 52 if ARM64_PA_BITS_52

(1) 页面粒度大小不同会导致选取的虚拟地址长度不同。
(2) 虚拟地址长度不同会导致页表级数不同。


嵌入式主流配置:

CONFIG_ARM64_VA_BITS=39
CONFIG_ARM64_VA_BITS_39=y
CONFIG_ARM64_PA_BITS=48
CONFIG_ARM64_PA_BITS_48=y
CONFIG_ARM64_4K_PAGES=y

注:ARM64v8-A架构处理器支持最大48bit物理地址。页面粒度可以是4K、16K、64K, 页表级数可以是2级、3级、4级,但是页表级数与页面粒度和虚拟地址位数有固定对应关系


2. 地址空间

对于4K粒度页面,地址宽度VA=PA=48bit, 4级页表,内存分布:

------------------------------------------------------------
start                  end                    size   use
------------------------------------------------------------
0x0000_0000_0000_0000  0x0000_FFFF_FFFF_FFFF  256TB  user
0xFFFF_0000_0000_0000  0xFFFF_FFFF_FFFF_FFFF  256TB  kernel
------------------------------------------------------------


对于4K粒度页面,地址宽度VA=39bit,PA=48bit, 3级页表,内存分布:

------------------------------------------------------------
start                  end                    size   use
------------------------------------------------------------
0x0000_0000_0000_0000  0x0000_007F_FFFF_FFFF  512GB  user
0xFFFF_0080_0000_0000  0xFFFF_FFFF_FFFF_FFFF  512GB  kernel
------------------------------------------------------------


3. PGD相关宏

//arch/arm64/include/asm/pgalloc.h
#define PGD_SIZE    (PTRS_PER_PGD * sizeof(pgd_t)) //1<<9 * 8 = 2^12 = 4K

//arm64/include/asm/pgtable-hwdef.h
#define PGDIR_SHIFT        ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS) //参数1, 结果是30
#define PGDIR_SIZE        (_AC(1, UL) << PGDIR_SHIFT) //1UL<<30 一个PGD页表项能描述的大小
#define PGDIR_MASK        (~(PGDIR_SIZE-1)) //0xffffffffc0000000, 低30bit全是0,其它bit全是1
#define PTRS_PER_PGD    (1 << (MAX_USER_VA_BITS - PGDIR_SHIFT)) //1<<(39-30)=1<<9=512


4. PUD相关宏

//include/asm-generic/pgtable-nop4d-hack.h
#define PUD_SHIFT    PGDIR_SHIFT //30
#define PTRS_PER_PUD    1
#define PUD_SIZE    (1UL << PUD_SHIFT) //1<<30=0x40000000=1G
#define PUD_MASK    (~(PUD_SIZE-1)) //0xffffffffc0000000, 同 PGDIR_MASK
#define PTRS_PER_PUD  1 //是1

注:3级页表虽然没有PUD页表,但是也定义了相关宏。


5. PMD相关宏

//arm64/include/asm/pgtable-hwdef.h
#define PMD_SHIFT        ARM64_HW_PGTABLE_LEVEL_SHIFT(2) //21
#define PMD_SIZE        (_AC(1, UL) << PMD_SHIFT) //1UL << 21 = 0x200000 = 2M 一个PMD页表项能描述的大小
#define PMD_MASK        (~(PMD_SIZE-1)) //~0x1fffff=0xffffffffffe00000 低21bit全是0,其它bit全是1
#define PTRS_PER_PMD    PTRS_PER_PTE //512


6. PTE相关宏

//arch/arm64/include/asm/pgtable-hwdef.h
#define PTRS_PER_PTE  (1 << (PAGE_SHIFT - 3)) //1<<(12-3) = 1<<9 = 512

/* 这些是与PTE相关的page配置项 */
//arch/arm64/include/asm/page-def.h
#define PAGE_SHIFT        CONFIG_ARM64_PAGE_SHIFT //12
#define PAGE_SIZE        (_AC(1, UL) << PAGE_SHIFT) //1UL<<12=4K
#define PAGE_MASK        (~(PAGE_SIZE-1)) //~(0x1000-1)=0xfffffffffffff000


7. section映射相关宏

//arch/arm64/include/asm/pgtable-hwdef.h
#define SECTION_SHIFT  PMD_SHIFT //21
#define SECTION_SIZE   (_AC(1, UL) << SECTION_SHIFT) //1<<21 也即2MB, 和PMD_SIZE相同
#define SECTION_MASK   (~(SECTION_SIZE-1)) // ~(1<<21 -1) = 0xffffffffffe00000


8. 页表相关数据结构

//arm64/include/asm/pgtable-types.h

typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 pgdval_t;

/* 注释说这样实现有利于C类型检查 */
typedef struct { pteval_t pte; } pte_t;
#define pte_val(x)    ((x).pte)            //宏参数x是pte_t类型的变量
#define __pte(x)    ((pte_t) { (x) } )   //将宏参数x强制转换为 pte_t 类型

typedef struct { pmdval_t pmd; } pmd_t;
#define pmd_val(x)    ((x).pmd)            //宏参数x是pmd_t类型的变量
#define __pmd(x)    ((pmd_t) { (x) } )   //将宏参数x强制转换为 pmd_t 类型

typedef struct { pgdval_t pgd; } pgd_t;
#define pgd_val(x)    ((x).pgd)            //宏参数x是pgd_t类型的变量
#define __pgd(x)    ((pgd_t) { (x) } )   //将宏参数x强制转换为 pgd_t 类型

typedef struct { pteval_t pgprot; } pgprot_t;
#define pgprot_val(x)    ((x).pgprot)          //宏参数x是 pgprot_t 类型的变量
#define __pgprot(x)    ((pgprot_t) { (x) } )     //将宏参数x强制转换为 pgprot_t 类型

//include/asm-generic/pgtable-nop4d-hack.h
typedef struct { pgd_t pgd; } pud_t;  //3级页表没有PUD, 这里是这样定义的


9. PTE页表属性

PTE页表描述符包含了丰富的属性,定义如下:

//arch/arm64/include/asm/pgtable-hwdef.h
/* Level 3 descriptor (PTE). */
#define PTE_VALID        (_AT(pteval_t, 1) << 0)
#define PTE_TYPE_MASK    (_AT(pteval_t, 3) << 0)
#define PTE_TYPE_PAGE    (_AT(pteval_t, 3) << 0)
#define PTE_TABLE_BIT    (_AT(pteval_t, 1) << 1)
#define PTE_USER        (_AT(pteval_t, 1) << 6)        /* AP[1] */
#define PTE_RDONLY        (_AT(pteval_t, 1) << 7)        /* AP[2] */
#define PTE_SHARED        (_AT(pteval_t, 3) << 8)        /* SH[1:0], inner shareable */
#define PTE_AF            (_AT(pteval_t, 1) << 10)    /* Access Flag */
#define PTE_NG            (_AT(pteval_t, 1) << 11)    /* nG */
#define PTE_DBM            (_AT(pteval_t, 1) << 51)    /* Dirty Bit Management */
#define PTE_CONT        (_AT(pteval_t, 1) << 52)    /* Contiguous range */
#define PTE_PXN            (_AT(pteval_t, 1) << 53)    /* Privileged XN */
#define PTE_UXN            (_AT(pteval_t, 1) << 54)    /* User XN */
#define PTE_HYP_XN        (_AT(pteval_t, 1) << 54)    /* HYP XN */

//arch/arm64/include/asm/pgtable-prot.h
/* Software defined PTE bits definition. */
#define PTE_WRITE        (PTE_DBM)                 /* same as DBM (51) */
#define PTE_DIRTY        (_AT(pteval_t, 1) << 55)
#define PTE_SPECIAL        (_AT(pteval_t, 1) << 56)
#define PTE_DEVMAP        (_AT(pteval_t, 1) << 57)
#define PTE_PROT_NONE    (_AT(pteval_t, 1) << 58) /* only when !PTE_VALID */

ARMv8的L3(PTE)页表项的定义中,Bit[58:55]是硬件预留给软件使用的,Linux内核使用这几位来实现 PTE_DIRTY、PTE_SPECIAL、PTE_DEVMAP、PTE_PROT_NONE。

注:在ARMv8架构的页表项中 PTE_DBM 位用于表示该页面是脏页,而在Linux内核中额外实现一个软件标志 PTE_DIRTY 来表示页面是脏的,它使用的是第55位。


10. PTE属性查询

//arch/arm64/include/asm/pgtable.h 
/* The following only work if pte_present(). Undefined behaviour otherwise. */
#define pte_present(pte)    (!!(pte_val(pte) & (PTE_VALID | PTE_PROT_NONE)))
#define pte_young(pte)        (!!(pte_val(pte) & PTE_AF))
#define pte_special(pte)    (!!(pte_val(pte) & PTE_SPECIAL))
#define pte_write(pte)        (!!(pte_val(pte) & PTE_WRITE))
#define pte_user_exec(pte)    (!(pte_val(pte) & PTE_UXN))
#define pte_cont(pte)        (!!(pte_val(pte) & PTE_CONT))
#define pte_devmap(pte)        (!!(pte_val(pte) & PTE_DEVMAP))

#define pte_hw_dirty(pte)    (pte_write(pte) && !(pte_val(pte) & PTE_RDONLY))
#define pte_sw_dirty(pte)    (!!(pte_val(pte) & PTE_DIRTY))
#define pte_dirty(pte)        (pte_sw_dirty(pte) || pte_hw_dirty(pte))

#define pte_valid(pte)        (!!(pte_val(pte) & PTE_VALID))
#define pte_valid_not_user(pte) ((pte_val(pte) & (PTE_VALID | PTE_USER)) == PTE_VALID)
#define pte_valid_user(pte) ((pte_val(pte) & (PTE_VALID | PTE_USER)) == (PTE_VALID | PTE_USER))

#define pte_accessible(mm, pte)    (mm_tlb_flush_pending(mm) ? pte_present(pte) : pte_valid(pte))

#define pte_access_permitted(pte, write) (pte_valid_user(pte) && (!(write) || pte_write(pte)))
#define pmd_access_permitted(pmd, write) (pte_access_permitted(pmd_pte(pmd), (write)))
#define pud_access_permitted(pud, write) (pte_access_permitted(pud_pte(pud), (write)))

各个宏作用说明:

pte_present(): 判断该页是否在内存中。若不存在说明这个页表项对应的物理页面没有在内存中。是通过判断 PTE_VALID 和 PTE_PROT_NONE 位实现的。

pte_young(): 判断该页是否被访问过。是通过判断 AF 位实现的。

pte_write(): 判断该页是否具有可写属性。是通过判断 DBM 位实现的。

pte_user_exec(): 判断该页面是否属于用户态映射的页面。

pte_dirty(): 判断该页是否被写入过。是通过判断软件脏 PTE_DIRTY 位,或硬件脏 PTE_DBM 实现的。

pte_hw_dirty(): 判断该页是否被硬件设置为脏页。是通过判断 PTE_DBM 位实现的。

pte_sw_dirty(): 判断该页是否被软件设置为脏页。是通过判断 PTE_DIRTY 位实现的。

pte_valid_not_user(): 表示该页面不能在用户态访问,即该页面属于内核态。

TODO: 其它每项的解释。


11. PTE页表标志设置

设置页表项标志位的函数:

clear_pte_bit() //清除页表项的标志位。
set_pte_bit() //设置页表项的标志位。
pte_wrprotect() //设置写保护。清除 PTE_WRITE 并设置 PTE_RDONLY.
pte_mkwrite() //使能可写属性。设置 PTE_WRITE 并清除 PTE_RDONLY.
pte_mkclean() //清除 PTE_DIRTY 并设置 PTE_RDONLY.
pte_mkdirty() //设置 PTE_DIRTY,若具有可写属性,则清除 PTE_RDONLY.
pte_mkold() //清除页访问标志 PTE_AF. 把此页标记为未访问。
pte_mkyoung() //设置页访问标志 PTE_AF, 把此页标记为已访问过。
pte_mkspecial() //设置 PTE_SPECIAL 标志位,标记该页表项是特殊页表项,这是软件定义的。
pte_mkcont() //设置连续页标记位 PTE_CONT.
pte_mknoncont() //清除连续页标记位 PTE_CONT.
pte_mkpresent() //设置该页在内存中,设置 PTE_VALID 标志位。

当需要把PTE页表项写入硬件页表时,可以使用 set_pte_at(), 它最终是调用 set_pte() 来完成的。

static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t pte)
{
    /* pte_user_exec表示该页面属于用户态映射的页面 */
    if (pte_present(pte) && pte_user_exec(pte) && !pte_special(pte))
        /* 高速缓存的一致性操作 */
        __sync_icache_dcache(pte);

    __check_racy_pte_update(mm, ptep, pte);

    set_pte(ptep, pte);
}

static inline void set_pte(pte_t *ptep, pte_t pte)
{
    WRITE_ONCE(*ptep, pte);

    /* 表示该页面不能在用户态访问,即该页面属于内核态 */
    if (pte_valid_not_user(pte)) {
        /* 写入后要使用dsb保证页表更新完成 */
        dsb(ishst);
        isb();
    }
}


12. 各页表操作相关宏函数

//mm/pgd.c
pgd_alloc(mm) //分配一个全新的PGD页表,就是分配一个物理页。通常进程创建时会使用该函数来创建进程的PGD页表。
pgd_free(mm, pgd) //释放PGD页表。

//arch/arm64/include/asm/pgtable.h
pgd_offset_k(addr) //查找地址在内核全局页表(swapper_pg_dir)PGD中对应的页表项。
pgd_offset(mm, addr) //查找地址在进程 mm->pgd 页表的PGD中对应的页表项。
pgd_index(addr) //返回addr在PGD页表中的偏移

//include/asm-generic/pgtable-nop4d-hack.h
pgd_page(pgd) //返回pgd所在物理页面对应的page数据结构
pud_offset(dir, addr) //3级页表直接返回参数dir.
pud_index(addr) //3级页表没有这个宏
pud_page(pud) ///返回pud所在物理页面对应的page数据结构

//arch/arm64/include/asm/pgtable.h
pmd_offset(dir, addr) //返回dir对应的PMD页表项的虚拟地址。
pmd_index(addr) //返回addr在PMD页表项中的偏移
pmd_page(pmd) //返回PMD所在物理页的page数据结构

pte_offset_kernel(dir,addr) //返回addr对应的PTE页表项的虚拟地址
pte_offset_map(dir,addr) //和上面pte_offset_kernel是同一个宏

mk_pte(page,prot) //将page结构转换为pfn, 然后再组合成一个PTE页表项的内容。
pte_index(addr) //返回addr在PTE页表项中的偏移

//include/linux/mm.h
pte_offset_map_lock(mm, pmd, address, ptlp) //在pte_offset_map的基础上申请page数据结构中ptlp自旋锁的保护。
pte_unmap_unlock(pte, ptl) //先释放ptl自旋锁,然后再pte_unmap(), 后者是个空实现。
pte_alloc(mm, pmd) //当pmd为空时使用新分配的PTE来设置这PMD页表项。

注:这里的 mm 表示进程的内存描述符 mm_struct, addr 表示虚拟地址,pgd/pmd/pud/pte分别表示PGD/PUD/PMD/PTE页表项,prot表示页表属性,page 表示 struct page 指针,dir 表示上一级页表对应的页表项。 


四、页表切换

内核页表:存放在 init_mm.pgd = swapper_pg_dir,硬件并不直接使用。但是在 setup_arch --> paging_init --> cpu_replace_ttbr1(lm_alias(swapper_pg_dir)); init_mm.pgd = swapper_pg_dir; 之前,PGD一直就是 init_pg_dir。

进程页表 :每个进程自己的页表,放在进程自身的页目录 task_struct.mm.pgd 中。

内核页表中为所有进程共享,而进程页表是每个进程独有的。内核页表由内核自己维护更新,在vmalloc区发生page fault时,将内核页表同步到进程页表中。

do_fork() -> copy_process() -> copy_mm() 如果是fork一个内核线程的话,会直接使用当前普通线程的页表集,内核线程并不拥有自己的页表集。


1. 进程页表切换

进程调度而进行上下文切换时,会进行页表的切换,调用路径:

__schedule //core.c
    context_switch //core.c
        switch_mm_irqs_off //include/linux/mmu_context.h
            switch_mm //arm64/include/asm/mmu_context.h
                __switch_mm //arm64/include/asm/mmu_context.h
                    check_and_switch_context //context.c
                        cpu_switch_mm //arm64/include/asm/mmu_context.h
                            cpu_do_switch_mm //arm64/mm/proc.S

 

五、内核线程页表

内核线程与用户进程的关键区别在于:

(1) 无独立用户空间:内核线程没有用户态地址空间,其 mm_struct 为 NULL,直接使用内核共享的虚拟地址空间。
(2) 共享内核映射:所有内核线程共享同一份内核虚拟地址空间,包括内核代码段、数据段、页表结构等。

在Arm64 Linux内核中,所有内核线程的内核态页表部分完全相同,但它们的PGD(页全局目录)并非物理上的同一个结构。

当内核线程调度执行时,硬件的 TTBR0 寄存器(用户态页表基址)会被清零,仅使用 TTBR1 寄存器(内核态页表基址)指向全局的内核页表根目录 swapper_pg_dir。

TTBR0寄存器:每个用户进程拥有独立的用户态页表基址,切换进程时仅需更新 TTBR0,而 TTBR1 保持不变

TTBR1寄存器:固定映射 swapper_pg_dir 的物理地址,所有进程(包括内核线程)共享这部分内核态页表。

 

六、补充

1. 页表bit位补充解释

补充(主要参考: https://zhuanlan.zhihu.com/p/67053210):

P(Present) - 为1表明该page存在于当前物理内存中,为0则PTE的其他部分都失去意义了,不用看了,直接触发page fault。P位为0的PTE也不会有对应的TLB entry,因为早在P位由1变为0的时候,对应的TLB就已经被flush掉了。

G (Global)- 这个标志位在这篇文章中有介绍,主要是用于context switch的时候不用flush掉kernel对应的TLB,所以这个标志位在TLB entry中也是存在的。//https://zhuanlan.zhihu.com/p/66971714

A(Access) - 当这个page被访问(读/写)过后,硬件将该位置1,TLB只会缓存access的值为1的page对应的映射关系。软件可将该位置0,然后对应的TLB将会被flush掉。这样,软件可以统计出每个page被访问的次数,作为内存不足时,判断该page是否应该被回收的参考。

D (Dirty)- 这个标志位只对file backed的page有意义,对anonymous的page是没有意义的。当page被写入后,硬件将该位置1,表明该page的内容比外部disk/flash对应部分要新,当系统内存不足,要将该page回收的时候,需首先将其内容flush到外部存储。之后软件将该标志位清0。


R/W和U/S属于权限控制类:

R/W(Read/Write) - 置为1表示该page是writable的,置为0则是readonly,对只读的page进行写操作会触发page fault。

U/S(User/Supervisor) - 置为0表示只有supervisor(比如操作系统中的kernel)才可访问该page,置为1表示user也可以访问。


PCD和PWT和cache属性相关:

PCD(Page Cache Disabled)- 置为1表示disable,即该page中的内容是不可以被cache的。如果置为0(enable),还要看CR0寄存器中的CD位这个总控开关是否也是0。

PWT (Page Write Through)- 置为1表示该page对应的cache部分采用write through的方式,否则采用write back。

 

posted on 2024-07-17 10:36  Hello-World3  阅读(12)  评论(0)    收藏  举报

导航