ARM32 頁表映射

来源:https://www.cnblogs.com/linhaostudy/archive/2020/05/03/12821206.html
-Advertisement-
Play Games

在32bit中的Linux內核中一般採用3層映射模型,第1層是頁面目錄(PGD),第2層是頁面中間目錄(PMD),第3層才是頁面映射表(PTE)。 但在ARM32系統中只用到兩層映射,因此在實際代碼中就要3層映射模型中合併一層 。在ARM32架構中,可以按段(section)來映射,這時採用單層映射 ...


在32bit中的Linux內核中一般採用3層映射模型,第1層是頁面目錄(PGD),第2層是頁面中間目錄(PMD),第3層才是頁面映射表(PTE)。但在ARM32系統中只用到兩層映射,因此在實際代碼中就要3層映射模型中合併一層。在ARM32架構中,可以按段(section)來映射,這時採用單層映射模式。使用頁面映射需要兩層映射結構,頁面的選擇可以是64KB的大頁面或4KB的小頁面,如圖2.4所示。Linux內核通常使用4KB大小的小頁面。

如果採用單層的段映射,記憶體中有一個段映射表,表中有4096個表項,每個表項的大小是4Byte,所以這個段映射表的大小是16KB,而且其位置必須與16KB邊界對齊。每個段表項可以定址1MB大小的地址空間。當CPU訪問記憶體時,32位虛擬地址的高12位(bit[31:20])用作訪問段映射表的索引,從表中找到相應的表項。每個表項提供了一個12位的物理段地址,以及相應的標誌位,如可讀、可寫等標誌位。將這個12bit的物理地址和虛擬地址的低20bit拼湊在一起,就得到32bit的物理地址;

如果使用頁面映射的方式,段映射表就變成了一級映射表(First Level table,在linux內核中成為PGD),其表項提供的不再是物理段地址,而是二級頁表的基地址。32bit虛擬地址的高12bit(bit[31:20])作為訪問一級頁表的索引值,找到相應的表項,每個表項指向一個二級頁表。以虛擬地址的次8bit(bit[19:12])作為訪問二級目錄的索引值,得到相應的頁表項,從這個頁表項找到20位的物理頁面地址。最後將這20bit的物理頁面地址和虛擬地址的低12bit拼湊起來,得到最終的32bit物理地址。這個過程在ARM32架構中由MMU硬體完成,軟體不需要介入;

[arch/arm/include/asm/pgtable-21level.h]
#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))

ARM32架構中一級頁表PGD的偏移量應該從20bit開始,為何這裡的頭文件定義從21bit開始呢?

我們從ARM linux內核建立具體記憶體區間的頁表映射過程中來看頁表映射是如何實現的。crate_mapping()函數就是為一個給定記憶體區間建立頁表映射,這個函數使用map_desc數據結構來描述一個記憶體區間。

struct map_desc {
	unsigned long virtual;	//虛擬地址的起始地址
	unsigned long pfn;		//物理地址的開始地址的頁幀號
	unsigned long length;	//記憶體區間大小
	unsigned int type;		
}

其中,virtual表示這個區間的虛擬地址起始點,pfn表示起始物理地址的頁幀號,length表示記憶體區間的長度,type表示記憶體區間的屬性,通常有個struct mem_type[]數組來描述記憶體屬性。struct mem_type數據結構描述記憶體區間類型以及相應的許可權和屬性等信息,其數據結構定義如下:

struct mem_type {
	pteval_t prot_pte;
	pteval_t prot_pte_s2;
	pmdval_t prot_l1;
	pmdval_t prot_sect;
	unsigned int domain;
};

其中,domain成員用於ARM中定義的不同域,ARM中允許使用16個不同域,但在ARM Linux只定義和使用3個;

#define DOMAIN_KERNEL	2
#define DOMAIN_TABLE	2
#define DOMAIN_USER	1
#define DOMAIN_IO	0

DOMAIN_KERNEL和DOMAIN_TABLE其實用於系統空間,DOMAIN_IO用於I/O地址域,實際上也屬於系統空間,DOMAIN_USER則是用戶空間;

prot_pte成員用於頁面表項的控制位和標誌位,具體定義在:

#define L_PTE_VALID	(_AT(pteval_t, 1) << 0)
#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)
#define L_PTE_NONE	(_AT(pteval_t, 1) << 11)
#define PROT_PTE_DEVICE	L_PTE_PRESENT|L_PTE_YOUNG|L_PTE_DIRTY|L_PTE_XN
#define PROT_PTE_S2_DEVICE	PROT_PTE_DEVICE
#define PROT_SECT_DEVICE	PMD_TYPE_SECT|PMD_SECT_AP_WRITE

prot__l1用於一級頁表項的控制位和標誌位,具體定義如下:

/*
 * Hardware page table definitions.
 *
 * + Level 1 descriptor (PMD)
 *   - common
 */
#define PMD_TYPE_MASK		(_AT(pmdval_t, 3) << 0)
#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 */

系統中定義了一個全局的mem_type[]數組來描述所有的記憶體區間類型。例如MT_DEVICE_CACHED、MT_DEVICE_WC、MT_MEMORY_RWX和MT_MEMORY_RW類型的記憶體區間的定義如下:

static struct mem_type mem_types[] = {
	[MT_DEVICE] = {		  /* Strongly ordered / ARMv6 shared device */
		.prot_pte	= PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |
				  L_PTE_SHARED,
		.prot_pte_s2	= s2_policy(PROT_PTE_S2_DEVICE) |
				  s2_policy(L_PTE_S2_MT_DEV_SHARED) |
				  L_PTE_SHARED,
		.prot_l1	= PMD_TYPE_TABLE,
		.prot_sect	= PROT_SECT_DEVICE | PMD_SECT_S,
		.domain		= DOMAIN_IO,
	},
	[MT_DEVICE_NONSHARED] = { /* ARMv6 non-shared device */
		.prot_pte	= PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED,
		.prot_l1	= PMD_TYPE_TABLE,
		.prot_sect	= PROT_SECT_DEVICE,
		.domain		= DOMAIN_IO,
	},
	[MT_DEVICE_CACHED] = {	  /* ioremap_cached */
		.prot_pte	= PROT_PTE_DEVICE | L_PTE_MT_DEV_CACHED,
		.prot_l1	= PMD_TYPE_TABLE,
		.prot_sect	= PROT_SECT_DEVICE | PMD_SECT_WB,
		.domain		= DOMAIN_IO,
	},
	[MT_DEVICE_WC] = {	/* ioremap_wc */
		.prot_pte	= PROT_PTE_DEVICE | L_PTE_MT_DEV_WC,
		.prot_l1	= PMD_TYPE_TABLE,
		.prot_sect	= PROT_SECT_DEVICE,
		.domain		= DOMAIN_IO,
	},
	[MT_UNCACHED] = {
		.prot_pte	= PROT_PTE_DEVICE,
		.prot_l1	= PMD_TYPE_TABLE,
		.prot_sect	= PMD_TYPE_SECT | PMD_SECT_XN,
		.domain		= DOMAIN_IO,
	},
	[MT_CACHECLEAN] = {
		.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN,
		.domain    = DOMAIN_KERNEL,
	},
#ifndef CONFIG_ARM_LPAE
	[MT_MINICLEAN] = {
		.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN | PMD_SECT_MINICACHE,
		.domain    = DOMAIN_KERNEL,
	},
#endif
	[MT_LOW_VECTORS] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
				L_PTE_RDONLY,
		.prot_l1   = PMD_TYPE_TABLE,
		.domain    = DOMAIN_USER,
	},
	[MT_HIGH_VECTORS] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
				L_PTE_USER | L_PTE_RDONLY,
		.prot_l1   = PMD_TYPE_TABLE,
		.domain    = DOMAIN_USER,
	},
	[MT_MEMORY_RWX] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY,
		.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,
	},
	[MT_ROM] = {
		.prot_sect = PMD_TYPE_SECT,
		.domain    = DOMAIN_KERNEL,
	},
	[MT_MEMORY_RWX_NONCACHED] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
				L_PTE_MT_BUFFERABLE,
		.prot_l1   = PMD_TYPE_TABLE,
		.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
		.domain    = DOMAIN_KERNEL,
	},
	[MT_MEMORY_RW_DTCM] = {
		.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_XN,
		.domain    = DOMAIN_KERNEL,
	},
	[MT_MEMORY_RWX_ITCM] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY,
		.prot_l1   = PMD_TYPE_TABLE,
		.domain    = DOMAIN_KERNEL,
	},
	[MT_MEMORY_RW_SO] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
				L_PTE_MT_UNCACHED | L_PTE_XN,
		.prot_l1   = PMD_TYPE_TABLE,
		.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_S |
				PMD_SECT_UNCACHED | PMD_SECT_XN,
		.domain    = DOMAIN_KERNEL,
	},
	[MT_MEMORY_DMA_READY] = {
		.prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
				L_PTE_XN,
		.prot_l1   = PMD_TYPE_TABLE,
		.domain    = DOMAIN_KERNEL,
	},
};

這樣一個map_desc數據結構就完整描述了一個記憶體區間,調用create_mapping()時以此數據結構指針為調用參數;

start_kernel()	-->
	setup_arch()	-->
		paging_init()	-->
			map_lowmem()	-->
				create_mapping()
/*
 * Create the page directory entries and any necessary
 * page tables for the mapping specified by `md'.  We
 * are able to cope here with varying sizes and address
 * offsets, and we take full advantage of sections and
 * supersections.
 */
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;

	if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
		pr_warn("BUG: not creating mapping for 0x%08llx at 0x%08lx in user region\n",
			(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
		return;
	}

	if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
	    md->virtual >= PAGE_OFFSET &&
	    (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
		pr_warn("BUG: mapping for 0x%08llx at 0x%08lx out of vmalloc space\n",
			(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
	}
	
	type = &mem_types[md->type];

#ifndef CONFIG_ARM_LPAE
	/*
	 * Catch 36-bit addresses
	 */
	if (md->pfn >= 0x100000) {
		create_36bit_mapping(md, type);
		return;
	}
#endif

	addr = md->virtual & PAGE_MASK;
	phys = __pfn_to_phys(md->pfn);
	length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));

	if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
		pr_warn("BUG: map for 0x%08llx at 0x%08lx can not be mapped using pages, ignoring.\n",
			(long long)__pfn_to_phys(md->pfn), addr);
		return;
	}

	pgd = pgd_offset_k(addr);
	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);
}

這個函數的入參是一個結構體為map_desc叫 md 的東東,這個玩意用來表徵一個映射關係的結構;

在create_mapping()函數中,以PGDIR_SIZE為單位,在記憶體區域[virtual, virtual+length]中通過調用alloc_init_pud()來初始化PGD頁表項內容和下一級頁表PUD。pgd_addr_end()以PGDIR_SIZE為步長;

在第7行代碼中,通過md->type來獲取描述記憶體區域屬性的mem_type數據結構,這裡只需要通過查表的方式來獲取mem_type數據結構里的具體內容;

在第13行代碼中,通過pgd_offset_k()函數獲取所屬的頁面目錄項PGD。內核頁表存放在

swapper_pg_dir地址中,可以通過init_mm數據結構來獲取;

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)
};

內核頁表的基地址定義在arch/arm/kernel/head.S彙編代碼中。

[arch/arm/kernel/head.S]
#define KERNEL_RAM_VADDR	(PAGE_OFFSET+TEXT_OFFSET)
#define PG_DIR_SIZE	0x4000
.globl	swapper_pg_dir
.equ	swapper_pg_dir,	KERNEL_RAM_VADDR - PG_DIR_SIZE
[arch/arm/Makefile]
textofs-y	:= 0x00008000
TEXT_OFFSET	:= $(textofs-y)

從上面的代碼中,可以推算出頁表的基地址是0xc0004000;

pgd_offset_k()巨集可以從init_mm數據結構所指定的頁面目錄中找到地址addr所屬的頁面目錄項指針pgd。首先通過init_mm結構體得到頁表的基地址,然後通過addr右移PGDIR_SHIFT得到pgd的索引值,最後在一級頁表中找到頁表項pgd指針。pgd_offset_k()巨集定義如下:

