閱讀293 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Linux內核剖析 之 內存尋址(三)

//接前一篇博客:

本節主要講述Linux中的分頁機製,注意對比Linux和80x86下分頁機製的不同。

5Linux中的分頁


                 圖:Linux分頁模式(四級頁表)

       Linux的進程處理很大程度上依賴於分頁,每個進程都有自己的頁全局目錄和自己的頁表集。

5.1、線性地址字段:

      下列宏簡化了頁表處理。(80x86

      PAGE_SHIFT

      指定Offset字段的位數;當用於80x86處理器時,其值為12

      PMD_SHIFT

      指定線性地址的Offset字段和Table字段的總位數;即頁中間目錄項可以映射的區域大小的對數。

      PUD_SHIFT

      確定頁上級目錄項所能映射的區域大小的對數。

      PGDIR_SHIFT

      確定頁全局目錄項所能映射的區域大小的對數。

      PTRS_PER_PTEPTRS_PER_PMDPTRS_PER_PUDPTRS_PER_PGD

      用於計算頁表、頁中間目錄、頁上級目錄和頁全局目錄表中表項的個數。

      PAE被禁用時,它們產生的值分別為102411102432位係統,頁上級目錄(PUD)、頁中間目錄(PMD)置0,從根本上取消了頁上級目錄和頁中間目錄字段)。

      PAE被激活時,產生的值分別為5125121432位係統使用三級頁表,Linux的頁全局目錄對應於80x86的頁目錄指針表(PDPT),取消了頁上級目錄,頁中間目錄對應頁目錄,頁表對應80x86的頁表)。

====》注意區分Linux的分頁機製和80x86的分頁機製的不同。

5.2、頁表處理(宏)

      #頁表項格式轉化

      pte_tpmd_tpud_tpgd_t分別描述頁表項、頁中間目錄項、頁上級目錄項和頁全局目錄項的格式。pgprot_t表示與一個單獨表項相關的保護標誌。

 

      #讀頁標誌的函數

      #設置頁標誌的函數

      #對頁表項操作的宏

      #頁分配函數

5.3、物理內存布局:

      在初始化階段,內核必須建立一個物理地址映射來指定哪些物理地址範圍對內核可用而哪些物理地址範圍對內核不可用。

      內核將下列頁框標記為保留:

             #在不可用的物理地址範圍內的頁框;

            #含有內核代碼和已初始化的數據結構的頁框。

      一般來說,Linux內核安裝在RAM從物理地址0x00100000開始的地方,即從第二個MB開始。

      為什麼內核沒有安裝在RAM的第一個MB開始的地方?因為PC的體係結構,有幾個獨特的地方必須考慮到。如:

             #頁框0BIOS使用,存放加電自檢檢查到的係統硬件配置信息;

             #物理地址從0x000a00000x000fffff的範圍通常留給BIOS例程;

             #第一個MB內的頁框可能由特定計算機模型保留。

      Linux2.6的前768個頁框(3MB):

 

5.4、進程頁表:

      每一個進程都有它自己的頁全局目錄和頁表集,當發生進程切換時,cr3的內容被保存在前一個執行進程的task_struct中,(task_struct->mm->pgd

      將下一個進程的pgd地址裝入cr3寄存器。

      進程的線性地址空間分為兩部分

             0-3G    用戶態與內核態都可尋址

             3G-4G 隻有內核態才能尋址

      進程的頁全局目錄的第一部分表項映射的線性地址小於3G

      剩餘的表項對於所有進程都是相同的,等於內核頁表中的相應表項。

 

5.5、內核頁表:

      內核維持著一組自己使用的頁表,駐留在主內核頁全局目錄中。在係統初始化時建立。

      #內核創建一個有限的地址空間,將內核裝入RAM中和對其初始化的核心數據結構進行存儲;

      #內核充分利用剩餘的RAM並適當地建立分頁表。

      (1)、臨時內核頁表

      臨時頁全局目錄是在內核編譯過程中靜態初始化的,而臨時頁表是由startup_32()匯編語言函數初始化的。

      臨時頁全局目錄放在swapper_pg_dir變量中。臨時頁表在pg0變量處開始存放,緊接在內核未初始化的數據段後麵。

      (2)、當RAM小於896MB時的最終內核頁表

      Swapper_pg_dir頁全局目錄有如下代碼初始化:

     pagetable_init()函數====>>>

static void __init pagetable_init (void)
{
	unsigned long vaddr;
	pgd_t *pgd_base = swapper_pg_dir;

#ifdef CONFIG_X86_PAE
	int i;
	/* Init entries of the first-level page table to the zero page */
	for (i = 0; i < PTRS_PER_PGD; i++)
		set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));
#endif

	/* Enable PSE if available */
	if (cpu_has_pse) {
		set_in_cr4(X86_CR4_PSE);
	}

	/* Enable PGE if available */
	if (cpu_has_pge) {
		set_in_cr4(X86_CR4_PGE);
		__PAGE_KERNEL |= _PAGE_GLOBAL;
		__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;
	}

	kernel_physical_mapping_init(pgd_base); //核心語句,具體實現頁初始化
	remap_numa_kva();

	/*
	 * Fixed mappings, only the page table structure has to be
	 * created - mappings will be set by set_fixmap():
	 */
	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
	page_table_range_init(vaddr, 0, pgd_base);

	permanent_kmaps_init(pgd_base);

#ifdef CONFIG_X86_PAE
	/*
	 * Add low memory identity-mappings - SMP needs it when
	 * starting up on an AP from real-mode. In the non-PAE
	 * case we already have these mappings through head.S.
	 * All user-space mappings are explicitly cleared after
	 * SMP startup.
	 */
	pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}

       kernel_physical_mapping_init()函數====>>>關鍵在於中間的for循環實現!

static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
	unsigned long pfn;
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	int pgd_idx, pmd_idx, pte_ofs;

	pgd_idx = pgd_index(PAGE_OFFSET);
	pgd = pgd_base + pgd_idx;
	pfn = 0;

	for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
		pmd = one_md_table_init(pgd);
		if (pfn >= max_low_pfn)
			continue;
		for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {
			unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;

			/* Map with big pages if possible, otherwise create normal page tables. */
			if (cpu_has_pse) {
				unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;

				if (is_kernel_text(address) || is_kernel_text(address2))
					set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
				else
					set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
				pfn += PTRS_PER_PTE;
			} else {
				pte = one_page_table_init(pmd);

				for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {
						if (is_kernel_text(address))
							set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
						else
							set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
				}
			}
		}
	}
}
      (3)、當RAM大小在896MB4096MB之間時的最終內核頁表

      在這種情況下,並不把所有RAM全部映射到內核地址空間。Linux把一個具有896MBRAM窗口映射到內核線性地址空間。如果一個程序需要對現有RAM的其餘部分尋址,必須把某些其他的線性地址間接映射到所需的RAM(動態重映射)。

      (4)、當RAM大於4096MB時的最終內核頁表

      使用三級分頁模型。


6、處理硬件高速緩存和TLB

      硬件高速緩存的同步,由處理器自動完成。

      TLB的同步,由內核完成,因為線性地址到物理地址的映射是否有效,由內核決定。

      獨立於係統的使TLB表項無效的方法:

      

      在一個處理器上運行的函數發送處理器間中斷,給其他CPU,強製它們執行適當的函數刷新TLB


最後更新:2017-04-03 05:39:30

  上一篇:go 關於概率性事件的產品性能和客戶體驗討論
  下一篇:go 連載:麵向對象葵花寶典:思想、技巧與實踐(37) - 設計模式:瑞士軍刀 or 錘子?