Linux 內核 虛擬地址 物理地址 轉換【轉】


轉自:https://blog.csdn.net/yang_chen_shi_wo/article/details/50275059

內核從3G開始的那一段是連續映射

而且這種固定映射最大到896M的地址范圍,也即從0xc0000000-0xf7ffffff的虛擬地址采用固定映射,稱為內核邏輯地址.剩下的1G-896=128M范圍的虛擬地址可以映射到任意物理地址.稱為內核虛擬地址.當實際內存大於1G時(實際上是> 896M時),用這塊地址空間做映射.

實際的計算機體系結構有硬件的制約,這限制了頁框可以使用的方式。尤其是,Linux內核必須處理80x86體系結構的兩種硬件約束:

  • ISA總線的直接存儲器(DMA)處理器有一個嚴格的限制:它們只能對RAM的前16MB尋址。
  • 在具有大容量RAM的現代32位計算機中,CPU不能直接訪問所有的物理存儲器,因為線形地址空間太小。

為了應付這兩種限制,Linux把物理存儲器划分為三個管理區(zone):
ZONE_DMA:包含低於16MB的存儲器頁

 

ZONE_NORMAL:包含高於16MB且低於896MB的存儲器頁

ZONE_HIGHMEM:包含高於896MB的存儲器頁

ZONE_DMA區包含的頁可以由老式基於ISA的設備通過DMA使用。

ZONE_DMA和ZONE_NORMAL和區包含的存儲器的“常規”頁,通過把它們線性地映射到線性地址空間的第4個GB,內核就可以直接進行訪問。相反,包含的存儲器頁不能由內核直接訪問,但它們也線性映射到了線性地址空間的第4個GB。在64位體系結構上沒有使用在64位體系結構上沒有使用ZONE_NORMAL。

 

這里只分析分配連續物理地址的函數。對於 vmalloc() 這種分配非連續物理地址的函數不在本記錄范圍之內。

1、kmalloc() 分配連續的物理地址,用於小內存分配。
2、__get_free_page() 分配連續的物理地址,用於整頁分配。

至於為什么說以上函數分配的是連續的物理地址和返回的到底是物理地址還是虛擬地址,下面的記錄會做出解釋。


kmalloc() 函數本身是基於 slab 實現的。slab是為分配小內存提供的一種高效機制。但 slab 這種分配機制又不是獨立的,它本身也是在頁分配器的基礎上來划分更細粒度的內存供調用者使用。也就是說系統先用頁分配器分配以頁為最小單位的連續物理地址,然后 kmalloc() 再在這上面根據調用者的需要進行切分。關於以上論述,我們可以查看malloc() 的實現,kmalloc()函數的實現是在 __do_kmalloc() 中,可以看到在__do_kmalloc() 代碼里最終調用了_cache_alloc() 來分配一個slab,其實kmem_cache_alloc() 等函數的實現也是調用了這個函數來分配新的 slab。我們按_cache_alloc() 函數的調用路徑一直跟蹤下去會發現在 cache_grow() 函數中使用了kmem_getpages() 函數來分配一個物理,kmem_getpages() 函數中調用的alloc_pages_node() 最終是使用 __alloc_pages() 來返回一個struct page 結構,而這個結構正是系統用來描述物理頁面的。這樣也就證實了上面所說的,slab 是在物理頁面基礎上實現的。kmalloc() 分配的是物理地址。


__get_free_page() 是頁面分配器提供給調用者的最底層的內存分配函數。它分配連續的物理內。__get_free_page() 函數本身是基於 buddy 實現的。在使用 buddy 實現的物理內存管理中最小分配粒度是以頁為單位的。關於以上論述,我們可以查看__get_free_page() 的實現,可以看到 __get_free_page() 函數只是一個非常簡單的封狀,它的整個函數實現就是無條件的調用 __alloc_pages() 函數來分配物理內存,上面記錄 kmalloc()實現時也提到過是在調用_alloc_pages() 函數來分配物理頁面的前提下進行的 slab 管理。那么這個函數是如何分配到物理頁面又是在什么區域中進行分配的?回答這個問題只能看下相關的實現。可以看到在 __alloc_pages() 函數中,多次嘗試調用get_page_from_freelist() 函數從 zonelist 中取得相關 zone,並從其中返回一個可用的 struct page 頁面(這里的有些調用分支是因為標志不同)。至此,可以知道一個物理頁面的分配是從 zonelist(一個 zone 的結構數組)中的 zone 返回的。那么 zonelist/zone 是如何與物理頁面關聯,又是如何初始化的呢?繼續來看 free_area_init_nodes() 函數,此函數在系統初始化時由 zone_sizes_init() 函數間接調用的,zone_sizes_init()
函數填充了三個區域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。並把他們作為參數調用 free_area_init_nodes(),在這個函數中會分配一個 pglist_data 結構,此結構中包含了zonelist/zone結構和一個 struct page 的物理頁結構,在函數最后用此結構作為參數調用了 free_area_init_node() 函數,在這個函數中首先使用 calculate_node_totalpages() 函數標記 pglist_data 相關區域,然后調用 alloc_node_mem_map() 函數初始化 pglist_data結構中的 struct page 物理頁。最后使用free_area_init_core()函數關聯 pglist_data 與 zonelist。可見__get_free_page()是從buddy systems分配的頁框。現在通以上分析已經明確了__get_free_page() 函數分配物理內存的流程。但這里又引出了幾個新問題,那就是此函數分配的物理頁面是如何映射的?映射到了什么位置?到這里不得不去看下與 VMM 相關的引導代碼。


