溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

計(jì)算機(jī)中內(nèi)核怎么獲取內(nèi)存

發(fā)布時(shí)間:2021-06-17 11:45:10 來源:億速云 閱讀:372 作者:小新 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)計(jì)算機(jī)中內(nèi)核怎么獲取內(nèi)存,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

一、第一階段從底層bios獲取數(shù)據(jù)

首先是由最底層的bios掃描到硬件信息,然后上傳給上層的kernel使用的。這里bios定義了一系列的中斷調(diào)用函數(shù)供上層使用。對(duì)于內(nèi)存在x86下則是定義了INT 0x15,eax = 0xE820來獲取萬恒的內(nèi)存映射。INT 0x15,AX = 0xE801則是用于獲取內(nèi)存大小。INT 0x15,AX = 0x88也是用于獲取內(nèi)存大小。

內(nèi)核就是通過調(diào)用INT 0x15,EAX = 0xE820來獲取物理內(nèi)存狀態(tài)的。

內(nèi)核具體是通過函數(shù)detect_memory_e820(arch/x86/boot/memory.c)來執(zhí)行中斷調(diào)用。該函數(shù)主要是循環(huán)執(zhí)行bios的中斷系統(tǒng)調(diào)用,知道寄存器ebx的值為0的時(shí)候。其過程大致分為以下幾步:

  1. 記錄e820的內(nèi)存地址。因?yàn)镮NT 15中斷處理函數(shù)會(huì)將e820記錄的數(shù)據(jù)拷貝到es:di指向的內(nèi)存位置,因此需要在首次調(diào)用的時(shí)候,將es:di指向一塊內(nèi)存區(qū)域。后續(xù)每次中斷調(diào)用的時(shí)候,后需要將es:di增加一個(gè)e820記錄大小的偏移,用于記錄下一個(gè)e820記錄。

  2. e820記錄的索引。e820記錄的索引是通過寄存器ebx傳遞的。如果還有e820記錄,中斷處理函數(shù)會(huì)將ebx值加1。當(dāng)沒有e820記錄需要讀取的時(shí)候,中斷處理函數(shù)會(huì)將ebx的值置為0。因此內(nèi)核這里使用ebx的值是否為0來判斷記錄是否已經(jīng)讀完。

        計(jì)算機(jī)中內(nèi)核怎么獲取內(nèi)存

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);  //執(zhí)行bios 0x15中斷系統(tǒng)調(diào)用
        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; //讀取到的數(shù)據(jù)拷貝到desc
        count++;
    } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_table));

    return boot_params.e820_entries = count; //返回所有的e820條目
}

一個(gè)典型的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)

內(nèi)核獲取到的最終結(jié)果存儲(chǔ)在boot_params.e820_table中。

內(nèi)核在bootload的第一個(gè)階段從bios中獲取到內(nèi)存的原始數(shù)據(jù)信息,在內(nèi)核會(huì)將其逐步轉(zhuǎn)化,主要有三個(gè)數(shù)據(jù)結(jié)構(gòu):

e820_table_firmware:最原始的固件版本數(shù)據(jù),在bootloader階段傳遞給內(nèi)核。

e820_table_kexec:內(nèi)核輕微修改過的版本,內(nèi)核標(biāo)記setup_data list為reserved,因此kexec可以重用setup_data信息。此外,kexec可以修改該結(jié)構(gòu)來fake一個(gè)mptable。

e820_table:這是由底層x86代碼管理的最主要的結(jié)構(gòu),它最終會(huì)傳遞到上層的MM管理層。一旦信息傳遞到上層內(nèi)存管理層,e820 map數(shù)據(jù)將不再有效,因此它的主要目的是作為一個(gè)臨時(shí)存儲(chǔ),用于存儲(chǔ)早期啟動(dòng)階段固件特定的內(nèi)存布局?jǐn)?shù)據(jù)。

二、第二階段將數(shù)據(jù)拷貝到e820_table結(jié)構(gòu)

因此下一個(gè)階段就是將物理內(nèi)存信息從boot_params.e820_table中轉(zhuǎn)換到e820_table中。

計(jì)算機(jī)中內(nèi)核怎么獲取內(nèi)存

該過程其實(shí)比較簡單,在平臺(tái)初始化的時(shí)候會(huì)調(diào)用e820__memory_setup_default函數(shù)。該函數(shù)最終會(huì)調(diào)用__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結(jié)構(gòu)傳遞給上層MM管理單元使用。這里用到的函數(shù)e820__memblock_setup。該函數(shù)是在setup_arch中被調(diào)用。

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();
}

主要是調(diào)用memblock_add添加新的memblock region。其會(huì)調(diào)用memlock_add_range來添加內(nèi)存塊到全局變量memblock.memory。在memlock_add_range中主要調(diào)用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;
}

這里涉及到兩個(gè)數(shù)據(jù)結(jié)構(gòu)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是一種處于啟動(dòng)階段的內(nèi)存管理方式,在啟動(dòng)階段,通常的內(nèi)存管理單元還沒有起來運(yùn)行。memblock將系統(tǒng)內(nèi)存看做連續(xù)區(qū)域的集合,分為三個(gè)集合:memory、reserved、physmem。

memory:描述的是kernel使用的物理內(nèi)存。

reserved:描述的是已分配的regions。

physmem:描述的是boot過程中實(shí)際可用的物理內(nèi)存。physmem只在某些架構(gòu)下可用。

每一個(gè)區(qū)域通過struct memblock_region來表示。每一個(gè)內(nèi)存類型通過struct memblock_type來表示,其包含了一組memory regions。

在系統(tǒng)啟動(dòng)過程中,mem_init函數(shù)將會(huì)釋放掉所有的內(nèi)存給頁分配器使用。除非架構(gòu)支持CONFIG_ARCH_KEEP_MEMBLOCK,否則除了physmem的所有memblock數(shù)據(jù)結(jié)構(gòu)在系統(tǒng)初始化完成后都將被丟棄。

關(guān)于“計(jì)算機(jī)中內(nèi)核怎么獲取內(nèi)存”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI