Linux 記憶體管理系統:初始化

來源:互聯網
上載者:User

作者:Joe Knapka
臭翻:colyli
記憶體管理系統的初始化處理流程分為三個基本階段:
啟用頁記憶體管理
在swapper_pg_dir中初始化核心的頁表
初始化一系列和記憶體管理相關的核心資料
Turning On Paging (i386)
啟動分頁機制(i386)
Kernel 代碼被載入到物理地址0x100000(1MB),在分頁機制開啟後被重新對應到
PAGE_OFFSET + 0x100000的位置(PAGE_OFFSET在IA32上為3GB,即進程虛擬位址中使用者
空間與核心空間的分界處)。這是通過將物理地址映射到編譯進來的頁表 (在
arch/i386/kernel/head.S中)的0-8MB以及PAGE_OFFSET-PAGE_OFFSET+8MB實現的。然後
我們跳轉到init/main.c中的start_kernel,這個函數被定位到PAGE_OFFSET+某一個地址。
這看起來有些狡猾。要注意到在head.S中啟動分頁機制的代碼是通過讓它自己所執行的地
址空間不再有效方式來實現這一點的;因此0-4MB被映射(不明白:hence the 0-4MB
identity mapping.)。在分頁機制沒有啟動之前,start_kernel是不會被調用的,我們假
定他運行在PAGE_OFFSET+某一個地方的位置。因此head.S中的頁表必須同樣映射核心代碼
所使用的地址,這樣後繼才能跳轉到staert_kernel處;因此PAGE_OFFSET被映射(不明
白:hence the PAGE_OFFSET mapping.)。
下面在head.S中分頁機制啟動時的一些神奇的代碼:
/*
* Enable paging
*/
3:
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
jmp 1f /* flush the prefetch-queue */
1:
movl $1f,%eax
jmp *%eax /* make sure eip is relocated */
1:
在兩個1的label之間的代碼將第二個label 1的地址載入到EAX中,然後跳轉到那裡。這
時,指令指標寄存器EIP指向1MB+某個數值的物理地址。而label都在核心的虛擬位址空
間(PAGE_OFFSET+某個位置),所以這段代碼將EIP有效從物理地址空間重新置放到了虛
擬地址空間。
Start_kernel函數初始化了所有的核心資料,然後啟動了init核心線程。Start_kernel中
最初的幾件事情之一就是調用setup_arch函數,這是一個和具體的體繫結構相關的設定函
數, 調用了更底層的初始化細節。對於x86 平台而言, 這些函數在
arch/i386/kernel/setup.c中。
在setup_arch 中和記憶體相關的第一件事就是計算低端記憶體(low-memory) 和高端記憶體
(high-memory)的有效頁的數目;每種記憶體類型(each memory type)最高端的頁的數目分別
儲存在全域變數highstart_pfn和highend_pfn中。高端記憶體並不是直接映射到核心的虛擬
記憶體(VM)中;這是後面要討論的。
接下來,setup_arch 調用init_bootmem 函數以初始化啟動時的記憶體 Clerk(boot-time
memory allocator)。Bootmem記憶體 Clerk僅僅在系統boot的過程中使用,為永久的核心數
據分配頁。因此我們不會對它涉及太多。需要記住的就是bootmem 分配器(bootmem
allocator)在核心初始化時提供頁,這些頁為核心專門預留,就好像他們是從核心景象文
件中載入的一樣,他們在系統啟動以後不參與任何的記憶體管理活動。
初始化核心頁表
之後,setup_arch調用在arch/i386/mm/init.c中的paging_init函數。這個函數做了一些
事情。首先它調用pagetable_init函數去映射整個的實體記憶體,或者在PAGE_OFFSET到4GB
之間的儘可能多的實體記憶體,這是從PAGE_OFFSET處開始。
在pagetable_init函數中,我們在swapper_pg_dir中精確的建立了核心的頁表,映射到
截至PAGE_OFFSET的整個實體記憶體。
這是一個將正確的數值填充到頁目錄和頁表中去的簡單的算術活。映射建立在
swapper_pg_dir中,即kernel頁目錄;這也是初始化頁機制時所使用的頁目錄。(當使用
4MB的頁表的時候,直到下一個4MB邊界的虛擬位址才會被映射在這裡,但這沒有什麼,
因為我們不會使用這個記憶體所以沒有什麼問題)。如果有這裡有剩下實體記憶體沒有被映射,
那就是大於4GB-PAGE_OFFSET範圍的記憶體,這些記憶體只有CONFIG_HIGHMEM選項被設定後
才能使用(即使用大於4GB的記憶體)。
在接近pagetable_init函數的尾部,我們調用了fixrange_init為編譯時間固定的虛擬記憶體
映射預留頁表。這些表將寫入程式碼到Kernel中的虛擬位址進行映射,但是他們並不是已經加
載的核心資料的一部分。Fixmap表在運行時調用set_fixmap函數被映射到實體記憶體。
在初始化了fixmap之後,如果CONFIG_HIGHMEM被設定了,我們還要分配一些頁表給kmap
分配器。Kmap允許kernel將物理地址的任何頁映射到kernel的虛擬位址空間,以臨時使用。
這很有用,例如對在pagetable_init中不能直接映射的實體記憶體進行映射。
Fixmap 和kmap 頁表們佔據了kernel 虛擬空間頂部的一部分——因此這些地址不能在
PAGE_OFFSET映射中被永久的映射到物理頁上。由於這個原因,Kernel虛擬記憶體的頂部的
128MB就被預留了(vmalloc分配器仍然是用這個範圍內的地址)。(下面這句實在是不知
道怎麼翻譯) Any physical pages that would otherwise be mapped into the
PAGE_OFFSET mapping in the 4GB-128MB range are instead (if CONFIG_HIGHMEM is
specified) included in the high memory zone, accessible to the kernel only via
kmap()。如果沒有設定CONFIG_HIGMEM,這些頁就完全是停用。這僅針對配置有大量內
存的機器(900多MB或者更多)。例如,如果PAGE_OFFSET=3GB,並且機器有2GB的RAM,
那麼只有開始的1GB-128MB的實體記憶體可以被映射到PAGE_OFFSET和fixmap/kmap地址範
圍之間。剩餘的頁是停用——實際上對於使用者進程映射來說,他們是可以直接映射的頁
——但是核心不能夠直接存取它們。
回到paging_init,我們可以通過調用kmap_init函數來初始化kmap系統,kmap_init簡
單的緩衝了最先的kmap_pagetable[在TLB?]。然後,我們通過計算zone的大小並調用
free_area_init 去建立mem_map 和初始化freelist,初始化了zone 分配器。所有的
freelist被初始化為空白,並且所有的頁都被標誌為reserved(不可被VM系統訪問);這
種情況之後會被糾正。
當paging_init完成後,實體記憶體的分布如下[注意在2.4的核心中這不全對]:
0x00000000: 0-page
0x00100000: kernel-text
0x????????: kernel_data
0x???????? =_end: whole-mem pagetables
0x????????: fixmap pagetables
0x????????: zone data (mem_map, zone_structs, freelists &c)
0x???????? =start_mem: free pages
這塊記憶體被swapper_pg_dir和whole-mem-pagetables映射以定址PAGE_OFFSET。
進一步的VM子系統初始化
現在我們回到start_kernel。在paging_init完成後,我們為核心的其他子系統進行一些
額外的配置工作,它們中的一些使用bootmem分配器分配額外的核心記憶體。從記憶體管理的觀
點來看,這其中最重要的是kmem_cache_init,他初始化了slab分配器的資料。
在kmem_cache_init 調用之後不久,我們調用了mem_init。這個通過清除空閑物理頁的
zone資料中的PG_RESERVED位在free_area_init的開始完成了初始化freelist的工作;
為不能被用為DMA的頁清除PG_DMA位;然後釋放所有可用的頁到他們各自的zone中。最後
一步,在bootmem.c 中的free_all_bootmem函數中完成,很有趣。他建立了夥伴位元影像和
freelist描述了所有存在的沒有預留的頁,這是通過簡單的釋放他們並讓free_page_ok
做正確的事情。一旦mem_init被調用了,bootmem分配器就不再使用了,所以它的所有的
頁也會被釋放到zone分配器的世界中。

