linux kernel内存初始化过程

基于linux4.14.79内核,TI AM5728平台。

0.在boot中对内存块的大小设置

spl/ubbot中对内存的配置

board_init_f()
    sdram_init()
        dmm_init(DMM_BASE);
            emif_get_dmm_regs(&lisa_map_regs);    //根据board得到内存lisa_map
            struct dmm_lisa_map_regs *hw_lisa_map_regs = (struct dmm_lisa_map_regs *)DMM_BASE;
            //把lisa_map写到dmm_lisa_map_i寄存器中
            writel(lisa_map_regs->dmm_lisa_map_3,&hw_lisa_map_regs->dmm_lisa_map_3);
            writel(lisa_map_regs->dmm_lisa_map_3,&hw_lisa_map_regs->dmm_lisa_map_3);
            writel(lisa_map_regs->dmm_lisa_map_3,&hw_lisa_map_regs->dmm_lisa_map_3);
            writel(lisa_map_regs->dmm_lisa_map_3,&hw_lisa_map_regs->dmm_lisa_map_3);
        gd->ram_size = omap_sdram_size();    //读取dmm_lisa_map_i寄存器的lisa_map,计算出sdram的大小
    setup_dest_addr()
        gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
board_init_r()
    dram_init_banksize()
        gd->bd->bi_dram[0].start = CONFIG_SYS_SDRAM_BASE;
	    gd->bd->bi_dram[0].size = get_effective_memsize();//gd->ram_size

 boot过程中加载fdt后把gd中保存的dram信息写入memory reg属性:

do_bootm_linux
    boot_prep_linux
        image_setup_linux
            boot_relocate_fdt    //重定位设备树
            image_setup_libfdt    //设置设备树
                fdt_root    //更新根节点串口号
                fdt_chosen    //更新启动参数
                fdt_fixup_ethernet    //更新网卡mac地址
                arch_fixup_fdt    //更新内存信息
                    for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
		                start[bank] = bd->bi_dram[bank].start;
		                size[bank] = bd->bi_dram[bank].size;
                    }
                    fdt_fixup_memory_banks    //设置设备内存reg属性
                

1.内核初始化过程中跟内存相关的步骤:

start_kernel
    setup_arch
        setup_machine_fdt    //根据设备树初始化machine_desc结构体,包括解析memory节点
        adjust_lowmem_bounds();    //调整计算低端内存和高端内存的位置
        arm_memblock_init(mdesc);    //内存块初始化!!包括保留内存,连续内存等
        adjust_lowmem_bounds();    //调整计算低端内存和高端内存的位置

2.解析设备树memory节点:

setup_machine_fdt
    early_init_dt_scan_nodes
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
            early_init_dt_scan_memory
                early_init_dt_add_memory_arch
                    memblock_add
                        memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
                            memblock_insert_region

3.调整低端内存和高端内存的位置:adjust_lowmem_bounds

下面是部分关键代码

void __init adjust_lowmem_bounds(void)
{
	u64 vmalloc_limit;
	phys_addr_t lowmem_limit = 0;

	vmalloc_limit = (u64)(uintptr_t)vmalloc_min - PAGE_OFFSET + PHYS_OFFSET;
	for_each_memblock(memory, reg) {
		phys_addr_t block_start = reg->base;
		phys_addr_t block_end = reg->base + reg->size;

		if (reg->base < vmalloc_limit) {
			if (block_end > lowmem_limit)
				lowmem_limit = min_t(u64,
							 vmalloc_limit,
							 block_end);
                block_start,block_end,lowmem_limit);
			}
		}
	}
	arm_lowmem_limit = lowmem_limit;
	high_memory = __va(arm_lowmem_limit - 1) + 1;	
}

可以看到,主要是根据当前memory块,和vmalloc_limit进行比较得到high_memory即高端内存和低端内存的分界地址。而vmalloc_limit又由vmalloc_min 决定,vmalloc_min 默认是240M(3.3版本以前为128M),可以通过在启动参数添加 vmalloc_min=size字段设置其大小,范围是16M~984M。