在看 VMM 相關的引導代碼前,先來看一下virt_to_phys() 與phys_to_virt 這兩個函數。顧名思義,即是虛擬地址到物理地址和物理地址到虛擬地址的轉換。函數實現十分簡單,前者調用了__pa( address ) 轉換虛擬地址到物理地址,后者調用 __va( addrress ) 將物理地址轉換為虛擬地址。再看下 __pa __va 這兩個宏到底做了什么。

#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

通過上面可以看到僅僅是把地址加上或減去 PAGE_OFFSET,而PAGE_OFFSET 在 x86 下定義為0xC0000000。這里又引出了疑問,在 linux 下寫過 driver 的人都知道,在使用 kmalloc() 與__get_free_page() 分配完物理地址后,如果想得到正確的物理地址需要使用 virt_to_phys() 進行轉換。那么為什么要有這一步呢?我們不分配的不就是物理地址么?怎么分配完成還需要轉換?如果返回的是虛擬地址,那么根據如上對 virt_to_phys() 的分析,為什么僅僅對 PAGE_OFFSET 操作就能實現地址轉換呢?虛擬地址與物理地址之間的轉換不需要查頁表么?代着以上諸多疑問來看 VMM 相關的引導代碼。


直接從 start_kernel() 內核引導部分來查找VMM 相關內容。可以看到第一個應該關注的函數是 setup_arch(),在這個函數當中使用paging_init() 函數來初始化和映射硬件頁表(在初始化前已有 8M內存被映射,在這里不做記錄),而 paging_init() 則是調用的pagetable_init() 來完成內核物理地址的映射以及相關內存的初始化。在pagetable_init() 函數中,首先是一些PAE/PSE/PGE 相關判斷與設置,然后使用 kernel_physical_mapping_init() 函數來實現內核物理內存的映射。在這個函數中可以很清楚的看到,pgd_idx 是以PAGE_OFFSET 為啟始地址進行映射的,也就是說循環初始化所有物理地址是以 PAGE_OFFSET 為起點的。繼續觀察我們可以看到在 PMD 被初始化后,所有地址計算均是以 PAGE_OFFSET 作為標記來遞增的。分析到這里已經很明顯的可以看出,物理地址被映射到以 PAGE_OFFSET開始的虛擬地址空間。這樣以上所有疑問就都有了答案。kmalloc() 與__get_free_page() 所分配的物理頁面被映射到了 PAGE_OFFSET 開始的虛擬地址,也就是說實際物理地址與虛擬地址有一組一一對應的關系,正是因為有了這種映射關系,對內核以 PAGE_OFFSET 啟始的虛擬地址的分配也就是對物理地址的分配(當然這有一定的范圍,應該在 PAGE_OFFSET
與 VMALLOC_START 之間,后者為vmalloc() 函數分配內存的啟始地址)。這也就解釋了為什么 virt_to_phys() 與phys_to_virt() 函數的實現僅僅是加/減 PAGE_OFFSET 即可在虛擬地址與物理地址之間轉換,正是
因為了有了這種映射,且固定不變,所以才不用去查頁表進行轉換。這也同樣回答了開始的問題,即 kmalloc() / __get_free_page() 分配的是物理地址,而返回的則是虛擬地址(雖然這聽上去有些別扭)。正是因為有了這種映射關系,所以需要將它們的返回地址減去 PAGE_OFFSET 才可以得到真正的物理地址。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM