linux3.10系统start_kernel初始化流程详解(一)

下面从start_kernel 开始分析一下linux3.10内核在s3c2440 cpu上面的整个初始化流程:

(1)lockdep_init();

是个空函数,定义在include/linux/lockdep.h 中

(2)smp_setup_processor_id();

这个设置对称处理器的函数对2440来说没有太大意义,源码在arch/arm/kernel/setup.c中:

void __init smp_setup_processor_id(void)
{

	int i;
	u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0;
	u32 cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
	cpu_logical_map(0) = cpu;
	for (i = 1; i < nr_cpu_ids; ++i)
		cpu_logical_map(i) = i == cpu ? 0 : i;

	printk(KERN_INFO "Booting Linux on physical CPU 0x%x\n", mpidr);
}

这边mpidr和cpu值都为0,nr_cpu_ids为1,只是调用cpu_logical_map(0)设置了__cpu_logical_map

(3)debug_objects_early_init();
(4)boot_init_stack_canary();
(5)cgroup_init_early();

上边三个函数都是空函数。

(6)local_irq_disable();

关闭中断:

#define local_irq_disable() \
	do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)

其中trace_hardirqs_off 是空函数,raw_local_irq_disable 调用arch_local_irq_disable,把cpsr寄存器的第7位设置为1,即把I标志位置1,关闭irq中断。

static inline void arch_local_irq_disable(void)
{
	unsigned long temp;
	asm volatile(
		"	mrs	%0, cpsr	@ arch_local_irq_disable\n" //  把cpsr放入第一个寄存器中,%0 代表从输出寄存器开始计数的第一个寄存器
		"	orr	%0, %0, #128\n"
		"	msr	cpsr_c, %0"    
		: "=r" (temp)    //输出寄存器,r选项为不指定寄存器,自动选择
		:          //输入寄存器,为空
		: "memory", "cc");  //可能会改变的寄存器
}
(7)boot_cpu_init(); //初始化标志cpu的变量
(8)page_address_init();//这个函数在这边为空
(9)setup_arch(&command_line);

这个函数里面做的事情比较多:

void __init setup_arch(char **cmdline_p)
{
	struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);
        先检查dtb表,看从dtb表中是否能找到匹配的machine
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
    如果在dtb中没有找到匹配的项,则再使用uboot 传过来的machine id来进行匹配
	machine_desc = mdesc;
	machine_name = mdesc->name;

	setup_dma_zone(mdesc);

	if (mdesc->restart_mode)  设置特定machine的reboot函数
		reboot_setup(&mdesc->restart_mode);
对struct mm_struct init_mm相关结构进行初始化,用于内存管理
	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	/* populate cmd_line too for later use, preserving boot_command_line */
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;

	parse_early_param();
尝试对从uboot 传过来的参数进行解析

	sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);

    在没有配置高端内存的情况下,其实没做什么
	sanity_check_meminfo();

初始化memblock结构体,该结构用于管理物理内存
	arm_memblock_init(&meminfo, mdesc);

	paging_init(mdesc);
	request_standard_resources(mdesc);

	if (mdesc->restart)
		arm_pm_restart = mdesc->restart;

	unflatten_device_tree();

	arm_dt_init_cpu_maps();
#ifdef CONFIG_SMP
	if (is_smp()) {
		smp_set_ops(mdesc->smp);
		smp_init_cpus();
	}
#endif

	if (!is_smp())
		hyp_mode_check();

	reserve_crashkernel();

#ifdef CONFIG_MULTI_IRQ_HANDLER
	handle_arch_irq = mdesc->handle_irq;
#endif

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
	conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
	conswitchp = &dummy_con;
#endif
#endif

	if (mdesc->init_early)
		mdesc->init_early();
}
(9.1)setup_processor();

 

先调用list = lookup_processor_type(read_cpuid_id());
找出对应的process_info,做一些初始化工作

该函数利用cpu id,去查找proc.info.init字段中对应的处理器信息,linux 编译的时候,会把特定架构处理器相关的信息编译到