#define PGDIR_SHIFT	21
#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)

create_mapping()函數中的第15-22行代碼,由於ARM Vexpress平臺支持兩級頁表映射,所以PUD和PMD設置成與PGD等同了。

#define pud_offset(dir,addr) \
	((pud_t *) pgd_page_vaddr(*(dir)) + (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1)))
#endif

/* Find an entry in the third-level page table.. */
#define pmd_offset(dir,addr) \
	((pmd_t *) pud_page_vaddr(*(dir)) + (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1)))

因此,alloc_init_pud()函數一路調用到alloc_init_pte()函數。

static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
				  unsigned long end, unsigned long pfn,
				  const struct mem_type *type)
{
	pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
	do {
		set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
		pfn++;
	} while (pte++, addr += PAGE_SIZE, addr != end);
}

alloc_init_pte()首先判斷相應的PTE頁表項是否已經存在,如果不存在,那就要新建PTE頁表項。接下來的while迴圈是根據物理地址的pfn頁幀號來生成新的PTE表項(PTE entry),最後設置到ARM硬體頁表中。

create_mapping ->
	alloc_init_pud	->
		alloc_init_pmd	->
			alloc_init_pte	->
				early_pte_alloc
static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
{
	if (pmd_none(*pmd)) {
		pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
		__pmd_populate(pmd, __pa(pte), prot);
	}
	BUG_ON(pmd_bad(*pmd));
	return pte_offset_kernel(pmd, addr);
}

pmd_none()檢查這個參數的對應的PMD表項的內容,如果為0,說明這個頁表PTE還沒建立,所以要先去建立頁面表。這裡會去分配(PTE_HWTABLE_OFF+PTE_HWTABLE_SIZE)個PTE頁面表項,即會分配512+512個PTE頁表項。但是在ARM32架構中,二級頁表也只有256個頁面表項,為何要分配那麼多呢?

#define PTRS_PER_PTE	512
#define PTRS_PER_PMD	1
#define PTRS_PER_PGD	2048
#define PTE_HWTABLE_PTRS	(PTRS_PER_PTE)
#define PTE_HWTABLE_OFF	(PTE_HWTABLE_PTRS * sizoef(pte_t))
#define PTE_HWTABLE_SIZE	(PTRS_PER_PTE * sizoof(u32))

先回答剛纔的問題:ARM架構中一級頁表的PGD的偏移量應該從20位開始,為何這裡的頭文件定義從21位開始呢?

  • 這裡分配了兩個PTRS_PER_PTE(512)個頁面表項,也就是分配了兩份頁面表項。因為Linux內核預設的PGD從21位開始,也就是bit[31:21],一共2048個一級頁表項;而ARM32硬體結構上,PGD是從20bit開始的,頁表項為4096個,比Linux內核多一倍,那麼代碼實現上取巧了,以PTE_HWTABLE_OFF為偏移來寫PGD表項。而在真實硬體中,一個PGD頁表項,只有256個PTE,也就是說,前512個PTE頁表項是給OS用的(也就是Linux內核用的頁表,可以用於模擬L_PTE_DIRTY、L_PTE_YOUNG等標誌位),後512個頁面表是給ARM硬體MMU使用的;

  • 一次映射兩個相鄰的一級頁表項,也就是對應的兩個相鄰的二級頁表都存放在一個page中;

    然後把這個PTE頁面表的基地址通過__pmd_populate()函數設置到PMD頁表項中;

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] = __pmd(pmdval);
#ifndef CONFIG_ARM_LPAE
	pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
#endif
	flush_pmd_entry(pmdp);
}

註意這裡是把剛分配的1024個PTE頁面表中的第512個頁表項的地址作為基地址,再加上一些標誌位信息prot作為頁表項內容,寫入上一級頁表項PMD中。

相鄰的兩個二級頁表的基地址分別寫入PMD的頁表項中的pmdp[0]和pmdp[1]指針中。