4.内存块初始化!!!:arm_memblock_init

void __init arm_memblock_init(const struct machine_desc *mdesc)
{
	/* Register the kernel text, kernel data and initrd with memblock. */
	memblock_reserve(__pa(KERNEL_START), KERNEL_END - KERNEL_START);//预留内核镜像内存
	arm_initrd_init();
	arm_mm_memblock_reserve();    //预留vector page内存

	/* reserve any platform specific memblock areas */
	if (mdesc->reserve)
		mdesc->reserve();    //预留架构相关的内存,这里包括内存屏障和安全ram

	early_init_fdt_reserve_self();    //预留设备树自身加载所占内存
	early_init_fdt_scan_reserved_mem();    //初始化设备树扫描reserved-memory节点预留内存

	/* reserve memory for DMA contiguous allocations */
	dma_contiguous_reserve(arm_dma_limit);    //内核配置参数或命令行参数中预留的DMA连续内存

	arm_memblock_steal_permitted = false;
	memblock_dump_all();
}

上面代码可以看到设置保留内存的4种方法:
machine的reserve接口中设置;
设备树reserved-memory节点中设置;
配置文件:Device Drivers> Generic Driver Options> DMA Contiguous Memory Allocator;
启动参数添加字段 mem=size;

 

5.设备树中的预留内存:early_init_fdt_scan_reserved_mem

官方文档:linux-4.14.79\Documentation\devicetree\bindings\reserved-memory\reserved-memory.txt

CMA模块学习笔记:http://www.wowotech.net/memory_management/cma.html

主要注意的是  "no-map"   "reusable"     "linux,cma-default"   "linux,dma-default"这四个属性

no-map (optional) - empty property
    - Indicates the operating system must not create a virtual mapping
      of the region as part of its standard mapping of system memory,
      nor permit speculative access to it under any circumstances other
      than under the control of the device driver using the region.
reusable (optional) - empty property
    - The operating system can use the memory in this region with the
      limitation that the device driver(s) owning the region need to be
      able to reclaim it back. Typically that means that the operating
      system can use that region to store volatile or cached data that
      can be otherwise regenerated or migrated elsewhere.

Linux implementation note:
- If a "linux,cma-default" property is present, then Linux will use the
  region for the default pool of the contiguous memory allocator.

- If a "linux,dma-default" property is present, then Linux will use the
  region for the default pool of the consistent DMA allocator.

我的理解:(这里只讨论compatible = "shared-dma-pool";属性的节点)

用于shared-dma-pool的预留内存节点必须(且只能)no-map reusable其中之一:
包含了no-map 属性的预留内存不能被内核映射为系统内存的一部分,需要从系统内存中分离出来,作为DMA的专用预留内存,如果还包含了linux,dma-default属性,内核则使用这段预留内存区域作为DMA的默认内存。
而包含了reusable属性的预留内存不用从系统内存中分离出来,在驱动不使用这些内存的时候,OS可以使用这些内存(当然有限制条件),而当驱动从这个CMA area分配memory的时候,OS可以reclaim这些内存,让驱动可以使用它。如果还包含了linux,cma-default属性,系统将默认使用该连续内存进行分配。

/ {
	#address-cells = <1>;
	#size-cells = <1>;

	memory {
		reg = <0x40000000 0x40000000>;
	};
    
    reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

        dsp2_memory_region: dsp2-memory@9f000000 {
            compatible = "shared-dma-pool";
            reg = <0x0 0x9f000000 0x0 0x800000>;
            reusable;
            status = "okay";
        };
		
        cma_default_region1: cma_default_region@eec00000 {
            compatible = "shared-dma-pool";
            reg = <0x0 0xeec00000 0x0 0x10000000>;
            reusable;
            linux,cma-default;
            status = "okay";
        };
        
        cma_default_region2: cma_default_region@ac000000 {
            compatible = "shared-dma-pool";
            reg = <0x0 0xac000000 0x0 0xb000000>;
            no-map;
            linux,dma-default;
            status = "okay";
        };
    };
};

这里dsp2_memory_region表示一段在设备驱动程序不使用时系统可供系统使用的cma连续内存。
cma_default_region1和dsp2_memory_region一样,同时作为默认的连续内存区域来分配。
cma_default_region2表示一段从系统内存中分离出来的默认的dma专用预留内存。


early_init_fdt_scan_reserved_mem
    fdt_init_reserved_mem
        __reserved_mem_init_node

//这里会遍历__reservedmem_of_table段根据compatible匹配对应的设备并调用其初始化函数,以"shared-dma-pool"为例,会依次调用rmem_cma_setup和rmem_dma_setup,因为以下2个声明都是shared-dma-pool:
RESERVEDMEM_OF_DECLARE(dma, "shared-dma-pool", rmem_dma_setup);
RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);

static int __init rmem_dma_setup(struct reserved_mem *rmem)
{
	unsigned long node = rmem->fdt_node;
    //有reusable则返回
	if (of_get_flat_dt_prop(node, "reusable", NULL))    
		return -EINVAL;

#ifdef CONFIG_ARM
    //没有no-map则返回
	if (!of_get_flat_dt_prop(node, "no-map", NULL)) {    
		pr_err("Reserved memory: regions without no-map are not yet supported\n");
		return -EINVAL;
	}
    //有"linux,dma-default"则设为默认dma保留内存
	if (of_get_flat_dt_prop(node, "linux,dma-default", NULL)) {    
		WARN(dma_reserved_default_memory,
		     "Reserved memory: region for default DMA coherent area is redefined\n");
		dma_reserved_default_memory = rmem;
	}
#endif
	rmem->ops = &rmem_dma_ops;
	pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n",
		&rmem->base, (unsigned long)rmem->size / SZ_1M);
	return 0;
}

static int __init rmem_cma_setup(struct reserved_mem *rmem)
{
	phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
	phys_addr_t mask = align - 1;
	unsigned long node = rmem->fdt_node;
	struct cma *cma;
	int err;
    //没有"reusable"或有"no-map"属性则返回
	if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
	    of_get_flat_dt_prop(node, "no-map", NULL))
		return -EINVAL;

	if ((rmem->base & mask) || (rmem->size & mask)) {
		pr_err("Reserved memory: incorrect alignment of CMA region\n");
		return -EINVAL;
	}
    //初始化cma
	err = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, &cma);
	if (err) {
		pr_err("Reserved memory: unable to setup CMA region\n");
		return err;
	}
	/* Architecture specific contiguous memory fixup. */
	dma_contiguous_early_fixup(rmem->base, rmem->size);
    //有"linux,cma-default"属性则设为默认dma连续内存
	if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))
		dma_contiguous_set_default(cma);

	rmem->ops = &rmem_cma_ops;
	rmem->priv = cma;

	pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
		&rmem->base, (unsigned long)rmem->size / SZ_1M);

	return 0;
}

int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,
				 unsigned int order_per_bit,
				 const char *name,
				 struct cma **res_cma)
{
	struct cma *cma;
    ...
    ...
	//设置cma全局结构体并增加全局计数:cma_area_count
	cma = &cma_areas[cma_area_count];
	cma->name = name;
	cma->base_pfn = PFN_DOWN(base);
	cma->count = size >> PAGE_SHIFT;
	cma->order_per_bit = order_per_bit;
	*res_cma = cma;
	cma_area_count++;
	totalcma_pages += (size / PAGE_SIZE);

	return 0;
}


6.命令行参数和配置参数预留cma 连续内存:dma_contiguous_reserve

