1. 程式人生 > >程序頁表與核心頁表:頁表的初始化

程序頁表與核心頁表:頁表的初始化

摘要:linux剛剛加電啟動時,如何從真實模式進入保護模式?啟動分頁機制的前提是什麼?如何保證分頁機制之前和之後通過實地址和虛擬地址都能訪問到同一個實體地址呢?核心頁表是如何進行初始化的?使用者程序不能訪問核心的資料是在初始化的哪個階段決定的?這些內容,都牽扯到linu的程序頁表和核心頁表,以及核心頁表的初始化。本文也主要為你解答這些疑問.

1.程序頁表:

關鍵資料結構:

PAGE_OFFSET: 0xc000000

程序地址空間以0xc0000000(由巨集PAGE_OFFSET定義)分割成兩個部分,程序執行在使用者態,產生的地址小於0xc0000000;程序執行在核心態,產生的地址大於0xc0000000.但是,在某些情況下,核心為了訪問資料必須訪問使用者態線性地址空間。

程序地址空間,其中的核心態部分對於所有的程序都是一樣的,等於主核心頁全域性目錄的相應表項。

2.核心頁表

核心維護著自己的頁表,駐留在所謂的主核心頁全域性目錄中。我們將在此處解釋:核心如何初始化自己的頁表。

1)第一階段:核心映象剛剛裝入記憶體,CPU處於真實模式,分頁功能尚未開啟,核心建立一個有限的地址空間,128K,僅僅將核心裝入RAM並初始化核心資料。

2)第二階段:核心充分利用剩餘的RAM建立頁表,下面,我們將詳細討論這個頁表的建立過程。

2.1臨時核心頁表


關鍵資料結構:

swapper_pg_dir:臨時頁全域性目錄對應的虛擬地址。

pg0:第一個頁所在的實體地址

臨時頁全域性目錄在編譯核心過程中靜態初始化,而臨時頁全域性目錄存放在swapper_pg_dir之中。臨時頁表從pg0變數處開始存放。這裡,我們假設核心使用的段,臨時頁表和128KB的記憶體初始化資料可以存放在前8M的RAM之中。為了對映這8M,我們需要用到2個頁表。

開啟分頁的首要任務是確保真實模式和保護模式下都能對前8M進行定址(參考其中有關控制暫存器CR3的部分)。就是說,從0x0000000到0x007fffff的線性地址,從0xc0000000到0xc07fffff均可對映到實體地址範圍:0x00000000到0x007fffff。

是startup_32()來初始化的。它的等價程式碼(這段程式碼見於2.4.0核心,2.6核心以後不是這樣)如下:

 98         movl $swapper_pg_dir-__PAGE_OFFSET,%eax
 99         movl %eax,%cr3          /* set the page table pointer.. */
100         movl %cr0,%eax
101         orl $0x80000000,%eax
102         movl %eax,%cr0          /* ..and set paging (PG) bit */

其中,swapper_pg_dir是一個數組變數的名稱;核心通過將swapper_pg_dir的所有項都填充為0.除了0、1,ox300(十進位制768),0x301(十進位制769)除外。這四項按照下列方式進行初始化:

* 0和0x300設定位pg0的實體地址,1和0x301設定成pg1的地址

* 四項的present、R/W,U/S置位

* 四項的accessed、dirty、pcd和pagesize位置零

2.2RAM小於896M時候的最終核心頁表

關鍵資料結構:

關鍵函式:

巨集__pa和__va分別進行相應區域的實體地址和線性地址之間的轉換:位於核心空間的轉換

由核心頁表所提供的最終對映必須把從0xc0000000開始的線性地址對映到從0開始的實體地址。其中巨集__pa和__va分別進行相應區域的實體地址和線性地址之間的轉換。

主核心全域性目仍然在swapee_page_dir變數中,它由paging_init()函式進行初始化:

444 void __init paging_init(void)
445 {
446         pagetable_init();
447 
448         __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir)));
449 
450 #if CONFIG_X86_PAE
451         /*
452          * We will bail out later - printk doesnt work right now so
453          * the user would just see a hanging kernel.
454          */
455         if (cpu_has_pae)
456                 set_in_cr4(X86_CR4_PAE);
457 #endif  
458         
459         __flush_tlb_all();
460 
461 #ifdef CONFIG_HIGHMEM
462         kmap_init();
463 #endif
464         {
465                 unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0};
466                 unsigned int max_dma, high, low;
467 
468                 max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT;
469                 low = max_low_pfn;
470                 high = highend_pfn;
471 
472                 if (low < max_dma)
473                         zones_size[ZONE_DMA] = low;
474                 else {
475                         zones_size[ZONE_DMA] = max_dma;
476                         zones_size[ZONE_NORMAL] = low - max_dma;
477 #ifdef CONFIG_HIGHMEM
478                         zones_size[ZONE_HIGHMEM] = high - low;
479 #endif
480                 }
481                 free_area_init(zones_size);
482         }
483         return;
484 }

函式執行過程如下:

1)使用pagetable_init()建立頁表項

2)將swapper_pg_dir的實體地址寫入cr3

3)如果CPU編譯核心的時候支援PAE,則將CR4控制暫存器的PAE置位