proc.info.init字段,如下面的处理器定义:

        .section ".proc.info.init", #alloc, #execinstr

        .type   __arm926_proc_info,#object
__arm926_proc_info:
        .long   0x41069260                      @ ARM926EJ-S (v5TEJ)
        .long   0xff0ffff0
        .long   PMD_TYPE_SECT | \
                PMD_SECT_BUFFERABLE | \
                PMD_SECT_CACHEABLE | \   //这边的信息用于在head.s中初始化段页表,设置页表项

                PMD_BIT4 | \
                PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ
        .long   PMD_TYPE_SECT | \
                PMD_BIT4 | \
                PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ
        b       __arm926_setup
        .long   cpu_arch_name
        .long   cpu_elf_name
        .long   HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
        .long   cpu_arm926_name
        .long   arm926_processor_functions
        .long   v4wbi_tlb_fns
        .long   v4wb_user_fns
        .long   arm926_cache_fns
        .size   __arm926_proc_info, . - __arm926_proc_info

上面第一个字段即为处理器id 0x41069260,后面会调用cpu_init:

void notrace cpu_init(void)
{
	unsigned int cpu = smp_processor_id();
	struct stack *stk = &stacks[cpu];

	if (cpu >= NR_CPUS) {
		printk(KERN_CRIT "CPU%u: bad primary CPU number\n", cpu);
		BUG();
	}

	/*
	 * This only works on resume and secondary cores. For booting on the
	 * boot cpu, smp_prepare_boot_cpu is called after percpu area setup.
	 */
	set_my_cpu_offset(per_cpu_offset(cpu));

	cpu_proc_init();

	/*
	 * Define the placement constraint for the inline asm directive below.
	 * In Thumb-2, msr with an immediate value is not allowed.
	 */
#ifdef CONFIG_THUMB2_KERNEL
#define PLC	"r"
#else
#define PLC	"I"
#endif

	/*
	 * setup stacks for re-entrant exception handlers
	 */
下面的代码初始化三个堆栈
	__asm__ (
	"msr	cpsr_c, %1\n\t"
	"add	r14, %0, %2\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %3\n\t"
	"add	r14, %0, %4\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %5\n\t"
	"add	r14, %0, %6\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %7"
	    :
	    : "r" (stk),
	      PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
	      "I" (offsetof(struct stack, irq[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
	      "I" (offsetof(struct stack, abt[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
	      "I" (offsetof(struct stack, und[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
	    : "r14");
}

这个函数中利用struct stack初始化了irq,abt,undifined三种模式下的堆栈,每个堆栈其实只有12个字节:

struct stack {
    u32 irq[3];
    u32 abt[3];
    u32 und[3];
} ____cacheline_aligned;

linux其实在进入这几个模式下时,会很快切到SVC模式,所以不需担心堆栈溢出,具体参考下面的博文:

https://blog.csdn.net/boarmy/article/details/8652768

(9.2)setup_machine_fdt

接下来会调用setup_machine_fdt,通过比对dtb表中cpu的comptiable id来匹配machine,如果找不到,则用setup_machine_tags函数根据boot传过来的__machine_arch_type,即machine id来进行匹配,__atags_pointer是dtb

或者uboot 环境变量的物理地址,在head.s 中填入__atags_pointer字段:

struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	struct boot_param_header *devtree;
	struct machine_desc *mdesc, *mdesc_best = NULL;
	unsigned int score, mdesc_score = ~1;
	unsigned long dt_root;
	const char *model;

#ifdef CONFIG_ARCH_MULTIPLATFORM
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
	MACHINE_END

	mdesc_best = (struct machine_desc *)&__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys)
		return NULL;

	devtree = phys_to_virt(dt_phys);

	/* check device tree validity */
	if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)
		return NULL;

	/* Search the mdescs for the 'best' compatible value match */
	initial_boot_params = devtree;
	dt_root = of_get_flat_dt_root();
	for_each_machine_desc(mdesc) {
		查找root节点的第一个comptible的值,依次和machine 描述符中的
dt_compat进行对比,在内核编译的时候,需要使用DT_MACHINE_START 字段,来注册某而过machine
		score = of_flat_dt_match(dt_root, mdesc->dt_compat);
		if (score > 0 && score < mdesc_score) {
			mdesc_best = mdesc;
			mdesc_score = score;
		}
	}
	if (!mdesc_best) {
		const char *prop;
		long size;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	model = of_get_flat_dt_prop(dt_root, "model", NULL);
	if (!model)
		model = of_get_flat_dt_prop(dt_root, "compatible", NULL);
	if (!model)
		model = "";
	pr_info("Machine: %s, model: %s\n", mdesc_best->name, model);

	/* Retrieve various information from the /chosen node */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
查找chosen节点,把里面的bootargs 字段赋值给boot_command_line字段,chosen节点由uboot创建,
用来传递bootargs

	/* Initialize {size,address}-cells info */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);
初始化dt_root_size_cells和dt_root_addr_cells两个全局变量

	/* Setup memory, calling early_init_dt_add_memory_arch */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
查找memory节点,初始化struct meminfo meminfo

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc_best->nr;

	return mdesc_best;
}

DT_MACHINE_START会在.arch.info.init字段注册如下形式的machine 信息:

static const char *const s3c2440_dt_compat[] __initconst = {
    "samsung,SMDK2440",
    NULL
};
DT_MACHINE_START(S3C2440, "SMDK2440")

	.atag_offset	= 0x100,
	.dt_compat    = s3c2440_dt_compat,
	.init_irq	= s3c2440_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.init_time	= samsung_timer_init,
	.restart	= s3c244x_restart,
MACHINE_END

of_scan_flat_dt 等of开头的api都是操作dtb文件的相关api,有关dtb文件的格式,可以参照下面的博文:

https://blog.csdn.net/cc289123557/article/details/51782449

early_init_dt_scan_chosen 用来根据dtb的chosen节点中的bootargs字段,来初始化boot_command_line,

uboot中common/fdt_support.c文件中的fdt_chosen函数会设置chosen节点:

int fdt_chosen(void *fdt)
{
	int   nodeoffset;
	int   err;
	char  *str;		/* used to set string properties */

	err = fdt_check_header(fdt);
	if (err < 0) {
		printf("fdt_chosen: %s\n", fdt_strerror(err));
		return err;
	}

	/* find or create "/chosen" node. */
	nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
	if (nodeoffset < 0)
		return nodeoffset;

	str = getenv("bootargs");
	if (str) {
		err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
				  strlen(str) + 1);
		if (err < 0) {
			printf("WARNING: could not set bootargs %s.\n",
			       fdt_strerror(err));
			return err;
		}
	}

	return fdt_fixup_stdout(fdt, nodeoffset);
}
(9.3)parse_early_param();

内核编译的时候,会在.init.setup字段放入用early_param和__setup申明的结构字段,最终都会调用__setup_param,区别就是early_param会把early设置为1(猜测可能需要更早的调用),所以这边用bootargs和和该字段里面的每个结构进行比对,看哪个字段是否需要初始化,这里对弈解析出来的uboot的每个参数,都是调用do_early_param:

static int __init do_early_param(char *param, char *val, const char *unused)
{
	const struct obs_kernel_param *p;

	for (p = __setup_start; p < __setup_end; p++) {
		//if(p->str)
		//	pr_err("do_early_param get  '%s'\n",p->str);
		if ((p->early && parameq(param, p->str)) ||
		    (strcmp(param, "console") == 0 &&
		     strcmp(p->str, "earlycon") == 0)
		) {
		pr_err("do %s  address=%p\n",p->str,p->setup_func);
			if (p->setup_func(val) != 0)
				pr_err("Malformed early option '%s'\n", param);
		}
	}
	/* We accept everything at this stage. */
	return 0;
}

最终只匹配到console这个字段,去执行setup_func,这边其实什么也没做。

关于early_param相关的解释,可以参照下面的博文:

https://blog.csdn.net/tianya_lu/article/details/17291817

(9.4)arm_memblock_init(&meminfo, mdesc);

初始化该结构:

struct memblock {
    phys_addr_t current_limit;
    struct memblock_type memory;
    struct memblock_type reserved;
};

该结构用来进行物理内存的管理,其中memory表示的是当前的物理内存范围,而reserved表示的是那些不能被分配使用的内存:

void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
{
	int i;

	for (i = 0; i < mi->nr_banks; i++)
		memblock_add(mi->bank[i].start, mi->bank[i].size);
把物理地址0x30000000 到 0x34000000 加到memory域中
	/* 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);
把内核代码使用的区域加到reserve域中
#endif
#ifdef CONFIG_BLK_DEV_INITRD
	if (phys_initrd_size &&
	    !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
		pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",
		       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%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",
		       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;
	}
bootargs 中没有传递initd 参数,所以在前面的params_earliy中都没有初始化phys_initrd_size ,这边什么都不做,本来是用来reserve initrd ramdisk的
#endif

	arm_mm_memblock_reserve();
把页表的地址放入reserve 域

	arm_dt_memblock_reserve();
把dtb的地址放入reserve域

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

	/*
	 * reserve memory for DMA contigouos allocations,
	 * must come from DMA area inside low memory
	 */
dma_contiguous没有使用
	dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));

	arm_memblock_steal_permitted = false;
	memblock_allow_resize();
	memblock_dump_all();
}

最终memory和reserve的分布如下:

linux3.10系统start_kernel初始化流程详解(一)_第1张图片

可以看到dtb放在物理内存的顶端位置,为0x2000字节,而页表项则放在内核的下方,占0x4000字节,因为linux刚开始使用段映射,每1M有一个页表项,刚好4096个页表,每个页表4字节,总共刚好0x4000 字节。

关于Memblock的管理,可以参照下面的博文,写的非常详细:

https://blog.csdn.net/liuhangtiant/article/details/80561148

(9.5)paging_init(mdesc);

paging_init函数设计到linux系统内存管理初始化的过程,涵盖内容比较多,在另一篇博文里面单独记录:

https://blog.csdn.net/oqqYuJi12345678/article/details/96029177

(9.6)request_standard_resources(mdesc);

一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,这边用resource结构来描述该设置的这段线性空间,并把他们串接起来:

static void __init request_standard_resources(struct machine_desc *mdesc)
{
	struct memblock_region *region;
	struct resource *res;

	kernel_code.start   = virt_to_phys(_text);
	kernel_code.end     = virt_to_phys(_etext - 1);
	kernel_data.start   = virt_to_phys(_sdata);
	kernel_data.end     = virt_to_phys(_end - 1);

	for_each_memblock(memory, region) {
		res = alloc_bootmem_low(sizeof(*res));
		res->name  = "System RAM";
		res->start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
		res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
		res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;

		request_resource(&iomem_resource, res);

		if (kernel_code.start >= res->start &&
		    kernel_code.end <= res->end)
			request_resource(res, &kernel_code);
		if (kernel_data.start >= res->start &&
		    kernel_data.end <= res->end)
			request_resource(res, &kernel_data);
	}


下面的函数都没有定义
	if (mdesc->video_start) {
		video_ram.start = mdesc->video_start;
		video_ram.end   = mdesc->video_end;
		request_resource(&iomem_resource, &video_ram);
	}

	/*
	 * Some machines don't have the possibility of ever
	 * possessing lp0, lp1 or lp2
	 */
	if (mdesc->reserve_lp0)
		request_resource(&ioport_resource, &lp0);
	if (mdesc->reserve_lp1)
		request_resource(&ioport_resource, &lp1);
	if (mdesc->reserve_lp2)
		request_resource(&ioport_resource, &lp2);
}

从代码中可以看到,主的resource 结构是一个iomem_resource结构,像系统ram,代表的是一个设备,挂载在iomem_resource之后,而kernel data和text空间,占用的系统ram的空间,所以它又属于系统ram的范围,resource结构描述如下:

struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};
resource->name描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切;
resource->start描述设备实体在cpu总线上的线性起始物理地址;
resource->end描述设备实体在cpu总线上的线性结尾物理地址;
resource->flag描述这个设备实体的一些共性和特性的标志位;
resource->parent描述管理本设备实体的父类resource指针
resource->sibling描述比本设备实体物理地址大的下一个设备实体的resource指针[这是一个单向链表]
resource->child描述本设备实体可能还要更详细的描述本设备实体内部的一些物理地址区段,来让linux能够更清楚本设备实体的内部的级联关系,甚至可以将本设备实体内部的具有特色的区段的给它起个名字标示一下。

(9.7)unflatten_device_tree();

 

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, &of_allnodes,
				early_init_dt_alloc_memory_arch);

	/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
	of_alias_scan(early_init_dt_alloc_memory_arch);
}

函数 __unflatten_device_tree中利用dtb在of_allnodes下建立device node,of_alias_scan函数则扫描of_allnodes 的device node,把chosen 节点存储在of_chosen全局变量中,早期已经扫描chosen 节点取出了里面的bootargs。同样的,记录of_aliases节点,并把of_aliases节点中的一些信息存储到aliases_lookup链表中

具体的__unflatten_device_tree 如何利用dtb 建立device node的详解,可以参考下面的博文:

https://www.cnblogs.com/tureno/articles/6432924.html

需要注意的是,这边会解析DTS文件,保存到全局链表allnodes中,在调用.init_machine时,会根据allnodes中的信息注册平台总线和设备。值得注意的是,加载流程并不是按找从树根到树叶的方式递归注册,而是只注册根节点下的第一级子节点,第二级及之后的子节点暂不注册。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据allnodes节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。of_alias_scan中关于of_aliases节点的初始化以及使用方式,参考下面的博文:

https://blog.csdn.net/qq_16777851/article/details/88958098

(10)mm_init_owner(&init_mm, &init_task);什么都没做
(11)mm_init_cpumask(&init_mm); 什么都没做
(12)setup_command_line(command_line);

把bootargs 存入saved_command_line和static_command_line全局变量中。

(13)setup_nr_cpu_ids();

