您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關計算機中內核怎么獲取內存,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
首先是由最底層的bios掃描到硬件信息,然后上傳給上層的kernel使用的。這里bios定義了一系列的中斷調用函數供上層使用。對于內存在x86下則是定義了INT 0x15,eax = 0xE820來獲取萬恒的內存映射。INT 0x15,AX = 0xE801則是用于獲取內存大小。INT 0x15,AX = 0x88也是用于獲取內存大小。
內核就是通過調用INT 0x15,EAX = 0xE820來獲取物理內存狀態的。
內核具體是通過函數detect_memory_e820(arch/x86/boot/memory.c)來執行中斷調用。該函數主要是循環執行bios的中斷系統調用,知道寄存器ebx的值為0的時候。其過程大致分為以下幾步:
記錄e820的內存地址。因為INT 15中斷處理函數會將e820記錄的數據拷貝到es:di指向的內存位置,因此需要在首次調用的時候,將es:di指向一塊內存區域。后續每次中斷調用的時候,后需要將es:di增加一個e820記錄大小的偏移,用于記錄下一個e820記錄。
e820記錄的索引。e820記錄的索引是通過寄存器ebx傳遞的。如果還有e820記錄,中斷處理函數會將ebx值加1。當沒有e820記錄需要讀取的時候,中斷處理函數會將ebx的值置為0。因此內核這里使用ebx的值是否為0來判斷記錄是否已經讀完。
static int detect_memory_e820(void) { int count = 0; struct biosregs ireg, oreg; struct boot_e820_entry *desc = boot_params.e820_table; static struct boot_e820_entry buf; /* static so it is zeroed */ initregs(&ireg); ireg.ax = 0xe820; ireg.cx = sizeof buf; ireg.edx = SMAP; ireg.di = (size_t)&buf; /* * Note: at least one BIOS is known which assumes that the * buffer pointed to by one e820 call is the same one as * the previous call, and only changes modified fields. Therefore, * we use a temporary buffer and copy the results entry by entry. * * This routine deliberately does not try to account for * ACPI 3+ extended attributes. This is because there are * BIOSes in the field which report zero for the valid bit for * all ranges, and we don't currently make any use of the * other attribute bits. Revisit this if we see the extended * attribute bits deployed in a meaningful way in the future. */ do { intcall(0x15, &ireg, &oreg); //執行bios 0x15中斷系統調用 ireg.ebx = oreg.ebx; /* for next iteration... */ /* BIOSes which terminate the chain with CF = 1 as opposed to %ebx = 0 don't always report the SMAP signature on the final, failing, probe. */ if (oreg.eflags & X86_EFLAGS_CF) break; /* Some BIOSes stop returning SMAP in the middle of the search loop. We don't know exactly how the BIOS screwed up the map at that point, we might have a partial map, the full map, or complete garbage, so just return failure. */ if (oreg.eax != SMAP) { count = 0; break; } *desc++ = buf; //讀取到的數據拷貝到desc count++; } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_table)); return boot_params.e820_entries = count; //返回所有的e820條目 }
一個典型的INT 15h,EAX = E820的輸出如下[1]:
Base Address | Length | Type 0x0000000000000000 | 0x000000000009FC00 | Free Memory (1) 0x000000000009FC00 | 0x0000000000000400 | Reserved Memory (2) 0x00000000000E8000 | 0x0000000000018000 | Reserved Memory (2) 0x0000000000100000 | 0x0000000001F00000 | Free Memory (1) 0x00000000FFFC0000 | 0x0000000000040000 | Reserved Memory (2) |
內核獲取到的最終結果存儲在boot_params.e820_table中。
內核在bootload的第一個階段從bios中獲取到內存的原始數據信息,在內核會將其逐步轉化,主要有三個數據結構:
e820_table_firmware:最原始的固件版本數據,在bootloader階段傳遞給內核。
e820_table_kexec:內核輕微修改過的版本,內核標記setup_data list為reserved,因此kexec可以重用setup_data信息。此外,kexec可以修改該結構來fake一個mptable。
e820_table:這是由底層x86代碼管理的最主要的結構,它最終會傳遞到上層的MM管理層。一旦信息傳遞到上層內存管理層,e820 map數據將不再有效,因此它的主要目的是作為一個臨時存儲,用于存儲早期啟動階段固件特定的內存布局數據。
二、第二階段將數據拷貝到e820_table結構
因此下一個階段就是將物理內存信息從boot_params.e820_table中轉換到e820_table中。
該過程其實比較簡單,在平臺初始化的時候會調用e820__memory_setup_default函數。該函數最終會調用__e820__range_add。就是將全局變量e820_table的entryies賦予boot_params.e820_table條目中的值。
/* * Add a memory region to the kernel E820 map. */ static void __init __e820__range_add(struct e820_table *table, u64 start, u64 size, enum e820_type type) { int x = table->nr_entries; if (x >= ARRAY_SIZE(table->entries)) { pr_err("too many entries; ignoring [mem %#010llx-%#010llx]\n", start, start + size - 1); return; } table->entries[x].addr = start; table->entries[x].size = size; table->entries[x].type = type; table->nr_entries++; }
三、第三階段將e820_table傳遞給memblock
最后就是將e820_table結構傳遞給上層MM管理單元使用。這里用到的函數e820__memblock_setup。該函數是在setup_arch中被調用。
void __init e820__memblock_setup(void) { int i; u64 end; /* * The bootstrap memblock region count maximum is 128 entries * (INIT_MEMBLOCK_REGIONS), but EFI might pass us more E820 entries * than that - so allow memblock resizing. * * This is safe, because this call happens pretty late during x86 setup, * so we know about reserved memory regions already. (This is important * so that memblock resizing does no stomp over reserved areas.) */ memblock_allow_resize(); for (i = 0; i < e820_table->nr_entries; i++) { struct e820_entry *entry = &e820_table->entries[i]; end = entry->addr + entry->size; if (end != (resource_size_t)end) continue; if (entry->type != E820_TYPE_RAM && entry->type != E820_TYPE_RESERVED_KERN) continue; memblock_add(entry->addr, entry->size); } /* Throw away partial pages: */ memblock_trim_memory(PAGE_SIZE); memblock_dump_all(); }
主要是調用memblock_add添加新的memblock region。其會調用memlock_add_range來添加內存塊到全局變量memblock.memory。在memlock_add_range中主要調用memblock_insert_region來插入新的memblock region。
/** * memblock_insert_region - insert new memblock region * @type: memblock type to insert into * @idx: index for the insertion point * @base: base address of the new region * @size: size of the new region * @nid: node id of the new region * @flags: flags of the new region * * Insert new memblock region [@base, @base + @size) into @type at @idx. * @type must already have extra room to accommodate the new region. */ static void __init_memblock memblock_insert_region(struct memblock_type *type, int idx, phys_addr_t base, phys_addr_t size, int nid, enum memblock_flags flags) { struct memblock_region *rgn = &type->regions[idx]; BUG_ON(type->cnt >= type->max); memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn)); rgn->base = base; rgn->size = size; rgn->flags = flags; memblock_set_region_node(rgn, nid); type->cnt++; type->total_size += size; }
這里涉及到兩個數據結構struct memblock_type和struct memblock_region,其定義如下:
/** * struct memblock_region - represents a memory region * @base: physical address of the region * @size: size of the region * @flags: memory region attributes * @nid: NUMA node id */ struct memblock_region { phys_addr_t base; phys_addr_t size; enum memblock_flags flags; #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP int nid; #endif }; /** * struct memblock_type - collection of memory regions of certain type * @cnt: number of regions * @max: size of the allocated array * @total_size: size of all regions * @regions: array of regions * @name: the memory type symbolic name */ struct memblock_type { unsigned long cnt; unsigned long max; phys_addr_t total_size; struct memblock_region *regions; char *name; };
memblock是一種處于啟動階段的內存管理方式,在啟動階段,通常的內存管理單元還沒有起來運行。memblock將系統內存看做連續區域的集合,分為三個集合:memory、reserved、physmem。
memory:描述的是kernel使用的物理內存。
reserved:描述的是已分配的regions。
physmem:描述的是boot過程中實際可用的物理內存。physmem只在某些架構下可用。
每一個區域通過struct memblock_region來表示。每一個內存類型通過struct memblock_type來表示,其包含了一組memory regions。
在系統啟動過程中,mem_init函數將會釋放掉所有的內存給頁分配器使用。除非架構支持CONFIG_ARCH_KEEP_MEMBLOCK,否則除了physmem的所有memblock數據結構在系統初始化完成后都將被丟棄。
關于“計算機中內核怎么獲取內存”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。