typedef struct (pmdval_t pgd(2))
/* 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))

PGD的定義其實是pmdval_t pgd[2],長度是兩倍,也就是pgd包括兩份相鄰的PTE頁表。所以pgd_offset()在查找pgd表項時,是按照pgd[2]長度來進行計算的,因此查找相應的pgd表項時,其中pgd[0]指向第一份PTE頁表,pgd[1]指向第二份PTE頁表;

pte_offset_kernel()函數返回相應的PTE頁表表項,然後通過__pgprot()和pfn組成PTE entry,最後由set_pte_ext()完成對硬體頁表項的設置;

static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
				  unsigned long end, unsigned long pfn,
				  const struct mem_type *type)
{
	pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
	do {
		set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
		pfn++;
	} while (pte++, addr += PAGE_SIZE, addr != end);
}

set_pte_ext()對於不同的CPU有不同的實現。對於基於ARMv7-A架構的處理器,例如Contex-A9,它的實現是在彙編函數cpu_v7_set_pte_ext中:

ENTRY(cpu_v7_set_pte_ext)
#ifdef CONFIG_MMU
	str	r1, [r0]			@ linux version

	bic	r3, r1, #0x000003f0
	bic	r3, r3, #PTE_TYPE_MASK
	orr	r3, r3, r2
	orr	r3, r3, #PTE_EXT_AP0 | 2

	tst	r1, #1 << 4
	orrne	r3, r3, #PTE_EXT_TEX(1)

	eor	r1, r1, #L_PTE_DIRTY
	tst	r1, #L_PTE_RDONLY | L_PTE_DIRTY
	orrne	r3, r3, #PTE_EXT_APX

	tst	r1, #L_PTE_USER
	orrne	r3, r3, #PTE_EXT_AP1

	tst	r1, #L_PTE_XN
	orrne	r3, r3, #PTE_EXT_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]! )
 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)

cpu_v7_set_pte_ext()函數參數r0表示PTE entry頁表項的指針,註意ARM Linux中實現了兩份頁表,硬體頁表的地址r0+2048。因此r0指Linux版本的頁面表地址,r1表示要寫入的Linux版本的PTE頁面表項內容,這裡指Linux版本的頁面表項內容,而非硬體版本的頁面表項內容。該函數的主要目的是根據Linux版本的頁面表項內容來填充ARM硬體版本的頁表項;

首先把linux內核版本的頁表項內容寫入linux版本的頁表中,然後根據mem_type數據結構prot_pte的標誌位來設置ARMV7-A硬體相關標誌位。prot_pte的標誌位是linux內核中採用的,定義在arch/arm/include/asm/pgtable-2level.h頭文件。而硬體相關的標誌位定義在arch/arm/include/asm/pgtable-2level-hwdef.h頭文件。這兩個標誌位對應的偏移是不一樣的,所以不同架構下的處理器需要單獨處理。ARM32架構硬體PTE頁面表定義的標誌位如下:

/*
 *   - 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 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)

第9~10行代碼設置ARM硬體頁表的PTE_EXT_TEX比特位;

第13~15行代碼設置ARM硬體頁表的PTE_EXT_APX比特位;

第17~18行代碼設置ARM硬體頁表的PTE_EXT_AP1比特位;

第20~21行代碼設置ARM硬體頁表的PTE_EXT_XN比特位;

第23~27行代碼,在舊版本中的linux內核代碼中(例如linux3.7),等同如下代碼片段:

tst r1, #L_PTE_YOUNG
tstne r1, #L_PTE_PRESENT
moveq r3, #0

如果沒有設置L_PTE_YOUNG並且L_PTE_PRESENT置位,那就保持Linux版本的頁表不變,把ARM32硬體版本的頁表表項內容清零。代碼中的L_PTE_VAILD和L_PTE_NONE這兩個軟體比特位是後來添加的,因此在linux3.7及以前內核版本中更加容易理解;

為什麼這裡要把ARM硬體版本的頁面表項內容清零呢?我們觀察ARM32硬體版本的頁面表的相關標誌位會發現,沒有表示被訪問和頁面在記憶體中的硬體標誌位。linux內核最早基於x86體繫結構設計的,所以linux內核關於頁表的許多術語和設計都是針對x86體系的,而ARM Linux只能從軟體架構上去跟隨了,因此設計了兩套頁表。在x86的頁表中有3個標誌位是ARM32硬體頁面表沒有提供的。

  • PTE_DIRTY: CPU是寫操作時會設置該標誌位,表示該頁表被寫過,為臟頁;
  • PTE_YOUNG: CPU訪問該頁時會設置該標誌位,在頁面換出的時候,如果該標誌位置位了,說明該頁剛被訪問過,頁面是young的,不適合把該頁換出,同事清除該標誌位;
  • PTE_PRESENT:表示該頁在記憶體中;

因此在ARM Linux中實現中需要模擬上述3個比特位;

如何模擬PTE_DIRTY呢?在ARM MMU硬體中為一個乾凈頁面建立映射時,設置硬體頁表項是只讀許可權的。當往一個乾凈頁表寫入時,會觸發寫許可權缺頁中斷(雖然linux版本的頁面表項標記了可寫許可權,但是ARM硬體頁面表項還不具有寫入許可權),那麼在缺頁中斷處理handle_pte_falut()會在該頁的linux版本中PTE頁面表項標記為"dirty",並且發現PTE頁表項內容改變了,ptep_set_access_flags()函數會把新的linux版本的頁表項內容寫入硬體頁表,從而實現模擬過程;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 面試題03. 數組中重覆的數字 題目 找出數組中重覆的數字。 ...
  • Session 1. 概念:伺服器端會話技術,在一次會話的多次請求間共用數據,將數據保存在伺服器端對象中。HttpSession 2. 快速入門 1. 獲取HttpSession對象 2. HttpSession對象: object getAttribute(String name) void se ...
  • 前幾天,世界著名的科技期刊/圖書出版公司施普林格(Springer)宣佈: 免費向公眾開放 400 多本正版的電子書!! Springer 即施普林格出版社,於1842 年在德國柏林創立,20 世紀60年代建立了其國際性科技出版公司的地位。 目前,施普林格是 全球第一大科技圖書出版公司和第二大科技期 ...
  • 打造更好用的 EF 自動審計 Intro 上次基於 EF Core 實現了一個自動審計的功能,詳細可以參考 ,雖然說多數情況下可以適用,但是因為要顯式繼承於一個 或 ,所以對代碼的侵入性比較強,對於已經無法修改的代碼或者已經繼承於某一個類了,就無法再繼承 了,就沒有辦法實現自動審計了,在 1.7.0 ...
  • 大家在平時開發中大多都會遵循介面編程,這樣就可以方便實現依賴註入也方便實現多態等各種小技巧,但這種是以犧牲性能為代價換取代碼的靈活性,萬物皆有陰陽,看你的應用場景進行取捨。 一:背景 1. 緣由 在項目的性能改造中,發現很多方法簽名的返回值都是採用IEnumerable介面,比如下麵這段代碼: 2. ...
  • 文件查找 小編在學這堂課的前一天夜裡打嗝打了半宿,第二天上課的時候迷迷糊糊,所以,導致文件查找這章我放了好久的鴿子。 這裡是回看視頻摘出來的筆記。如有理解有偏差,請留言。 本篇只有3個命令,重點是find,時間有限的朋友可以只看find命令。這太過重要了。 whereis命令 在一些特定的目錄搜索, ...
  • 知識點:Python庫及簡單定時器的使用 1. 滑鼠自動點擊屏幕代碼 (1). 首先 pip install pymouse (2). 運行代碼出現:ModuleNotFoundError: No module named ‘windows’ 原因:缺少pyuserinput工具 解決:pip in ...
  • Linux系統作為一個GPOS(通用操作系統)發展至今已經非常成熟可靠了,並且由於遵循GPL協議,開放所有系統源代碼,非常易於裁剪。更重要的是,與其他開源的GPOS或RTOS相比,Linux系統支持多種處理器、開發板,提供多種軟體開發工具,同時Linux系統對網路和圖形界面的支持非常出色。顯然,選擇 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...