在內核進行page初始化以及mmu配置之前,首先需要知道整個memory map。
PAGE_OFFSET Start address of Kernel space 0xC000_0000
lowmem Kernel direct-mapped RAM region (1:1 mapping) Maximum 896M
HIGH_MEMORY End address of lowmem PAGE_OFFSET + MEMORY_SIZE
pkmap 用來把HIGHMEM page 永久映射到 kernel space 2MB (這個大小每個平臺不一樣) kmap() / kunmap()
Page gap To against out-of-bounds errors 8MB
vmalloc vmalloc() / ioremap() space
DMA DMA memory mapping region
Fixmap kmap()可能會進入睡眠,所以不能用在中斷上下文等地方. 所以Fixmap就是用于在中斷上下文中把 highmem映射到內核空間的. Mapping HIGHMEM pages atomically kmap_atomic() :Fixmap在使用這個函數,所以可以在中斷上下文中使用
Vector CPU vectors are mapped here
Modules Kernel modules inserted via insmod are placed here 16MB (14MB, if HIGHMEM is enabled)
在內核初始化的時候,上面說的lowmemory中,還需要去除一些reserved memory。這些預留的內存是供一些外設使用的。下面來看一下預留內存的去除方式以及內核怎么讀取預留的。 (這里不包含具體的內存分配內容,比如slab或者buddy系統等)。
以高通平臺為例,bootloader中有如下函數會負責更新device tree中的memory node
int update_device_tree() { ... ret = fdt_path_offset(fdt, "/memory"); offset = ret; ret = target_dev_tree_mem(fdt, offset); ...}“/memory”一般定義在sekeleton.dtsi,這也是為什么雖然skeleton.dtsi文件里邊都是空的內容,但還是需要include這個文件的原因。
//skeleton64.dtsi/ { #address-cells = <2>; #size-cells = <2>; cpus { }; soc { }; chosen { }; aliases { }; memory { device_type = "memory"; reg = <0 0 0 0>; };};然后在kernel里調用如下函數來讀取memory大小等賦值給memblock變量:
setup_machine_fdt(){ ... of_scan_flat_dt(early_init_dt_scan_memory, NULL); ...}int __init early_init_dt_scan_memory(unsigned long node, const char *uname, int depth, void *data){ const char *type = of_get_flat_dt_linux,usable-memory", &l); if (reg == NULL) reg = of_get_flat_dt_prop(node, "reg", &l); if (reg == NULL) return 0; endp = reg + (l / sizeof(__be32)); pr_debug("memory scan node %s, reg size %d, data: %x %x %x %x,/n", uname, l, reg[0], reg[1], reg[2], reg[3]); while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { u64 base, size; base = dt_mem_next_cell(dt_root_addr_cells, ®); size = dt_mem_next_cell(dt_root_size_cells, ®); if (size == 0) continue; pr_debug(" - %llx , %llx/n", (unsigned long long)base, (unsigned long long)size); early_init_dt_add_memory_arch(base, size); } return 0;}在內核啟動之后,
start_kernel()->setup_arch()->setup_arch()->sanity_check_meminfo()的時候打印的memblock的內容為:
<6>[0.000000] [0:wapper:0] sanity_check_meminfo memblock.memory.cnt=2<6>[0.000000] [0:wapper:0] pys_addr vmalloc_limit = 0xa9c00000<6>[0.000000] [0:wapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000<6>[0.000000] [0:wapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000<6>[0.000000] [0:wapper:0] arm_lowmem_limit =0xa9c00000內存分為兩個CS: CS1基地址為0x80000000,大小為0x30000000。 CS2基地址為0xb0000000,大小為0x30000000。 所以物理內存開始地址為0x800000000,總的大小為1.5GB。 但中間缺了0x2fd00000到0x30000000的3MB大小的內存,哪里去了??(應該是bootloader改的~~,預留了sec_debug相關的內存)
這段3MB里邊,包含了sec_dbg的內容,但大小沒有3MB這么大,其余的用作什么了還得查<0>[0.000000] [0:swapper:0] sec_dbg_setup: str=@0xaff00008<0>[0.000000] [0:swapper:0] sec_dbg_setup: secdbg_paddr = 0xaff00008<0>[0.000000] [0:swapper:0] sec_dbg_setup: secdbg_size = 0x80000之后會調用如下函數,讀取memory相關的device tree內容,預留modem,audio等相關的內存:
setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()這時打印的內容為:
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xa9c00000<6>[0.000000] [0:swapper:0] cma: Found external_image__region@0, memory base 0x85500000, size 19 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found modem_adsp_region@0, memory base 0x86800000, size 88 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found pheripheral_region@0, memory base 0x8c000000, size 6 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found venus_region@0, memory base 0x8c600000, size 5 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found secure_region@0, memory base 0x00000000, size 109 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found qseecom_region@0, memory base 0x00000000, size 13 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found audio_region@0, memory base 0x00000000, size 3 MiB, limit 0xffffffff<6>[0.000000] [0:swapper:0] cma: Found splash_region@8E000000, memory base 0x8e000000, size 20 MiB, limit 0xffffffff讀取的dts文件內容可以找到,,內容如下:
{ memory { #address-cells = <2>; #size-cells = <2>;/* Additionally Reserved 6MB for TIMA and Increased the TZ app size * by 2MB [total 8 MB ] */ external_image_mem: external_image__region@0 { linux,reserve-contiguous-region; linux,reserve-region; linux,remove-completely; reg = <0x0 0x85500000 0x0 0x01300000>; label = "external_image_mem"; }; modem_adsp_mem: modem_adsp_region@0 { linux,reserve-contiguous-region; linux,reserve-region; linux,remove-completely; reg = <0x0 0x86800000 0x0 0x05800000>; label = "modem_adsp_mem"; }; peripheral_mem: pheripheral_region@0 { linux,reserve-contiguous-region; linux,reserve-region; linux,remove-completely; reg = <0x0 0x8C000000 0x0 0x0600000>; label = "peripheral_mem"; }; venus_mem: venus_region@0 { linux,reserve-contiguous-region; linux,reserve-region; linux,remove-completely; reg = <0x0 0x8C600000 0x0 0x0500000>; label = "venus_mem"; }; secure_mem: secure_region@0 { linux,reserve-contiguous-region; reg = <0 0 0 0x6D00000>; label = "secure_mem"; }; qseecom_mem: qseecom_region@0 { linux,reserve-contiguous-region; reg = <0 0 0 0xD00000>; label = "qseecom_mem"; }; audio_mem: audio_region@0 { linux,reserve-contiguous-region; reg = <0 0 0 0x314000>; label = "audio_mem"; }; cont_splash_mem: splash_region@8E000000 { linux,reserve-contiguous-region; linux,reserve-region; reg = <0x0 0x8E000000 0x0 0x1400000>; label = "cont_splash_mem"; }; };};之后在
setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()還會調用一次sanity_check_meminfo()函數這時打印的內容變成了
<6>[0.000000] [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000<6>[0.000000] [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xb1200000比較兩次調用sanity_check_meminfo()函數打印的log,可以看到扣除的內存范圍,這些里邊只有external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem這幾個被扣除了。 后面的secure_region,qseecom_region,audio_region,splash_region哪去了??(這部分被ion memory預留!!)
以下是扣除的內容
external_image_mem: 0x85500000~0x86800000 大小為 19MBmodem_adsp_mem :0x86800000 ~0x8C000000 大小為 88MBperipheral_mem : 0x8C000000 ~ 0x8C600000 大小為6MBvenus_mem:0x8c600000 ~ 0x8cb00000 大小為5MBsecure_mem : 0xd9000000~ 0xe0000000 大小為112MB //這個與上面的109MB相比大小被調整,為什么?qseecom_region : 0xd8000000 ~ 0xd9000000 大小為16MB////這個與上面的109MB相比大小也被調整,為什么?audio_mem : 0xd7c00000 大小為4MB//大小被調整splash_region : 0x8E000000~ 0x8F400000 大小為20MBdefault region :0xa9400000 ~ 0xa9c00000 大小為8MBexternal_image_mem,modem_adsp_mem,peripheral_mem,venus_mem這些被扣除前后,memblock的 內容如下:
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000//第一次打印的時候是這樣的,第二次打印就變成下面這樣了<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000<6>[0.000000] [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xb1200000//vmalloc被cmdline設置為了340MB,所以vmalloc_limit= 0xb1200000//(0xff000000 - 0x15400000(340MB)的值,也就是從0xff00000開始減去vmalloc大小得到的值)。//這個值被調整完之后變成arm_lowmem_limit = 0xa9c00000。 //但第二次被sanity_check_meminfo()函數打印的時候被調整成了0xb1200000,怎么調整的??//arm_lowmem_limit這個是最終劃分Lowmemory和其他vmalloc區域的標準。//從下面的可以看到lowmemory地址最大的區域就是0xf000000~0xf120000。最大地址就到0xf1200000,和arm_lowmem_limit是一樣的。//highmemory的開始地址是high_memory的值,大小如下://high_memory = __va(arm_lowmem_limit - 1) + 1; //這個值加上VMALLOC_OFFSET即為vmalloc的開始地址//#define VMALLOC_START ((unsigned long)high_memory + VMALLOC_OFFSET)//VMALLOC_OFFSET一般為8MB整個內存的示意圖
contig_page_data里邊node_zones的Normal和HighMem的 zone_start_pfn,spanned_pages正好對應上面的地址。
Normal: zone_start_pfn = 0x80000000 zone_start_pfn加上spanned_pages的個數,算一下地址正好是arm_lowmem_limit的值HighMem: zone_start_pfn的值也是正好等于arm_lowmem_limit的值。 zone_start_pfn加上spanned_pages的值也正好等于0xE0000000。不管是x86架構還是ARM架構,現在大部分CPU訪問內存,一般通過MMU來實現虛擬內存和物理內存的轉換。 以下是一個簡單的示意圖。(如果要詳細分析的話,要看MMU分幾層,每個page大小怎么配置等等!!參考ARM架構的書)
在ARM平臺,二級頁表和三級頁表可以選擇用。但目前為止沒有見過三級頁表的,所以略過三級頁表,只看一下二級頁表的。
//在/kernel/arch/arm/include/asm/pgtable.h文件里邊#ifdef CONFIG_ARM_LPAE #include <asm/pgtable-3level.h>#else#include <asm/pgtable-2level.h>#endif設置一個page大小。這里先略過去寄存器的設置以及page大小類型等。這部分可以參考arm developer’s guide。 先看一下Linux里邊在哪里定義page大小的。
//kernel/include/asm-generic/page.h文件里邊#define PAGE_SHIFT 12#define PAGE_SIZE (1UL << PAGE_SHIFT)//12是最常看到的4k大小的page。以ARM二級頁表為例,一級頁表和二級頁表的種類有兩種。
//page大小為4K,按下面的組織方式都可以map最大4G的內存地址空間。1. 一級頁表是4096,二級頁表是2562. 一級頁表是2048,二級頁表是512//在ARM Linux中,分別定義了PTRS_PER_PGD,PTRS_PER_PMD,PTRS_PER_PTE分別表示原本三級的頁表,但如果是二級頁表的話。這三個值分別定義為如下:#define PTRS_PER_PTE 512#define PTRS_PER_PMD 1#define PTRS_PER_PGD 2048//上面的值正好對應1級頁表2048,二級頁表512的組織方式。二級頁表中,PUD,PMD沒有用。//一級頁表4096,二級頁表256這樣的配置,就可以定義成如下:#define PTRS_PER_PTE 256#define PTRS_PER_PMD 1#define PTRS_PER_PGD 4096頁表的示意圖如下:
create_mapping()函數具體負責頁表的生成。
//create_mapping()有幾個調用路徑1. devicemaps_init()->create_mapping()2. map_lowmem()->create_mapping()3. iotable_init()->create_mapping()4. debug_ll_io_init()->create_mapping()可以看一下create_mapping()函數怎么按照物理和對應的虛擬內存,構建頁表。
Linux內核進程,訪問的地址都是內核范圍之內的,只要做一個簡單的偏移就可以在物理地址和虛擬地址之間進行轉換,就不多說了。 用戶進程,其page table的地址,都會保存在其task struct的mm或者active_mm的pgd中。可以根據這個地址,按照頁表的分配方式來算。
從用戶進程的task_struct中可以知道pgd的地址,當然頁表分配方式上面已經講了,這里是4096,256的分配方式。如果這個進程中,訪問的虛擬地址是0x01206000。按照下面的方式可以算出來是0x578DB000。
按照ARM Developer’s Guide中的圖,來看一下是怎么一步一步算出來的。
1. 進程數據結構: task_struct 2. 進程內存管理數據結構: mm_struct mmap: 進程分配的所有內存的鏈表頭 pgd: page global directory 的地址 3. 進程分配的內存,由vm_area_struct管理 vm_start and vm_end: 虛擬內存的開始地址和結束地址
下圖是用戶進程訪問的虛擬內存通過pgd轉換成物理地址的示意圖,在前面已經詳細講過:
新聞熱點
疑難解答