段用來將線性地址空間劃分為專用的塊。線性空間是被VM子系統管理的。X86體繫結構從硬
件上支援段機制;你可以按照段+段內位移量的方式指定一個地址,這裡地址被描述為一定
範圍的線性(虛擬位址)並帶有特定的屬性(如保護屬性)。實際上,在x86體繫結構中你
必須使用段機制。所以我們要設定4個段:
一個kernel text段:從0 到4GB
一個kernel data段:從0 到4GB
一個user text段:從0 到4GB
一個user data段:從0 到4GB
因此我們可以使用任何一個有效段選取器(segment selector)訪問整個虛擬位址空間。
問題:
段是在哪裡被設定的?
答案:
通用描述元表(GDT)定義在head.s的450行。 GDT寄存器在250行被載入。
問題:
為什麼將核心段和使用者端分離開。是否他們都有許可權訪問整個4GB的範圍?
答案:
這是因為核心和使用者段的保護機制有區別:
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x2B user 4GB data at 0x00000000 */
段寄存器(CS,DS等)包含有一個13位的描述符表的索引,索引指向的描述符告訴CPU所選
擇的段的屬性。段選取器的低3位沒有被用來索引描述符表,而是用來儲存描述符類型(全
局或局部)以及需要的特權級。因此核心段選取器0x10和0x18使用特權級0(RPL0),用
戶選取器0x23和0x2B使用最特權級RPL 3。
要注意到第三個高序位元組的高位組對應核心和使用者也是不同的:對核心,描述符特權級
(DPL)為0;對使用者DPL為3。如果你閱讀了Intel的文檔,你將看到確切的含義,但是由
於Linux核心的x86段保護沒有涉及太多,所以我就不再討論太多了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.