空函数,应该设置了smp 才会使用

(14)setup_per_cpu_areas();

setup_per_cpu_areas是为了对内核的内存管理(mm)进行初始化而调用的函数之一。
setup_per_cpu_areas函数为SMP的每个处理器生成per-cpu数据。
per-cpu数据按照不同的CPU类别使用,以将性能低下引发的缓存一致性(cache coherency)问题减小到最小。下面看一下在单核cpu上如何处理per_cpu相关的数据:

void __init setup_per_cpu_areas(void)
{
	const size_t unit_size =
		roundup_pow_of_two(max_t(size_t, PCPU_MIN_UNIT_SIZE,
					 PERCPU_DYNAMIC_RESERVE));
        计算需要分配的per_cpu空间
	struct pcpu_alloc_info *ai;
	void *fc;

	ai = pcpu_alloc_alloc_info(1, 1);分配一个pcpu_alloc_info 空间,只有一个units和group
        分配该per_cpu空间,后续从per_cpu请求的空间从 这块空间分配出
	fc = __alloc_bootmem(unit_size, PAGE_SIZE, __pa(MAX_DMA_ADDRESS));
	if (!ai || !fc)
		panic("Failed to allocate memory for percpu areas.");
	/* kmemleak tracks the percpu allocations separately */
	kmemleak_free(fc);
        设置pcpu_alloc_info中管理的内存空间大小
	ai->dyn_size = unit_size;
	ai->unit_size = unit_size;
	ai->atom_size = unit_size;
	ai->alloc_size = unit_size;
	ai->groups[0].nr_units = 1;
	ai->groups[0].cpu_map[0] = 0;
        利用ai初始化chunk并挂载进链表
	if (pcpu_setup_first_chunk(ai, fc) < 0)
		panic("Failed to initialize percpu areas.");
}

上面函数先初始化了一个pcpu_alloc_info结构,然后分配per_cpu空间,再利用pcpu_alloc_info结构中的信息,初始化chunk,并挂载到全局链表中。看一下如何初始化chunk:

int __init pcpu_setup_first_chunk(const struct pcpu_alloc_info *ai,
				  void *base_addr)
{
	static char cpus_buf[4096] __initdata;
	static int smap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
	static int dmap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
	size_t dyn_size = ai->dyn_size;
这边ai->static_size和ai->reserved_size都没有设置,为0
	size_t size_sum = ai->static_size + ai->reserved_size + dyn_size;
	struct pcpu_chunk *schunk, *dchunk = NULL;
	unsigned long *group_offsets;
	size_t *group_sizes;
	unsigned long *unit_off;
	unsigned int cpu;
	int *unit_map;
	int group, unit, i;

	cpumask_scnprintf(cpus_buf, sizeof(cpus_buf), cpu_possible_mask);



	/* process group information and build config tables accordingly */
	group_offsets = alloc_bootmem(ai->nr_groups * sizeof(group_offsets[0]));
	group_sizes = alloc_bootmem(ai->nr_groups * sizeof(group_sizes[0]));
	unit_map = alloc_bootmem(nr_cpu_ids * sizeof(unit_map[0]));
	unit_off = alloc_bootmem(nr_cpu_ids * sizeof(unit_off[0]));

	for (cpu = 0; cpu < nr_cpu_ids; cpu++)
		unit_map[cpu] = UINT_MAX;

	pcpu_low_unit_cpu = NR_CPUS;
	pcpu_high_unit_cpu = NR_CPUS;
这边利用两个for循环,来设置group 和group 下面的units,由于只有一个cpu,所以nr_groups和nr_units
应该都是1
	for (group = 0, unit = 0; group < ai->nr_groups; group++, unit += i) {
		const struct pcpu_group_info *gi = &ai->groups[group];

		group_offsets[group] = gi->base_offset;
		group_sizes[group] = gi->nr_units * ai->unit_size;

		for (i = 0; i < gi->nr_units; i++) {
			cpu = gi->cpu_map[i];
			if (cpu == NR_CPUS)
				continue;

			unit_map[cpu] = unit + i;
			unit_off[cpu] = gi->base_offset + i * ai->unit_size;

			/* determine low/high unit_cpu */
			if (pcpu_low_unit_cpu == NR_CPUS ||
			    unit_off[cpu] < unit_off[pcpu_low_unit_cpu])
				pcpu_low_unit_cpu = cpu;
			if (pcpu_high_unit_cpu == NR_CPUS ||
			    unit_off[cpu] > unit_off[pcpu_high_unit_cpu])
				pcpu_high_unit_cpu = cpu;
		}
	}
	pcpu_nr_units = unit;



	/* we're done parsing the input, undefine BUG macro and dump config */
#undef PCPU_SETUP_BUG_ON
	pcpu_dump_alloc_info(KERN_ERR, ai);

	pcpu_nr_groups = ai->nr_groups;
	pcpu_group_offsets = group_offsets;
	pcpu_group_sizes = group_sizes;
	pcpu_unit_map = unit_map;
	pcpu_unit_offsets = unit_off;

	/* determine basic parameters */
	pcpu_unit_pages = ai->unit_size >> PAGE_SHIFT;
	pcpu_unit_size = pcpu_unit_pages << PAGE_SHIFT;
	pcpu_atom_size = ai->atom_size;
	pcpu_chunk_struct_size = sizeof(struct pcpu_chunk) +
		BITS_TO_LONGS(pcpu_unit_pages) * sizeof(unsigned long);

	/*
	 * Allocate chunk slots.  The additional last slot is for
	 * empty chunks.
	 */
计算需要几个slots 来管理per_cpu内存
	pcpu_nr_slots = __pcpu_size_to_slot(pcpu_unit_size) + 2;
为pcpu_slot 结构分配内存
	pcpu_slot = alloc_bootmem(pcpu_nr_slots * sizeof(pcpu_slot[0]));
	for (i = 0; i < pcpu_nr_slots; i++)
		INIT_LIST_HEAD(&pcpu_slot[i]);初始化全局pcpu_slot链表

	/*
	 * Initialize static chunk.  If reserved_size is zero, the
	 * static chunk covers static area + dynamic allocation area
	 * in the first chunk.  If reserved_size is not zero, it
	 * covers static area + reserved area (mostly used for module
	 * static percpu allocation).
	 */
初始化一个静态的chunk
	schunk = alloc_bootmem(pcpu_chunk_struct_size);
	INIT_LIST_HEAD(&schunk->list);
base_addr就是之前分配的per_cpu空间
	schunk->base_addr = base_addr;
设置map数据,这边初始化smap,为128个,map数组用来表示当前chunk管理的per_cpu空间的使用情况
map[i]>0,则表示当前map[i]里面的空闲空间,map[i]<0,则表示当前map[i]使用掉的当前per_cpu空间大小
	schunk->map = smap;
	schunk->map_alloc = ARRAY_SIZE(smap);
	schunk->immutable = true; immutable为真表示该块空间已经分配好了物理空间
填充populated位表,为1表示空闲,0表示占用
	bitmap_fill(schunk->populated, pcpu_unit_pages);
        
	if (ai->reserved_size) {这边reserved_size为0,没有使用
		schunk->free_size = ai->reserved_size;
		pcpu_reserved_chunk = schunk;
		pcpu_reserved_chunk_limit = ai->static_size + ai->reserved_size;
	} else {
		schunk->free_size = dyn_size;设置free_size为之前分配的per_cpu空间的大小
		dyn_size = 0;			/* dynamic area covered */
	}
contig_hint为当前可分配的最大连续空间
	schunk->contig_hint = schunk->free_size;
static_size为0,所以map[0]为0
	schunk->map[schunk->map_used++] = -ai->static_size;
	if (schunk->free_size)
		schunk->map[schunk->map_used++] = schunk->free_size;
map[1]设置为当前per_cpu的空间,当前内存还没有分配,所以map[1]代表一大块内存
	/* init dynamic chunk if necessary */
	if (dyn_size) { dyn_size设置为0了,所以下面代码不走
		dchunk = alloc_bootmem(pcpu_chunk_struct_size);
		INIT_LIST_HEAD(&dchunk->list);
		dchunk->base_addr = base_addr;
		dchunk->map = dmap;
		dchunk->map_alloc = ARRAY_SIZE(dmap);
		dchunk->immutable = true;
		bitmap_fill(dchunk->populated, pcpu_unit_pages);

		dchunk->contig_hint = dchunk->free_size = dyn_size;
		dchunk->map[dchunk->map_used++] = -pcpu_reserved_chunk_limit;
		dchunk->map[dchunk->map_used++] = dchunk->free_size;
	}

	/* link the first chunk in */
	pcpu_first_chunk = dchunk ?: schunk; pcpu_first_chunk为schunk
	pcpu_chunk_relocate(pcpu_first_chunk, -1);把schunk根据free_size的大小,挂载到
pcpu_slot的全局链表中

	/* we're done */
	pcpu_base_addr = base_addr;
	return 0;
}

