ICTOS作业三

内核启动过程中的内存初始化

陈峥嵘-201928013229143

start_kernel 中与内存初始化相关的部分

asmlinkage __visible void __init start_kernel(void)
{

    /*  设置特定架构的信息
     *  同时初始化memblock  */
    setup_arch(&command_line);
    mm_init_cpumask(&init_mm);

    setup_per_cpu_areas();

    /*  初始化内存结点和内段区域  */
    build_all_zonelists(NULL, NULL);
    page_alloc_init();


    /*
     * These use large bootmem allocations and must precede
     * mem_init();
     * kmem_cache_init();
     */
    mm_init();

    kmem_cache_init_late();

    kmemleak_init();
    setup_per_cpu_pageset();

    rest_init();
}
函数功能
setup_arch是一个特定于体系结构的设置函数, 其中一项任务是负责初始化自举分配器
mm_init_cpumask初始化CPU屏蔽字
setup_per_cpu_areas给每个CPU分配内存,并拷贝.data.percpu段的数据. 为系统中的每个CPU的per_cpu变量申请空间.
build_all_zonelists建立并初始化结点和内存域的数据结构
mm_init建立了内核的内存分配器
kmem_cache_init_late在kmem_cache_init之后, 完善分配器的缓存机制
kmemleak_initKmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,能够帮助定位大多数内存错误的上下文
setup_per_cpu_pageset初始化CPU高速缓存行, 为pagesets的第一个数组元素分配内存

启动过程中的内存管理

引导内存分配器bootmem

  • bootmem_data描述内存引导区
  • bootmem_data的结构定义
#ifndef CONFIG_NO_BOOTMEM
/*
* node_bootmem_map is a map pointer - the bits represent all physical 
* memory pages (including holes) on the node.
*/
typedef struct bootmem_data {
       unsigned long node_min_pfn;  // 节点起始地址
       unsigned long node_low_pfn;  // 低端内存最后一个page的页帧号
       void *node_bootmem_map;  // 指向内存中位图bitmap所在的位置
       unsigned long last_end_off;  // 分配的最后一个页内的偏移,如果该页完全使用,则offset为0
       unsigned long hint_idx;
       struct list_head list;
} bootmem_data_t;

extern bootmem_data_t bootmem_node_data[];

#endif
  • node_bootmem_map就是一个指向位图的指针. node_min_pfn表示存放bootmem位图的第一个页面(即内核映像结束处的第一个页面)
  • node_low_pfn 表示物理内存的顶点, 最高不超过896MB

bootmem分配内存接口——NUMA结构的分配函数

  • 首先介绍在UMA系统中, 可供使用的函数
  • 从ZONE_NORMAL区域分配函数
#define alloc_bootmem(x) \
    __alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
    // 按照指定大小在ZONE_NORMAL内存域分配函数. 数据是对齐的, 这使得内存或者从可适用于L1高速缓存的理想位置开始
#define alloc_bootmem_align(x, align) \
    __alloc_bootmem(x, align, BOOTMEM_LOW_LIMIT)
    // 同alloc_bootmem函数, 按照指定大小在ZONE_NORMAL内存域分配函数, 并按照align进行数据对齐
#define alloc_bootmem_nopanic(x) \
    __alloc_bootmem_nopanic(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
    // 同alloc_bootmem函数, 按照指定大小在ZONE_NORMAL内存域分配函数, 其中_page只是指定数据的对其方式从页边界(__pages)开始
#define alloc_bootmem_pages(x) \
    __alloc_bootmem(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
    // alloc_bootmem_nopanic是最基础的通用的,一个用来尽力而为分配内存的函数,它通过list_for_each_entry在全局链表bdata_list中分配内存.
    // alloc_bootmem和alloc_bootmem_nopanic类似,它的底层实现首先通过alloc_bootmem_nopanic函数分配内存,但是一旦内存分配失败,
    // 系统将通过panic("Out of memory")抛出信息,并停止运行
#define alloc_bootmem_pages_nopanic(x) \
    __alloc_bootmem_nopanic(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
  • 从ZONE_DMA区域分配函数
#define alloc_bootmem_low(x) \
    __alloc_bootmem_low(x, SMP_CACHE_BYTES, 0)
    // 按照指定大小在ZONE_DMA内存域分配函数. 类似于alloc_bootmem, 数据是对齐的
#define alloc_bootmem_low_pages_nopanic(x) \
    __alloc_bootmem_low_nopanic(x, PAGE_SIZE, 0)
    // 按照指定大小在ZONE_DMA内存域分配函数. 类似于alloc_bootmem, 数据是对齐的
#define alloc_bootmem_low_pages(x) \
    __alloc_bootmem_low(x, PAGE_SIZE, 0)
    // 按照指定大小在ZONE_DMA内存域分配函数. 类似于alloc_bootmem_pages, 数据在页边界对齐
  • 函数实现方式
static void * __init ___alloc_bootmem(unsigned long size, unsigned long align,
                    unsigned long goal, unsigned long limit)
{
    void *mem = ___alloc_bootmem_nopanic(size, align, goal, limit);

    if (mem)
        return mem;
    /*
     * Whoops, we cannot satisfy the allocation request.
     */
    pr_alert("bootmem alloc of %lu bytes failed!\n", size);
    panic("Out of memory");
    return NULL;
}
static void * __init ___alloc_bootmem_nopanic(unsigned long size,
                          unsigned long align,
                          unsigned long goal,
                          unsigned long limit)
{
    void *ptr;

restart:
    ptr = alloc_bootmem_core(size, align, goal, limit);
    if (ptr)
        return ptr;
    if (goal) {
        goal = 0;
        goto restart;
    }

    return NULL;
}
```c
  * 在NUMA系统上, 基本的API是相同的, 但是函数增加了_node后缀, 与UMA系统的函数相比, 还需要一些额外的参数, 用于指定内存分配的结点.
```c
#define alloc_bootmem_node(pgdat, x) \
    __alloc_bootmem_node(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_node_nopanic(pgdat, x) \
    __alloc_bootmem_node_nopanic(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages_node(pgdat, x) \
    __alloc_bootmem_node(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages_node_nopanic(pgdat, x) \
    __alloc_bootmem_node_nopanic(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
    __alloc_bootmem_low(x, PAGE_SIZE, 0)
#define alloc_bootmem_low_pages_node(pgdat, x) \
    __alloc_bootmem_low_node(pgdat, x, PAGE_SIZE, 0)

__alloc_memory_core进行内存分配

  • __alloc_memory_core函数的功能相对而言很广泛(在启动期间不需要太高的效率), 该函数基于最先适配算法,
  • 但是该分配器不仅可以分配整个内存页, 还能分配页的一部分. 它遍历所有的bootmem list然后找到一个合适的内存区域, 然后通过 alloc_bootmem_bdata来完成分配
该函数主要执行如下操作:
  • list_for_each_entry从goal开始扫描为图, 查找满足分配请求的空闲内存区
  • 然后通过alloc_bootmem_bdata完成内存的分配

memblock内存分配器

  • bootmem也有很多问题. 最明显的就是外碎片的问题, 因此内核维护了memblock内存分配器

memblock的数据结构

  • memblock结构的定义
struct memblock {
    bool bottom_up;  /* is bottom up direction? 
    如果true, 则允许由下而上地分配内存*/
    phys_addr_t current_limit; /*指出了内存块的大小限制*/	
    /*  接下来的三个域描述了内存块的类型,即预留型,内存型和物理内存*/
    struct memblock_type memory;
    struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
    struct memblock_type physmem;
#endif
};
  • 数据结构memblock_type
struct memblock_type
{
    unsigned long cnt;      /* number of regions */
    unsigned long max;      /* size of the allocated array */
    phys_addr_t total_size; /* size of all regions */
    struct memblock_region *regions;
};
  • 内存区域memblock_region
   struct memblock_region
{
    phys_addr_t base;
    phys_addr_t size;
    unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
    int nid;
#endif
};
  • memblock_region的flags字段存储了当期那内存域的标识信息, 标识用enum变量来定义
/* Definition of memblock flags. */
enum {
    MEMBLOCK_NONE       = 0x0,  /* No special request */
    MEMBLOCK_HOTPLUG    = 0x1,  /* hotpluggable region */
    MEMBLOCK_MIRROR     = 0x2,  /* mirrored region */
    MEMBLOCK_NOMAP      = 0x4,  /* don't add to kernel direct mapping */
};

初始化 memblock 静态变量

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endif


struct memblock memblock __initdata_memblock = {
    .memory.regions     = memblock_memory_init_regions,
    .memory.cnt         = 1,    /* empty dummy entry */
    .memory.max         = INIT_MEMBLOCK_REGIONS,

    .reserved.regions       = memblock_reserved_init_regions,
    .reserved.cnt       = 1,    /* empty dummy entry */
    .reserved.max       = INIT_MEMBLOCK_REGIONS,

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
    .physmem.regions    = memblock_physmem_init_regions,
    .physmem.cnt        = 1,    /* empty dummy entry */
    .physmem.max        = INIT_PHYSMEM_REGIONS,
#endif

    .bottom_up          = false,
    .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
};
  • __initdata_memblock宏指定存储位置
   #ifdef CONFIG_ARCH_DISCARD_MEMBLOCK
#define __init_memblock __meminit
#define __initdata_memblock __meminitdata
#else
#define __init_memblock
#define __initdata_memblock
#endif
  • 3个memblock_type的初始化
  • memblock结构体中3个memblock_type类型数据 memory, reserved和physmem的初始化
  • 它们的memblock_typecnt域(当前集合中区域个数)被初始化为1. memblock_typemax域(当前集合中最大区域个数)被初始化为INIT_MEMBLOCK_REGIONS和INIT_PHYSMEM_REGIONS
  • 其中INIT_MEMBLOCK_REGIONS为128

Memblock-API函数接口

/
//   基本接口
/
// 向memory区中添加内存区域.
memblock_add(phys_addr_t base, phys_addr_t size)

//  向memory区中删除区域.
memblock_remove(phys_addr_t base, phys_addr_t size)

//  申请内存
memblock_alloc(phys_addr_t size, phys_addr_t align)


// 释放内存
memblock_free(phys_addr_t base, phys_addr_t size)


/
//   查找 & 遍历
/
//  在给定的范围内找到未使用的内存
phys_addr_t memblock_find_in_range(phys_addr_t start, phys_addr_t end, phys_addr_t size, phys_addr_t align)

//  反复迭代 memblock
for_each_mem_range(i, type_a, type_b, nid, flags, p_start, p_end, p_nid)




/
//   获取信息
/
//  获取内存区域信息
phys_addr_t get_allocated_memblock_memory_regions_info(phys_addr_t *addr);
//  获取预留内存区域信息
phys_addr_t get_allocated_memblock_reserved_regions_info(phys_addr_t *addr);

/
//   获取信息
/
#define memblock_dbg(fmt, ...) \
	if (memblock_debug) printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
  • memblock_add将内存区域加入到memblock中
  • memblock_remove删除内存区域
  • memblock_alloc申请内存
  • memblock_free释放内存区域

不同架构下的 memblock 初始化

x86下

void __init setup_arch(char **cmdline_p)
{
    /*
     * Need to conclude brk, before memblock_x86_fill()
     *  it could use memblock_find_in_range, could overlap with
     *  brk area.
     */
    reserve_brk();

    cleanup_highmap();

    memblock_set_current_limit(ISA_END_ADDRESS);
    memblock_x86_fill();
}
// 内核建立内核页表需要扩展__brk, 而扩展后的brk就立即被声明为已分配. 这项工作是由reserve_brk通过调用memblock_reserve完成的,
// 而其实并不是正真通过memblock分配的, 因为此时memblock还没有完成初始化
static void __init reserve_brk(void)
{
    if (_brk_end > _brk_start)
        memblock_reserve(__pa_symbol(_brk_start),
                 _brk_end - _brk_start);

    /* Mark brk area as locked down and no longer taking any
       new allocations */
    _brk_start = 0;
}
// 设置完__brk后, 可以看到,setup_arch()函数通过memblock_x86_fill(),依据e820中的信息来初始化memblock.
void __init memblock_x86_fill(void)
{
    int i;
    u64 end;

    /*
     * EFI may have more than 128 entries
     * We are safe to enable resizing, beause memblock_x86_fill()
     * is rather later for x86
     */
    memblock_allow_resize();

    for (i = 0; i < e820.nr_map; i++) {
        struct e820entry *ei = &e820.map[i];

        end = ei->addr + ei->size;
        if (end != (resource_size_t)end)
            continue;

        if (ei->type != E820_RAM && ei->type != E820_RESERVED_KERN)
            continue;

        memblock_add(ei->addr, ei->size);
    }

    /* throw away partial pages */
    memblock_trim_memory(PAGE_SIZE);

    memblock_dump_all();
}

arm下

void __init setup_arch(char **cmdline_p)
{
 arm_memblock_init(mdesc);
}
// 
void __init arm_memblock_init(const struct machine_desc *mdesc)
{
 /* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
 memblock_reserve(__pa(_sdata), _end - _sdata);
#else
 memblock_reserve(__pa(_stext), _end - _stext);
#endif
#ifdef CONFIG_BLK_DEV_INITRD
 /* FDT scan will populate initrd_start */
 if (initrd_start && !phys_initrd_size) {
     phys_initrd_start = __virt_to_phys(initrd_start);
     phys_initrd_size = initrd_end - initrd_start;
 }
 initrd_start = initrd_end = 0;
 if (phys_initrd_size &&
     !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
     pr_err("INITRD: 0x%08llx+0x%08lx is not a memory region - disabling initrd\n",
            (u64)phys_initrd_start, phys_initrd_size);
     phys_initrd_start = phys_initrd_size = 0;
 }
 if (phys_initrd_size &&
     memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
     pr_err("INITRD: 0x%08llx+0x%08lx overlaps in-use memory region - disabling initrd\n",
            (u64)phys_initrd_start, phys_initrd_size);
     phys_initrd_start = phys_initrd_size = 0;
 }
 if (phys_initrd_size) {
     memblock_reserve(phys_initrd_start, phys_initrd_size);

     /* Now convert initrd to virtual addresses */
     initrd_start = __phys_to_virt(phys_initrd_start);
     initrd_end = initrd_start + phys_initrd_size;
 }
#endif

 arm_mm_memblock_reserve();

 /* reserve any platform specific memblock areas */
 if (mdesc->reserve)
     mdesc->reserve();

 early_init_fdt_reserve_self();
 early_init_fdt_scan_reserved_mem();

 /* reserve memory for DMA contiguous allocations */
 dma_contiguous_reserve(arm_dma_limit);

 arm_memblock_steal_permitted = false;
 memblock_dump_all();
}

arm64下

void __init setup_arch(char **cmdline_p)
{
 /*  初始化memblock  */
 arm64_memblock_init( );

 /*  分页机制初始化  */
 paging_init();

 bootmem_init();
}
```c
# 初始化 buddy 管理
* 在 arm64 架构下,内核在start_kernel()->setup_arch()函数中依次完成了如下(代码所示)工作
* 前面我们的内核从start_kernel开始, 进入setup_arch(), 并完成了早期内存分配器的初始化和设置工作.
```c
void __init setup_arch(char **cmdline_p)
{
 /*  初始化memblock  */
 arm64_memblock_init( );

 /*  分页机制初始化  */
 paging_init();

 bootmem_init();
}
  • 内核setup_arch的最后通过bootmem_init中完成了内存数据结构的初始化(包括内存结点pg_data_t,
  • 内存管理域zone和页面信息page), 数据结构已经基本准备好了, 在后面为内存管理做得一个准备工作就是
  • 将所有节点的管理区都链入到zonelist中,便于后面内存分配工作的进行.
  • 内存节点pg_data_t中将内存节点中的内存区域zone按照某种组织层次存储在一个zonelist中.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值