主頁 > 作業系統 > ARM32 頁表映射

ARM32 頁表映射

2020-09-11 08:36:13 作業系統

在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版本的頁表項內容寫入硬體頁表,從而實作模擬程序;

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/4947.html

標籤:嵌入式

上一篇:痞子衡嵌入式:揭秘i.MXRT1170 eFuse空間訪問可靠性的保護策略(冗余與ECC)

下一篇:【原創】xenomai內核決議之嵌入式實時linux概述

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more