4)呼叫__flush_tlb_all()使得所有的TLB無效

pagetable_init()執行的操作依賴於RAM容量和CPU模型,對全域性目錄的初始化程式碼等價如下:

314 static void __init pagetable_init (void)
315 {
316         unsigned long vaddr, end;
317         pgd_t *pgd, *pgd_base;
318         int i, j, k;
319         pmd_t *pmd;
320         pte_t *pte;
321 
322         /*
323          * This can be zero as well - no problem, in that case we exit
324          * the loops anyway due to the PTRS_PER_* conditions.
325          */
326         end = (unsigned long)__va(max_low_pfn*PAGE_SIZE);
327 
328         pgd_base = swapper_pg_dir;
329 #if CONFIG_X86_PAE
330         for (i = 0; i < PTRS_PER_PGD; i++) {
331                 pgd = pgd_base + i;
332                 __pgd_clear(pgd);
333         }
334 #endif
335         i = __pgd_offset(PAGE_OFFSET);
336         pgd = pgd_base + i;
337 
338         for (; i < PTRS_PER_PGD; pgd++, i++) {
339                 vaddr = i*PGDIR_SIZE;
340                 if (end && (vaddr >= end))
341                         break;
342 #if CONFIG_X86_PAE
343                 pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
344                 set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
345 #else
346                 pmd = (pmd_t *)pgd;
347 #endif
348                 if (pmd != pmd_offset(pgd, 0))
349                         BUG();
350                 for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
351                         vaddr = i*PGDIR_SIZE + j*PMD_SIZE;
352                         if (end && (vaddr >= end))
353                                 break;
354                         if (cpu_has_pse) {
355                                 unsigned long __pe;
356 
357                                 set_in_cr4(X86_CR4_PSE);
358                                 boot_cpu_data.wp_works_ok = 1;
359                                 __pe = _KERNPG_TABLE + _PAGE_PSE + __pa(vaddr);
360                                 /* Make it "global" too if supported */
361                                 if (cpu_has_pge) {
362                                         set_in_cr4(X86_CR4_PGE);
363                                         __pe += _PAGE_GLOBAL;
364                                 }
365                                 set_pmd(pmd, __pmd(__pe));
366                                 continue;
367                         }
368 
369                         pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
370                         set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte)));
371 
372                         if (pte != pte_offset(pmd, 0))
373                                 BUG();
374 
375                         for (k = 0; k < PTRS_PER_PTE; pte++, k++) {
376                                 vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE;
377                                 if (end && (vaddr >= end))
378                                         break;
379                                 *pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL);
380                         }
381                 }
382         }
383 
384         /*
385          * Fixed mappings, only the page table structure has to be
386          * created - mappings will be set by set_fixmap():
387          */
388         vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
389         fixrange_init(vaddr, 0, pgd_base);
390 
391 #if CONFIG_HIGHMEM
392         /*
393          * Permanent kmaps:
394          */
395         vaddr = PKMAP_BASE;
396         fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);
397 
398         pgd = swapper_pg_dir + __pgd_offset(vaddr);
399         pmd = pmd_offset(pgd, vaddr);
400         pte = pte_offset(pmd, vaddr);
401         pkmap_page_table = pte;
402 #endif
403 
404 #if CONFIG_X86_PAE
405         /*
406          * Add low memory identity-mappings - SMP needs it when
407          * starting up on an AP from real-mode. In the non-PAE
408          * case we already have these mappings through head.S.
409          * All user-space mappings are explicitly cleared after
410          * SMP startup.
411          */
412         pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
413 #endif
414 }


由startup_32()函式建立的實體記憶體前8M的恆等轉化在這種對映不再必要的時候,需要呼叫zap_low_mappings()進行撤銷。

2.3當RAM在896M和4096M之間的最終核心頁表

這種情況下,並不把RAM全部對映到核心地址空間。linux在初始化階段將把一個具有896MB的視窗對映到核心線性地址空間。如果一個程式需要對896M以上的地址進行定址,那麼就必須把線性地址對映到對應的RAM,這意味這修改某些頁表項的值。核心使用與前一種情況相同的程式碼來初始化頁全域性目錄。

2.4當RAM大於4096MB時候的最終核心頁表

此時,線性地址只有1G和RAM大於1G,此處的對映就可能涉及到PAE和高階記憶體,詳細可以參考高階記憶體。此時,linux僅僅對映前896M的RAM,剩下的不進行對映(剩下的就用於存放使用者資料了)。與前兩種的主要差異在於,此時,採用三級分頁模型,程式碼如下:

。。。。。。

頁全域性目錄的前三項與使用者線性地址空間對應,核心使用一個空頁(empty_zero_page)進行初始化,第四項,採用頁中間目錄的地址進行初始化,改頁中間目錄是通過呼叫alloc_bootmem_low_pages()獲得的。頁中間目錄的前448項用RAM的實體地址填充。

然後頁全域性目錄的第四項被拷貝到第一項中,這樣好為線性地址空間的前896M中的第實體記憶體對映做映象。為了完成對SMP系統的初始化,這個對映是必須的,初始化完成以後,核心呼叫zap-low_mappings()清楚對應的頁表項。