linux kernel的virtual kernel memory layout介绍(aarch64)
相關文件:
memory.h
pgtable.h
fixmap.h
page.h
1、重要的配置
我們就以VA_BITS=48,PAGE_SIZE=4k來介紹
(1)、(VA_BITS)
(arch/arm64/Kconfig)
(2)、(PAGE_SIZE、PAGE_SHIFT)
如果選擇了ARM64_4K_PAGES,那么PAGE_SIZE = 4K,PAGE_SHIFT=12
2、kernel 4.4代碼 : 計算各個區域的地址
(memory.h):
(fixmap.h)
#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT) #define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)(pgtable.h)
#define VMEMMAP_SIZE ALIGN((1UL << (VA_BITS - PAGE_SHIFT)) * sizeof(struct page), PUD_SIZE) #define VMALLOC_START (MODULES_END) #define VMALLOC_END (PAGE_OFFSET - PUD_SIZE - VMEMMAP_SIZE - SZ_64K) #define VMEMMAP_START (VMALLOC_END + SZ_64K)KASAN_SHADOW_SIZE = 0x2000_0000_0000
MODULES_VSIZE = 0x0800_0000
VA_BITS = 48
VA_START = 0xffff_0000_0000_0000
PAGE_OFFSET = 0xffff_8000_0000_0000
MODULES_VADDR=(VA_START + KASAN_SHADOW_SIZE) = 0xffff_2000_0000_0000
KIMAGE_VADDR = (MODULES_END) = (MODULES_VADDR + MODULES_VSIZE) = 0xffff_2000_0800_0000
KIMAGE_VADDR = 0xffff_2000_0800_0000
PCI_IO_END = 0xffff_8000_0000_0000 - 0x0000_0800 = 0xffff_7fff_ffff_e800
PCI_IO_START = 0xffff_7fff_ffff_e800 - 0x0100_0000 = 0xffff_7fff_feff_e800
FIXADDR_TOP = (PCI_IO_START - SZ_2M) = 0xffff_7fff_feff_e800 - 0x0020_0000 = 0xffff_7fff_fedf_e800 //(end addr)
PCI_IO_START = 0xffff_7fff_feff_e800
可見VMALLOC_START和KIMAGE_VADDR是重疊的,也就是kernel遷移到VMALLOC區域
再看VMEMMAP_SIZE :
(例如VA_BITS=48, PAGE_SHIFT=12的情況下)
(1UL << (VA_BITS - PAGE_SHIFT)) 表示48位的有效虛擬地址,一共可以表示多數個page頁
再乘以sizeof(struct page), 表示需要多數內存來存儲struct page
也就是說VMEMMAP是用來存儲所有頁面的struct page結構體的
結合以上地址,我們畫了張圖,更直觀:
3、kernel 4.14代碼 : 計算各個區域的地址
(memory.h):
同樣,我們也畫了一張圖:
4、kernel image搬移到vmalloc區域后,virt_to_phys的變化
virt_to_phys的作用是將內核虛擬地址轉換成物理地址(針對線性映射區域)。
在kernel image還在線性映射區域的時候,virt_to_phys宏可以將kernel代碼中的一個地址轉換成物理地址,因為線性映射區域,物理地址和虛擬地址只有一個偏移。因此兩者很容易轉換。
那么現在kernel image和線性映射區域分開了,virt_to_phys宏又該如何實現呢?
在kernel中PAGE_OFFSET = 0x8000_0000_0000
#define PAGE_OFFSET (UL(0xffffffffffffffff) - \(UL(1) << (VA_BITS - 1)) + 1)當virt_to_phys調用時候,先判斷bit47(最高有效位),如果為1,則表示是(memory)DRAM的地址。那么直接使用X[46:0]和PHYS_OFFSET相加即可
#define __virt_to_phys(x) ({ \phys_addr_t __x = (phys_addr_t)(x); \__x & BIT(VA_BITS - 1) ? (__x & ~PAGE_OFFSET) + PHYS_OFFSET : \(__x - kimage_voffset); })PHYS_OFFSET是DRAM的真實物理地址
memstart_addr = round_down(memblock_start_of_DRAM(),ARM64_MEMSTART_ALIGN);當virt_to_phys調用時候,先判斷bit47(最高有效位),如果為0,則表示是kernel image的地址, 那么直接使用X[46:0]和kimage_voffset相減即可
kimage_voffset來自匯編中的__mmap_switched函數
str_l x21, __fdt_pointer, x5 // Save FDT pointerldr_l x4, kimage_vaddr // Save the offset between sub x4, x4, x24 // the kernel virtual and str_l x4, kimage_voffset, x5 // physical mappings綜上所述,virt_to_phys()當前能夠轉換的依然還是:線性區域、kimg區域(kernel image區域)
5、PCI/IO區域
如X86處理器為外設專門實現了一個單獨的地址空間,稱為"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元
在arm64中,其實也有類似的指令,只是我們沒有用到這個區域,也沒有使用這些指令
具體在kernel/include/asm-generic/io.h中:
其中PCI_IOBASE對應的就是PCI/IO的及地址(PCI_IO_START)。
6、ioremap
那么ioremap映射到了哪個區域呢?
(arch/arm64/include/asm/io.h)
#define ioremap(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) #define ioremap_nocache(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) #define ioremap_wc(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC)) #define ioremap_wt(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) #define iounmap __iounmap(ioremap.c)
void __iomem *__ioremap(phys_addr_t phys_addr, size_t size, pgprot_t prot) {return __ioremap_caller(phys_addr, size, prot,__builtin_return_address(0)); } EXPORT_SYMBOL(__ioremap); static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,pgprot_t prot, void *caller) {unsigned long last_addr;unsigned long offset = phys_addr & ~PAGE_MASK;int err;unsigned long addr;struct vm_struct *area;/** Page align the mapping address and size, taking account of any* offset.*/phys_addr &= PAGE_MASK;size = PAGE_ALIGN(size + offset);/** Don't allow wraparound, zero size or outside PHYS_MASK.*/last_addr = phys_addr + size - 1;if (!size || last_addr < phys_addr || (last_addr & ~PHYS_MASK))return NULL;/** Don't allow RAM to be mapped.*/if (WARN_ON(pfn_valid(__phys_to_pfn(phys_addr))))return NULL;area = get_vm_area_caller(size, VM_IOREMAP, caller);if (!area)return NULL;addr = (unsigned long)area->addr;area->phys_addr = phys_addr;err = ioremap_page_range(addr, addr + size, phys_addr, prot);if (err) {vunmap((void *)addr);return NULL;}return (void __iomem *)(offset + addr); }調用了get_vm_area_caller(size, VM_IOREMAP, caller)
(vmalloc.c)
具體代碼不再深究,但可以知道iorempa是在vmalloc區域分配的
總結
以上是生活随笔為你收集整理的linux kernel的virtual kernel memory layout介绍(aarch64)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux kernel进程切换(寄存器
- 下一篇: [工具]-C语言中字符串的形式打印16进