void __init dma_contiguous_reserve(phys_addr_t limit)
{
	phys_addr_t selected_size = 0;
	phys_addr_t selected_base = 0;
	phys_addr_t selected_limit = limit;
	bool fixed = false;

	pr_debug("%s(limit %08lx)\n", __func__, (unsigned long)limit);

	if (size_cmdline != -1) {    //level1:命令行设置size
		selected_size = size_cmdline;
		selected_base = base_cmdline;
		selected_limit = min_not_zero(limit_cmdline, limit);
		if (base_cmdline + size_cmdline == limit_cmdline)
			fixed = true;
	} else {                    //level2:配置参数设置size
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
		selected_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
		selected_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
		selected_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
		selected_size = max(size_bytes, cma_early_percent_memory());
#endif
	}
    /*当设备树里含cma共享连续内存并设置为默认时,这里将不进行任何操作*/
	if (selected_size && !dma_contiguous_default_area) {
		pr_debug("%s: reserving %ld MiB for global area\n", __func__,
			 (unsigned long)selected_size / SZ_1M);

		dma_contiguous_reserve_area(selected_size, selected_base,
					    selected_limit,
					    &dma_contiguous_default_area,
					    fixed);
	}
}

这里可以看出预留dma连续内存的优先级为:设备树(reusable;linux,cma-default;) > 命令行 > 配置参数

函数调用关系:

dma_contiguous_reserve
    dma_contiguous_reserve_area
        cma_declare_contiguous 
           cma_init_reserved_mem

7.设备驱动程序申请连续内存

g_dmagpmc_dev->dma_buf = dma_alloc_coherent(NULL, GPMC_MEMORY_SIZE, &g_dmagpmc_dev->dma_addr, GFP_KERNEL);
    dma_alloc_attrs
        ops->alloc
            arm_dma_alloc
                __dma_alloc
                    
static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
			 gfp_t gfp, pgprot_t prot, bool is_coherent,
			 unsigned long attrs, const void *caller)
{
    ...
    allowblock = gfpflags_allow_blocking(gfp);
	cma = allowblock ? dev_get_cma_area(dev) : false;

	if (cma)    //最高优先级分配cma连续内存
		buf->allocator = &cma_allocator;
	else if (is_coherent)
		buf->allocator = &simple_allocator;
	else if (allowblock)
		buf->allocator = &remap_allocator;
	else
		buf->allocator = &pool_allocator;

	addr = buf->allocator->alloc(&args, &page);
    ...
}

buf->allocator->alloc(&args, &page);
    cma_allocator_alloc
        __alloc_from_contiguous
            dma_alloc_from_contiguous
                

static void *__alloc_from_contiguous(struct device *dev, size_t size,
				     pgprot_t prot, struct page **ret_page,
				     const void *caller, bool want_vaddr,
				     int coherent_flag, gfp_t gfp)
{
	unsigned long order = get_order(size);
	size_t count = size >> PAGE_SHIFT;
	struct page *page;
	void *ptr = NULL;
    //1.从cma连续内存中分配内存
	page = dma_alloc_from_contiguous(dev, count, order, gfp);
	if (!page)
		return NULL;
	__dma_clear_buffer(page, size, coherent_flag);
	if (!want_vaddr)
		goto out;
    //重映射
	if (PageHighMem(page)) {    //2.高端内存重映射
		ptr = __dma_alloc_remap(page, size, GFP_KERNEL, prot, caller);
		if (!ptr) {
			dma_release_from_contiguous(dev, page, count);
			return NULL;
		}
	} else {
		__dma_remap(page, size, prot);
		ptr = page_address(page);
	}
 out:
	*ret_page = page;
	return ptr;
}
        
cma_alloc(dev_get_cma_area(dev), count, align, gfp_mask);
    alloc_contig_range
        start_isolate_page_range
            __alloc_contig_migrate_range

__dma_alloc_remap
    dma_common_contiguous_remap
        __dma_common_pages_remap
            get_vm_area_caller    //在vmalloc中获取一段连续的虚拟内存空间
            map_vm_area        //映射dma内存到虚拟内存

 

你可能感兴趣的:(内存管理,linux,内核驱动)