上面的函数向物理内存申请了一块空间,设置进chunk中,后面就可以利用pcpu_alloc从chunk中分配空间。

(15)smp_prepare_boot_cpu();
(16)build_all_zonelists(NULL, NULL);

对pglist_data下面的zonelists进行初始化。说一下内存管理域zone和zonelist的一些含义。

首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节.而对于2440这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构。

接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射。

zone对于uma系统来说,这已经够了,因为uma系统只有一个本地内存节点,所有zone的信息都存放在本地内存节点的zone成员中。

对于numa系统来说,除了本地内存节点,还可以存在一个或多个远端内存节点,本地内存节点的zone成员并不会存放远端内存节点的zone信息。所以,这里引入zonelist的概念,本地内存节点和远端内存节点的zone统一挂在zonelist链表上。
所以对于uma系统,这边只是把zone 又挂载到了zonelist->_zonerefs中,代码详解参看链接:

https://blog.csdn.net/liuhangtiant/article/details/80957313

(17)page_alloc_init();

该函数为系统设置一个page_alloc_cpu_notify回调函数,该函数用来实现CPU的关闭与使能。在一个MPP结构的处理器系统或者大型服务器中有大量的CPU,该函数可以临时打开或者关闭某些Core或者CPU,此时Linux系统会调用page_alloc_cpu_notify函数,但是在这边没有使用。

(18)parse_early_param();

这个参数解析函数在之前的setup_arch里面就有调用过,调用过一次以后就会在内部把静态变量done 设置为1,这边不再进行处理了

(19)parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   -1, -1, &unknown_bootoption);

依旧是对bootargs的解析处理。可以看到,这个函数传入的参数有__start___param,在./include/asm-generic/vmlinux.lds.h中定义:

 __param : AT(ADDR(__param) - LOAD_OFFSET) {                     \
                VMLINUX_SYMBOL(__start___param) = .;                    \
                *(__param)                                              \
                VMLINUX_SYMBOL(__stop___param) = .;                     \
        }

可以看到,在__start___param和__stop___param中间有一个__param的段,在内核代码中,使用module_param_call()这个宏,会把申明的结构放入__param这个段。

依次对bootargs的每个参数进行解析:

parse_args

      -------------->parse_one

static int parse_one(char *param,
		     char *val,
		     const char *doing,
		     const struct kernel_param *params,
		     unsigned num_params,
		     s16 min_level,
		     s16 max_level,
		     int (*handle_unknown)(char *param, char *val,
				     const char *doing))
{
	unsigned int i;
	int err;

	/* Find parameter */
	for (i = 0; i < num_params; i++) {
		if (parameq(param, params[i].name)) {
			if (params[i].level < min_level
			    || params[i].level > max_level)
				return 0;
			/* No one handled NULL, so do it here. */
			if (!val && params[i].ops->set != param_set_bool
			    && params[i].ops->set != param_set_bint)
				return -EINVAL;
			pr_err("handling %s with %p\n", param,params[i].ops->set);
			mutex_lock(¶m_lock);
			err = params[i].ops->set(val, ¶ms[i]);
			mutex_unlock(¶m_lock);
			return err;
		}
	}

	if (handle_unknown) {
		pr_err("doing %s: %s='%s'\n", doing, param, val);
		return handle_unknown(param, val, doing);
	}

	pr_debug("Unknown argument '%s'\n", param);
	return -ENOENT;
}

