趣談Linux操做系統學習筆記:第二十六講

1、內核頁表

和用戶態頁表不一樣,在系統初始化的時候,咱們就要建立內核頁表了node

咱們從內核頁表的根swapper_pg_dir開始找線索,在linux-5.1.3/arch/x86/include/asm/pgtable_64.h中就能找到它的定義linux

extern pud_t level3_kernel_pgt[512];
extern pud_t level3_ident_pgt[512];
extern pmd_t level2_kernel_pgt[512];
extern pmd_t level2_fixmap_pgt[512];
extern pmd_t level2_ident_pgt[512];
extern pte_t level1_fixmap_pgt[512];
extern pgd_t init_top_pgt[];


#define swapper_pg_dir init_top_pgt

一、swapper_pg_dir指向內核最頂級的目錄pgd,同時出現的還有幾個頁表目錄。咱們能夠回憶一下,64位系統的虛擬地址空間的佈局數據結構

一、期中其中 XXX_ident_pgt 對應的是直接映射區app

二、XXX_kernel_pgt 對應的是內核代碼區ide

三、XXX_fixmap_pgt 對應的是固定映射區函數

二、它們是在哪裏初始化的呢?佈局

在彙編語言的文件裏面的linux-5.1.3/arch/x86/kernel/head_64.S,這段代碼比較難看懂,你只要明白它是幹什麼的就好了this

__INITDATA


NEXT_PAGE(init_top_pgt)
	.quad   level3_ident_pgt - __START_KERNEL_map + _KERNPG_TABLE
	.org    init_top_pgt + PGD_PAGE_OFFSET*8, 0
	.quad   level3_ident_pgt - __START_KERNEL_map + _KERNPG_TABLE
	.org    init_top_pgt + PGD_START_KERNEL*8, 0
	/* (2^48-(2*1024*1024*1024))/(2^39) = 511 */
	.quad   level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE


NEXT_PAGE(level3_ident_pgt)
	.quad	level2_ident_pgt - __START_KERNEL_map + _KERNPG_TABLE
	.fill	511, 8, 0
NEXT_PAGE(level2_ident_pgt)
	/* Since I easily can, map the first 1G.
	 * Don't set NX because code runs from these pages.
	 */
	PMDS(0, __PAGE_KERNEL_IDENT_LARGE_EXEC, PTRS_PER_PMD)


NEXT_PAGE(level3_kernel_pgt)
	.fill	L3_START_KERNEL,8,0
	/* (2^48-(2*1024*1024*1024)-((2^39)*511))/(2^30) = 510 */
	.quad	level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE
	.quad	level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE


NEXT_PAGE(level2_kernel_pgt)
	/*
	 * 512 MB kernel mapping. We spend a full page on this pagetable
	 * anyway.
	 *
	 * The kernel code+data+bss must not be bigger than that.
	 *
	 * (NOTE: at +512MB starts the module area, see MODULES_VADDR.
	 *  If you want to increase this then increase MODULES_VADDR
	 *  too.)
	 */
	PMDS(0, __PAGE_KERNEL_LARGE_EXEC,
		KERNEL_IMAGE_SIZE/PMD_SIZE)


NEXT_PAGE(level2_fixmap_pgt)
	.fill	506,8,0
	.quad	level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
	/* 8MB reserved for vsyscalls + a 2MB hole = 4 + 1 entries */
	.fill	5,8,0


NEXT_PAGE(level1_fixmap_pgt)
	.fill	51

一、爲何要減去__START_KERNEL_mapatom

一、由於level3_ident_pgt是定義在內核代碼裏的,寫代碼的時候,寫的都是虛擬地址,誰寫代碼的時候頁不知道未來加載的物理地址是多少呀,對不對
二、由於level3_ident_pgt是在虛擬地址的內核代碼裏的,而__START_KERNEL_map正是虛擬地址空間內核代碼段的其實地址,
三、level3_ident_pgt 減去 __START_KERNEL_map纔是物理地址spa

第一項定義完了之後,接下來咱們跳到PGD_PAGE_OFFSET的位置,再定義一項,從定義能夠看出
這一項就應該是__PAGE_OFFSET_BASE對應的,__PAGE_OFFSET_BASE 是虛擬地址空間裏面內核的起始地址
第二項也指向level3_ident_pgt 直接映射區

PGD_PAGE_OFFSET = pgd_index(__PAGE_OFFSET_BASE)
PGD_START_KERNEL = pgd_index(__START_KERNEL_map)
L3_START_KERNEL = pud_index(__START_KERNEL_map)

第二項定義完了之後,接下來跳到PGD_START_KERNEL的位置,再定義一項,從定義能夠看出,這一項應該是_START_KERNEL_map對應的項,

_START_KERNEL_map是虛擬地址空間裏面內核代碼段的起始地址。第三項指向level3_kernel_pgt,內核代碼區

接下來的代碼就很相似了,就是初始化個表項,而後指向下一級目錄,最終造成下面這張圖

 

內核頁表定完了,一開始這裏面的頁表可以覆蓋的內存範圍比較小,例如,內核代碼區512M,直接映射區1G,這個時候,其實只要可以映射基本的內核代碼和數據結構解能夠了。能夠看出,裏面還空着不少項,

能夠用於未來映射巨大的內核虛擬地址空間,等用到的時候再進行映射


若是是用戶態進程頁表,會有mm_struct指向進程項目錄pgd,對於內核來說,定義了一個mm_stuct,指向swapper_pg_dir

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),
	.user_ns	= &init_user_ns,
	INIT_MM_CONTEXT(init_mm)
};

