Linux內核-如何獲取虛擬地址對應的物理地址


 

enter image description here

在Linux中,

可以使用 pgd_offset()MACRO計算頁面全局目錄的偏移地址(cr3 +索引)。

可以使用 pud_offset()API計算頁面上目錄偏移地址。

可以使用 pmd_offset()API計算頁面中間目錄的偏移地址。

可以使用 pte_offset_map()MACRO計算頁表條目的偏移地址。

那么,如何獲得物理地址? (yellow line in above picture)
是否有功能或MACRO來計算物理地址?
edit : x86-64 architecture.

 

最佳答案

Linux內核使用通用的四頁分頁模型,該模型不僅適用於32位系統,而且還適用於64位系統。分頁單元是MMU(內存管理單元)的一部分,MMU將線性地址轉換為物理地址。

我為您編寫了一個內核模塊,以模擬從虛擬地址到物理地址的轉換過程。我假設您知道分頁系統的原理。

static void get_pgtable_macro(void) { printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET); printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT); printk("PUD_SHIFT = %d\n", PUD_SHIFT); printk("PMD_SHIFT = %d\n", PMD_SHIFT); printk("PAGE_SHIFT = %d\n", PAGE_SHIFT); printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD); printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD); printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD); printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE); printk("PAGE_MASK = 0x%lx\n", PAGE_MASK); } static unsigned long vaddr2paddr(unsigned long vaddr) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; unsigned long paddr = 0; unsigned long page_addr = 0; unsigned long page_offset = 0; pgd = pgd_offset(current->mm, vaddr); printk("pgd_val = 0x%lx\n", pgd_val(*pgd)); printk("pgd_index = %lu\n", pgd_index(vaddr)); if (pgd_none(*pgd)) { printk("not mapped in pgd\n"); return -1; } pud = pud_offset(pgd, vaddr); printk("pud_val = 0x%lx\n", pud_val(*pud)); if (pud_none(*pud)) { printk("not mapped in pud\n"); return -1; } pmd = pmd_offset(pud, vaddr); printk("pmd_val = 0x%lx\n", pmd_val(*pmd)); printk("pmd_index = %lu\n", pmd_index(vaddr)); if (pmd_none(*pmd)) { printk("not mapped in pmd\n"); return -1; } pte = pte_offset_kernel(pmd, vaddr); printk("pte_val = 0x%lx\n", pte_val(*pte)); printk("pte_index = %lu\n", pte_index(vaddr)); if (pte_none(*pte)) { printk("not mapped in pte\n"); return -1; } /* Page frame physical address mechanism | offset */ page_addr = pte_val(*pte) & PAGE_MASK; page_offset = vaddr & ~PAGE_MASK; paddr = page_addr | page_offset; printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset); printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr); return paddr; } static int __init v2p_init(void) { unsigned long vaddr = 0; printk("vaddr to paddr module is running..\n"); get_pgtable_macro(); printk("\n"); vaddr = (unsigned long)vmalloc(1000 * sizeof(char)); if (vaddr == 0) { printk("vmalloc failed..\n"); return 0; } printk("vmalloc_vaddr=0x%lx\n", vaddr); vaddr2paddr(vaddr); printk("\n\n"); vaddr = __get_free_page(GFP_KERNEL); if (vaddr == 0) { printk("__get_free_page failed..\n"); return 0; } printk("get_page_vaddr=0x%lx\n", vaddr); vaddr2paddr(vaddr); return 0; } static void __exit v2p_exit(void) { printk("vaddr to paddr module is leaving..\n"); vfree((void *)vaddr); free_page(vaddr); } 
  • Get_pgtable_macro()打印當前系統分頁機制中的一些宏。
  • 通過vmalloc()在內核空間中分配內存空間,調用vaddr2paddr()將轉換為虛擬地址的物理地址。
  • 使用vaddr2paddr()通過使用__get_free_pages()在內核空間中分配幀來將虛擬地址轉換為物理地址。
  • 分別通過vfree()和free_page()釋放請求的內存空間。

Vaddr2paddr()執行如下:
  • 通過pgd_offset計算頁面全局編錄條目的線性地址pgd,並傳入內存描述符mm和線性地址vaddr。接下來,打印pgd指向的頁面全局編錄條目。
  • 通過pud_offset計算頁面父目錄條目的線性地址pud,並將參數傳遞給頁面全局目錄條目的線性地址pgd和線性地址vaddr。然后打印指向父目錄條目的pud。
  • 通過pmd_offset計算頁面中間目錄條目的線性地址pmd,並將參數傳遞給父目錄條目的線性地址pud和線性地址vaddr。然后打印引用pmd目錄條目的頁面中間。
  • Pte_offset_kernel pte_offset_kernel由線性地址pte計算,該參數為線性地址pmd的線性地址和地址vaddr的目錄條目中間。然后打印pte指向的頁表項。
  • pte_val(* pte)刪除頁面表條目,並且PAGE_MASK階段和結果是訪問頁面的物理地址; vaddr&〜PAGE_MASK用於獲取線性地址偏移量字段;兩個或最終的物理地址計算。
  • 打印實際地址

原文: https://www.coder.work/article/1508383


免責聲明!

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



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