可以看到,先用bootargs中的参数和在__param段中定义的结构名进行比对,比对成功,就调用注册的函数:

params[i].ops->set(val, ¶ms[i]);,这里传入的ubi.mtd 会被匹配到,和build.c 中的module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 000);申请匹配,调用ubi_mtd_param_parse,该函数会把ubi.mtd 后面的参数3放入mtd_dev_param[0]->name[0]静态变量中。如果参数不匹配,就会调用handle_unknown,即unknown_bootoption。

unknown_bootoption

         ------------>obsolete_checksetup

先调用obsolete_checksetup,把bootargs再一次和.init.setup段中注册的结构表进行对比,匹配则调用注册的函数,这边依次匹配了  "root="  "rootfstype"  "console=" 三个参数,匹配了下面的几个结构:

__setup("console=", console_setup);  console_setup回调函数把console参数放入console_cmdline[0]中

__setup("root=", root_dev_setup);  saved_root_name中放入根目录路径

__setup("rootfstype=", fs_names_setup);    root_fs_names中放入rootfstype

如果都不匹配,则把剩下的bootargs 根据情况放入envp_init数组或者argv_init数组。

相关资料可查看下面的链接:

https://blog.csdn.net/Eric_tao/article/details/5708894

未完待续。。。。。。。。。。。。。。。。。。。。。

你可能感兴趣的:(linux,start_kernel,系统初始化,linux初始化,初始化流程)