1. 程式人生 > >Magenta源代碼筆記(3) —— 內存管理【轉】

Magenta源代碼筆記(3) —— 內存管理【轉】

sys priority them 根據 內存信息 add trie ldm csdn

轉自:http://blog.csdn.net/boymax2/article/details/52550197

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

Magenta內核支持虛擬地址的配置,依賴於cpu內的mmu模塊。

下面會從以下幾個方面對Magenta內核內存管理方面的代碼進行分析:

1、mmu初始化,也就是硬件mmu的初始化,以底層寄存器操作為主,匯編

2、pmm初始化,也就是代碼中物理內存結構的初始化

3、vmm初始化,也就是代碼中虛擬內存結構的初始化


mmu初始化

mmu初始化的代碼由匯編完成,其中主要涉及了以下幾個結構

TLB:內存映射表,其定義位於c代碼中

kernel
/arch/arm/arm/mmu.c [cpp] view plain copy uint32_t arm_kernel_translation_table[TT_ENTRY_COUNT] __ALIGNED(16384) __SECTION(".bss.prebss.translation_table"); 以及初始化的內存映射關系,以qemu-virt平臺 kernel/platform/qemu-virt/platform.c [cpp] view plain copy struct mmu_initial_mapping mmu_initial_mappings[] = {
/* all of memory */ { .phys = MEMORY_BASE_PHYS, // 內存物理基地址 .virt = KERNEL_BASE, // 內存虛擬基地址 .size = MEMORY_APERTURE_SIZE,// 虛擬內存大小 .flags = 0, .name = "memory" }, /* 1GB of peripherals */ { .phys
= PERIPHERAL_BASE_PHYS, // 外設物理基地址 .virt = PERIPHERAL_BASE_VIRT, // 外設虛擬基地址 .size = PERIPHERAL_BASE_SIZE, // 虛擬內存大小 .flags = MMU_INITIAL_MAPPING_FLAG_DEVICE, .name = "peripherals" }, /* null entry to terminate the list */ { 0 } }; 這兩個結構都會在之後的匯編代碼中使用。 mmu初始化的匯編代碼位於內核的啟動文件中,以arm32為例 自己對arm匯編不是很熟悉,在讀匯編代碼時花費了比較多的時間,希望有錯誤能指正出來 啟動文件中與mmu相關的代碼已經提取出來 在其中主要涉及到的操作為以下幾個: 1、重置mmu相關寄存器 2、計算物理地址相對虛擬地址的偏移 3、將tlb地址指向空間清零 4、遍歷mmu_initial_mappings結構,計算後寫入tlb 5、設置mmu相關寄存器 6、跳轉至c代碼 kernel/arch/arm/arm/start.S [plain] view plain copy #include <asm.h> #include <arch/arm/cores.h> #include <arch/arm/mmu.h> #include <kernel/vm.h> .section ".text.boot" .globl _start _start: b platform_reset b arm_undefined b arm_syscall b arm_prefetch_abort b arm_data_abort b arm_reserved b arm_irq b arm_fiq #if WITH_SMP b arm_reset #endif .weak platform_reset platform_reset: /* Fall through for the weak symbol */ // arm復位處理程序 .globl arm_reset arm_reset: /* do some early cpu setup */ // 讀SCTLR寄存器,手冊P1711 mrc p15, 0, r12, c1, c0, 0 /* i/d cache disable, mmu disabled */ // cache位與mmu位置0 bic r12, #(1<<12) bic r12, #(1<<2 | 1<<0) #if WITH_KERNEL_VM /* enable caches so atomics and spinlocks work */ // cache位與mmu位置1 orr r12, r12, #(1<<12) orr r12, r12, #(1<<2) #endif // WITH_KERNEL_VM // 寫SCTLR寄存器 mcr p15, 0, r12, c1, c0, 0 /* calculate the physical offset from our eventual virtual location */ // 計算物理地址相對虛擬地址的偏移,用於之後的轉換 .Lphys_offset: ldr r4, =.Lphys_offset adr r11, .Lphys_offset sub r11, r11, r4 ... #if ARM_WITH_MMU .Lsetup_mmu: /* set up the mmu according to mmu_initial_mappings */ /* load the base of the translation table and clear the table */ // 獲取轉換表地址 ldr r4, =arm_kernel_translation_table // 獲取轉換表物理地址 add r4, r4, r11 /* r4 = physical address of translation table */ mov r5, #0 mov r6, #0 /* walk through all the entries in the translation table, setting them up */ // 遍歷轉換表結構清零 0: str r5, [r4, r6, lsl #2] add r6, #1 cmp r6, #4096 bne 0b /* load the address of the mmu_initial_mappings table and start processing */ // 獲取初始映射地址 ldr r5, =mmu_initial_mappings // 獲取初始映射物理地址 add r5, r5, r11 /* r5 = physical address of mmu initial mapping table */ // 初始映射遍歷綁定至轉換表 // 轉換表的綁定 轉換表中元素的高12位為物理基地址下標,低20位為mmu相關flag .Linitial_mapping_loop: // 把結構體加載到各個通用寄存器中 ldmia r5!, { r6-r10 } /* r6 = phys, r7 = virt, r8 = size, r9 = flags, r10 = name */ /* round size up to 1MB alignment */ // 上調size對齊1MB ubfx r10, r6, #0, #20 add r8, r8, r10 add r8, r8, #(1 << 20) sub r8, r8, #1 /* mask all the addresses and sizes to 1MB boundaries */ // 物理地址 虛擬地址 大小 右移20位 取高12位 lsr r6, #20 /* r6 = physical address / 1MB */ lsr r7, #20 /* r7 = virtual address / 1MB */ lsr r8, #20 /* r8 = size in 1MB chunks */ /* if size == 0, end of list */ // 循環邊界判斷 cmp r8, #0 beq .Linitial_mapping_done /* set up the flags */ // 設置mmu相關flag,放置在r10 ldr r10, =MMU_KERNEL_L1_PTE_FLAGS teq r9, #MMU_INITIAL_MAPPING_FLAG_UNCACHED ldreq r10, =MMU_INITIAL_MAP_STRONGLY_ORDERED beq 0f teq r9, #MMU_INITIAL_MAPPING_FLAG_DEVICE ldreq r10, =MMU_INITIAL_MAP_DEVICE /* r10 = mmu entry flags */ 0: // 計算translation_table元素的值 // r10:mmu相關flag r6:物理地址高12位 // r12 = r10 | (r6 << 20) // 高20位為物理地址,低12位為mmu相關flag orr r12, r10, r6, lsl #20 /* r12 = phys addr | flags */ /* store into appropriate translation table entry */ // r4:轉換表物理基地址 r7:虛擬地址對應的section // r12 -> [r4 + r7 << 2] str r12, [r4, r7, lsl #2] /* loop until we‘re done */ // 準備下一個轉換表元素的填充 add r6, #1 add r7, #1 subs r8, #1 bne 0b b .Linitial_mapping_loop .Linitial_mapping_done: ... /* set up the mmu */ bl .Lmmu_setup #endif // WITH_KERNEL_VM ... // 跳轉至c程序 bl lk_main b . #if WITH_KERNEL_VM /* per cpu mmu setup, shared between primary and secondary cpus args: r4 == translation table physical r8 == final translation table physical (if using trampoline) */ // 設置mmu相關寄存器 // r4:轉換表物理基地址 // mmu相關寄存器 手冊P1724 .Lmmu_setup: /* Invalidate TLB */ mov r12, #0 mcr p15, 0, r12, c8, c7, 0 isb /* Write 0 to TTBCR */ // ttbcr寫0 mcr p15, 0, r12, c2, c0, 2 isb /* Set cacheable attributes on translation walk */ // 宏MMU_TTBRx_FLAGS為 (1 << 3) | (1 << 6) orr r12, r4, #MMU_TTBRx_FLAGS /* Write ttbr with phys addr of the translation table */ // 寫入ttbr0 mcr p15, 0, r12, c2, c0, 0 isb /* Write DACR */ // 寫DACR cache相關 mov r12, #0x1 mcr p15, 0, r12, c3, c0, 0 isb /* Read SCTLR into r12 */ // 讀SCTLR寄存器,手冊P1711 mrc p15, 0, r12, c1, c0, 0 /* Disable TRE/AFE */ // 禁用TRE和AFE標誌位 bic r12, #(1<<29 | 1<<28) /* Turn on the MMU */ // MMU使能標誌位 orr r12, #0x1 /* Write back SCTLR */ // 寫入SCTLR // MMU打開 mcr p15, 0, r12, c1, c0, 0 isb /* Jump to virtual code address */ // 跳轉 ldr pc, =1f 1: ... /* Invalidate TLB */ mov r12, #0 mcr p15, 0, r12, c8, c7, 0 isb /* assume lr was in physical memory, adjust it before returning */ // 計算跳轉點的虛擬地址,跳轉,之後會調用lk_main sub lr, r11 bx lr #endif ... 硬件層的內存管理相關的初始化基本完成後,會跳轉到c代碼 位於kernel/top/main.c 其中有關內存管理的函數調用順序為: 1、pmm_add_arena 將物理內存加入pmm結構 2、vm_init_preheap 堆初始化前的準備工作(鉤子) 3、heap_init 堆的初始化 4、vm_init_postheap 堆初始化後的工作(鉤子) 5、arm_mmu_init mmu相關的調整 首先要完成pmm初始化工作 pmm初始化主要分為以下幾步: 1、通過fdt庫從bootloader中獲取物理內存的長度 2、在pmm中加入物理內存 3、標記fdt結構的空間 4、標記bootloader相關的空間 pmm中比較重要的一個結構體,pmm_arena_t代表著一塊物理內存的抽象 kernel/include/kernel/vm.h [cpp] view plain copy typedef struct pmm_arena { struct list_node node; // 節點,物理內存鏈表 const char* name; // 名稱 uint flags; uint priority; paddr_t base; // 物理內存基地址 size_t size; // 物理內存長度 size_t free_count; // 空閑的頁數 struct vm_page* page_array; // 頁結構數組 struct list_node free_list; // 節點,該內存中空閑空間的鏈表 } pmm_arena_t; 接著以qemu-virt的platform為例,分析pmm初始化的過程 kernel/platform/qemu-virt.c [cpp] view plain copy // 全局物理內存結構體 static pmm_arena_t arena = { .name = "ram", .base = MEMORY_BASE_PHYS, .size = DEFAULT_MEMORY_SIZE, .flags = PMM_ARENA_FLAG_KMAP, }; ... // 該函數為平臺的早期初始化,在內核啟動時調用 void platform_early_init(void) { ... /* look for a flattened device tree just before the kernel */ // 獲取fdt結構 const void *fdt = (void *)KERNEL_BASE; int err = fdt_check_header(fdt); if (err >= 0) { /* walk the nodes, looking for ‘memory‘ and ‘chosen‘ */ int depth = 0; int offset = 0; for (;;) { offset = fdt_next_node(fdt, offset, &depth); if (offset < 0) break; /* get the name */ const char *name = fdt_get_name(fdt, offset, NULL); if (!name) continue; /* look for the properties we care about */ // 從fdt中查找到內存信息 if (strcmp(name, "memory") == 0) { int lenp; const void *prop_ptr = fdt_getprop(fdt, offset, "reg", &lenp); if (prop_ptr && lenp == 0x10) { /* we‘re looking at a memory descriptor */ //uint64_t base = fdt64_to_cpu(*(uint64_t *)prop_ptr); // 獲取內存長度 uint64_t len = fdt64_to_cpu(*((const uint64_t *)prop_ptr + 1)); /* trim size on certain platforms */ #if ARCH_ARM // 如果是32位arm,只使用內存前1GB if (len > 1024*1024*1024U) { len = 1024*1024*1024; /* only use the first 1GB on ARM32 */ printf("trimming memory to 1GB\n"); } #endif /* set the size in the pmm arena */ // 保存內存長度 arena.size = len; } } else if (strcmp(name, "chosen") == 0) { ... } } } /* add the main memory arena */ // 將改內存區域加入到pmm中 pmm_add_arena(&arena); /* reserve the first 64k of ram, which should be holding the fdt */ // 標記fdt區域 pmm_alloc_range(MEMBASE, 0x10000 / PAGE_SIZE, NULL); // 標記bootloader_ramdisk區域 platform_preserve_ramdisk(); ... } 內核在接下來初始化堆之前會在內存中構造出出一個VmAspace對象,其代表的是內核空間的抽象 kernel/kernel/vm/vm.cpp [cpp] view plain copy void vm_init_preheap(uint level) { LTRACE_ENTRY; // allow the vmm a shot at initializing some of its data structures // 構造代表內核空間的VmAspace對象 VmAspace::KernelAspaceInitPreHeap(); // mark all of the kernel pages in use LTRACEF("marking all kernel pages as used\n"); // 標記內核代碼所用內存 mark_pages_in_use((vaddr_t)&_start, ((uintptr_t)&_end - (uintptr_t)&_start)); // mark the physical pages used by the boot time allocator // 標記boot time allocator代碼所用內存 if (boot_alloc_end != boot_alloc_start) { LTRACEF("marking boot alloc used from 0x%lx to 0x%lx\n", boot_alloc_start, boot_alloc_end); mark_pages_in_use(boot_alloc_start, boot_alloc_end - boot_alloc_start); } } kernel/kernel/vm/vm_aspace.cpp [cpp] view plain copy void VmAspace::KernelAspaceInitPreHeap() { // the singleton kernel address space // 構造一個內核空間單例,因為這個函數只會在啟動時調用,所以是這個對象是單例 static VmAspace _kernel_aspace(KERNEL_ASPACE_BASE, KERNEL_ASPACE_SIZE, VmAspace::TYPE_KERNEL, "kernel"); // 初始化 auto err = _kernel_aspace.Init(); ASSERT(err >= 0); // save a pointer to the singleton kernel address space // 保存單例指針 VmAspace::kernel_aspace_ = &_kernel_aspace; } VmAspace::VmAspace(vaddr_t base, size_t size, uint32_t flags, const char* name) : base_(base), size_(size), flags_(flags) { DEBUG_ASSERT(size != 0); DEBUG_ASSERT(base + size - 1 >= base); Rename(name); LTRACEF("%p ‘%s‘\n", this, name_); } status_t VmAspace::Init() { DEBUG_ASSERT(magic_ == MAGIC); LTRACEF("%p ‘%s‘\n", this, name_); // intialize the architectually specific part // 標記為內核的空間 bool is_high_kernel = (flags_ & TYPE_MASK) == TYPE_KERNEL; uint arch_aspace_flags = is_high_kernel ? ARCH_ASPACE_FLAG_KERNEL : 0; // 調用mmu相關的函數 return arch_mmu_init_aspace(&arch_aspace_, base_, size_, arch_aspace_flags); } kernel/arch/arm/arm/mmu.c [cpp] view plain copy status_t arch_mmu_init_aspace(arch_aspace_t *aspace, vaddr_t base, size_t size, uint flags) { LTRACEF("aspace %p, base 0x%lx, size 0x%zx, flags 0x%x\n", aspace, base, size, flags); DEBUG_ASSERT(aspace); DEBUG_ASSERT(aspace->magic != ARCH_ASPACE_MAGIC); /* validate that the base + size is sane and doesn‘t wrap */ DEBUG_ASSERT(size > PAGE_SIZE); DEBUG_ASSERT(base + size - 1 > base); // 初始化內核空間中頁的鏈表 list_initialize(&aspace->pt_page_list); aspace->magic = ARCH_ASPACE_MAGIC; if (flags & ARCH_ASPACE_FLAG_KERNEL) { // 設置結構內相關參數,其中轉換表的物理內存通過vaddr_to_paddr獲取 // 該函數不詳細分析了,實質就是通過轉換表進行查詢得到的物理地址 aspace->base = base; aspace->size = size; aspace->tt_virt = arm_kernel_translation_table; aspace->tt_phys = vaddr_to_paddr(aspace->tt_virt); } else { ... } LTRACEF("tt_phys 0x%lx tt_virt %p\n", aspace->tt_phys, aspace->tt_virt); return NO_ERROR; } 到此內核空間的結構初始化完成 接下來進行內核堆的初始化,Magenta內核中提供了兩種堆的實現miniheap以及cmpctmalloc,用戶可以自己進行配置。 堆的具體實現方法會在之後進行具體的分析 堆的初始化完成以後,會調用相應的鉤子函數,該函數的主要的作用如下: 1、在vmm結構中標記內核已使用的虛擬地址 2、根據內核使用的地址的區域,分別設置內存的保護 [cpp] view plain copy void vm_init_postheap(uint level) { LTRACE_ENTRY; vmm_aspace_t* aspace = vmm_get_kernel_aspace(); // we expect the kernel to be in a temporary mapping, define permanent // regions for those now struct temp_region { const char* name; vaddr_t base; size_t size; uint arch_mmu_flags; } regions[] = { { .name = "kernel_code", .base = (vaddr_t)&__code_start, .size = ROUNDUP((size_t)&__code_end - (size_t)&__code_start, PAGE_SIZE), .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE, }, { .name = "kernel_rodata", .base = (vaddr_t)&__rodata_start, .size = ROUNDUP((size_t)&__rodata_end - (size_t)&__rodata_start, PAGE_SIZE), .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ, }, { .name = "kernel_data", .base = (vaddr_t)&__data_start, .size = ROUNDUP((size_t)&__data_end - (size_t)&__data_start, PAGE_SIZE), .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE, }, { .name = "kernel_bss", .base = (vaddr_t)&__bss_start, .size = ROUNDUP((size_t)&__bss_end - (size_t)&__bss_start, PAGE_SIZE), .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE, }, { .name = "kernel_bootalloc", .base = (vaddr_t)boot_alloc_start, .size = ROUNDUP(boot_alloc_end - boot_alloc_start, PAGE_SIZE), .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE, }, }; for (uint i = 0; i < countof(regions); ++i) { temp_region* region = &regions[i]; ASSERT(IS_PAGE_ALIGNED(region->base)); status_t status = vmm_reserve_space(aspace, region->name, region->size, region->base); ASSERT(status == NO_ERROR); status = vmm_protect_region(aspace, region->base, region->arch_mmu_flags); ASSERT(status == NO_ERROR); } // mmu_initial_mappings should reflect where we are now, use it to construct the actual // mappings. We will carve out the kernel code/data from any mappings and // unmap any temporary ones. const struct mmu_initial_mapping* map = mmu_initial_mappings; for (map = mmu_initial_mappings; map->size > 0; ++map) { LTRACEF("looking at mapping %p (%s)\n", map, map->name); // Unmap temporary mappings except where they intersect with the // kernel code/data regions. vaddr_t vaddr = map->virt; LTRACEF("vaddr 0x%lx, virt + size 0x%lx\n", vaddr, map->virt + map->size); while (vaddr != map->virt + map->size) { vaddr_t next_kernel_region = map->virt + map->size; vaddr_t next_kernel_region_end = map->virt + map->size; // Find the kernel code/data region with the lowest start address // that is within this mapping. for (uint i = 0; i < countof(regions); ++i) { temp_region* region = &regions[i]; if (region->base >= vaddr && region->base < map->virt + map->size && region->base < next_kernel_region) { next_kernel_region = region->base; next_kernel_region_end = region->base + region->size; } } // If vaddr isn‘t the start of a kernel code/data region, then we should make // a mapping between it and the next closest one. if (next_kernel_region != vaddr) { status_t status = vmm_reserve_space(aspace, map->name, next_kernel_region - vaddr, vaddr); ASSERT(status == NO_ERROR); if (map->flags & MMU_INITIAL_MAPPING_TEMPORARY) { // If the region is part of a temporary mapping, immediately unmap it LTRACEF("Freeing region [%016lx, %016lx)\n", vaddr, next_kernel_region); status = vmm_free_region(aspace, vaddr); ASSERT(status == NO_ERROR); } else { // Otherwise, mark it no-exec since it‘s not explicitly code status = vmm_protect_region( aspace, vaddr, ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE); ASSERT(status == NO_ERROR); } } vaddr = next_kernel_region_end; } } } 以上代碼中涉及到的幾個函數,只是做下簡單的介紹,不具體分析: vmm_reserve_space:在vmm中標記一塊虛擬內存,這塊虛擬內存抽象為VmRegion類,擁有自己的底層mmu相關的配置 vmm_protect_region:對某VmRegion對應的虛擬內存設置內存保護的相關參數 mmu相關的調整 mmu相關的調整,由內核新建的bootstrap2線程進行調用arch_init完成 kernel/arch/arm/arm/arch.c [cpp] view plain copy void arch_init(void) { ... #if ARM_WITH_MMU /* finish intializing the mmu */ arm_mmu_init(); #endif } kernel/arch/arm/arm/mmu.c [cpp] view plain copy void arm_mmu_init(void) { /* unmap the initial mapings that are marked temporary */ // 解除具有MMU_INITIAL_MAPPING_TEMPORARY標誌的內存映射 struct mmu_initial_mapping *map = mmu_initial_mappings; while (map->size > 0) { if (map->flags & MMU_INITIAL_MAPPING_TEMPORARY) { vaddr_t va = map->virt; size_t size = map->size; DEBUG_ASSERT(IS_SECTION_ALIGNED(size)); while (size > 0) { arm_mmu_unmap_l1_entry(arm_kernel_translation_table, va / SECTION_SIZE); va += MB; size -= MB; } } map++; } arm_after_invalidate_tlb_barrier(); #if KERNEL_ASPACE_BASE != 0 /* bounce the ttbr over to ttbr1 and leave 0 unmapped */ // 重新設置mmu相關的寄存器,禁用ttbcr0,將原先ttbr0的映射移動到ttbr1 // ttbr1為內核空間使用的寄存器 uint32_t n = __builtin_clz(KERNEL_ASPACE_BASE) + 1; DEBUG_ASSERT(n <= 7); uint32_t ttbcr = (1<<4) | n; /* disable TTBCR0 and set the split between TTBR0 and TTBR1 */ arm_write_ttbr1(arm_read_ttbr0()); ISB; arm_write_ttbcr(ttbcr); ISB; arm_write_ttbr0(0); ISB; #endif } 至此Magenta內核有關內存管理的初始化完成。

Magenta源代碼筆記(3) —— 內存管理【轉】