在setup_arch中,load_cr3(swapper_pg_dir)說明內核頁表要開始起做用了,而且刷新了TLB,初始化init_mm的成員變量,最重要的及時init_mem_mapping,最終它會調用kernel_physical_mapping_init

void __init setup_arch(char **cmdline_p)
{
	/*
	 * copy kernel address range established so far and switch
	 * to the proper swapper page table
	 */
	clone_pgd_range(swapper_pg_dir     + KERNEL_PGD_BOUNDARY,
			initial_page_table + KERNEL_PGD_BOUNDARY,
			KERNEL_PGD_PTRS);


	load_cr3(swapper_pg_dir);
	__flush_tlb_all();
......
	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code = (unsigned long) _etext;
	init_mm.end_data = (unsigned long) _edata;
	init_mm.brk = _brk_end;
......
	init_mem_mapping();
......
}

在 kernel_physical_mapping_init裏,咱們先經過 __va 將物理地址轉換爲虛擬地址,,而後在建立虛擬地址和物理地址的映射頁表。

一、既然對於內核來說,咱們能夠用 __va 和 __pa 直接在虛擬地址和物理地址之間轉來轉去?

由於這是CPU和內存的硬件的需求,也就是說。CPU在保護模式下訪問虛擬地址的時候,就會用CR3這個寄存器,這個寄存器是CPU定義的

做爲操做系統,咱們是軟件,只能按照硬件的要求來。

二、按照我們將初始化的時候的過程,系統早早就進入了保護模式,到了 setup_arch 裏面才 load_cr3,若是使用 cr3 是硬件的要求,那以前是怎麼辦的呢?

若是你仔細去看 arch\x86\kernel\head_64.S,這裏面除了初始化內核頁表以外,在這以前,還有另外一個頁表 early_top_pgt。

看到關鍵字 early 了嘛?這個頁表就是專門用在真正的內核頁表初始化以前,爲了遵循硬件的要求而設置的

2、vmalloc 和 kmap_atomic 原理

用戶態能夠經過malloc函數分配內存,固然malloc在分分配比較大的內存的時候,底層調用的是mmap,固然也能夠直接經過mmap作內存映射、在內核裏面也有相應的函數

在虛擬地址空間裏,有個vmalloc區域,從VMALLOC_START開始VMALLOC_END,能夠用於映射一段物理內存

/**
 *	vmalloc  -  allocate virtually contiguous memory
 *	@size:		allocation size
 *	Allocate enough pages to cover @size from the page level
 *	allocator and map them into contiguous kernel virtual space.
 *
 *	For tight control over page level allocator and protection flags
 *	use __vmalloc() instead.
 */
void *vmalloc(unsigned long size)
{
	return __vmalloc_node_flags(size, NUMA_NO_NODE,
				    GFP_KERNEL);
}


static void *__vmalloc_node(unsigned long size, unsigned long align,
			    gfp_t gfp_mask, pgprot_t prot,
			    int node, const void *caller)
{
	return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
				gfp_mask, prot, 0, node, caller);
}

咱們再來看內核的臨時映射函數kmap_atomic的實現,從下面的代碼咱們能夠看出:

一、若是是32位有高端地址的就須要調用set_pte經過內核頁表進行臨時映射;

二、若是是64位沒有高端地址的,就調用page_address,裏面會調用lowmen_page_address,

三、其實低端內存的映射,會直接使用_va進行臨時映射

void *kmap_atomic_prot(struct page *page, pgprot_t prot)
{
......
	if (!PageHighMem(page))
		return page_address(page);
......
	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
	set_pte(kmap_pte-idx, mk_pte(page, prot));
......
	return (void *)vaddr;
}


void *kmap_atomic(struct page *page)
{
	return kmap_atomic_prot(page, kmap_prot);
}


static __always_inline void *lowmem_page_address(const struct page *page)
{
	return page_to_virt(page);
}


#define page_to_virt(x)	__va(PFN_PHYS(page_to_pfn(x)

3、內核態缺頁異常

一、vmalloc 和 kmap_atomic 不一樣

kmap_atomic發現,沒有頁表的時候,就直接建立也表進行映射了、而vmalloc沒有,它只分配了內核的虛擬地址、因此,訪問它的時候,會產生缺頁異常

二、內核態的缺頁異常

內核態的缺頁異常仍是會調用do_page_fault,可是會走到我們上面用戶態缺頁異常中沒有解析的部分vmalloc_fault。這個函數並不複雜,主要用於關聯內核頁表項

/*
 * 32-bit:
 *
 *   Handle a fault on the vmalloc or module mapping area
 */
static noinline int vmalloc_fault(unsigned long address)
{
	unsigned long pgd_paddr;
	pmd_t *pmd_k;
	pte_t *pte_k;


	/* Make sure we are in vmalloc area: */
	if (!(address >= VMALLOC_START && address < VMALLOC_END))
		return -1;


	/*
	 * Synchronize this task's top level page-table
	 * with the 'reference' page table.
	 *
	 * Do _not_ use "current" here. We might be inside
	 * an interrupt in the middle of a task switch..
	 */
	pgd_paddr = read_cr3_pa();
	pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
	if (!pmd_k)
		return -1;


	pte_k = pte_offset_kernel(pmd_k, address);
	if (!pte_present(*pte_k))
		return -1;


	return 0

4、總結時刻

一、物理內存管理

 

二、內存分配內核態

kemem_cache和kmalloc的部分不會被換出、由於用這兩個函數分配的內存多用於保存內核關鍵的數據結構,內核中vmalloc分配的部分會被換出,於是當訪問的時候、發現再也不
就會調用do_page_fault

三、內存分配用戶態

四、內存分配體系總圖

相關文章
相關標籤/搜索