【linux kernel】start_kernel函数的早期操作

一、开篇

(注)本文源码基于linux内核版本:4.1.15


start_kernel()函数的开始处,定义了两个变量:

	char *command_line;
	char *after_dashes;

第一个表示指向内核命令行的指针,第二个用于包含parse_args()函数的结果,该函数解析带有name=value形式参数的输入字符串,查找特定的关键字并调用正确的处理程序。

接着,start_kernel()函数会调用set_task_stack_end_magic()函数。该函数获取init任务的地址并设置STACK _END_MAGIC(0x57AC6E9D)作为它的“魔术号”,以防止堆栈溢出。定义如下:

void set_task_stack_end_magic(struct task_struct *tsk)
{
	unsigned long *stackend;

	stackend = end_of_stack(tsk);
	*stackend = STACK_END_MAGIC;	/* for overflow detection */
}

start_kenrel()调用这个函数时传入的参数是init_task全局变量的地址,init task表示初始任务结构,其定义如下(/init/init_task.c):

struct task_struct init_task = INIT_TASK(init_task);

struct task_struct任务结构体存储了关于进程的所有信息,该结构体非常大。定义在(/include/linux/sched.c)文件中,该结构体包含100多个字段,堪称linux内核中的最大结构定义啦。

set_task_stack_end_magic()函数中,使用end_of_stack()函数获取到init_task代表的线程堆栈上最后一个可用的地址,然后将STACK _END_MAGIC(0x57AC6E9D)设置到该地址中。end_of_stack()函数定义如下:

static inline unsigned long *end_of_stack(struct task_struct *p)
{
#ifdef CONFIG_STACK_GROWSUP
	return (unsigned long *)((unsigned long)task_thread_info(p) + THREAD_SIZE) - 1;
#else
	return (unsigned long *)(task_thread_info(p) + 1);
#endif
}

从以上代码可见,end_of_stack()的返回值由CONFIG_STACK_GROWSUP宏来管控,本文以ARM架构为例,其堆栈是向下生长,故而没有定义CONFIG_STACK_GROWSUP宏,所以end_of_stack()函数将执行如下代码:

	return (unsigned long *)(task_thread_info(p) + 1);

task_thread_info()返回用INIT_TASK填充的堆栈:

#define task_thread_info(task)  ((struct thread_info *)(task)->stack)

接下来,start_kernel()函数将调用smp_setup_processor_id()函数,这是一个架构相关函数,在ARM架构下,其内容如下(/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;

   /* 清除启动cpu时的cpu偏移量,以避免由于过早使用percpu变量而引起的挂起 */
	set_my_cpu_offset(0);
	
    /* 在控制台上打印出与CPU相关的信息 */
	pr_info("Booting Linux on physical CPU 0x%x\n", mpidr);
}

start_kernel()的下一个函数是debug_objects_early_init()。定义如下(/lib/debugobjects.c):

void __init debug_objects_early_init(void)
{
	int i;
	
    /* 初始化对象hash表锁 */
	for (i = 0; i < ODEBUG_HASH_SIZE; i++)
		raw_spin_lock_init(&obj_hash[i].lock);
	
    /* 将obj_static_pool对象链接到obj_pool中 */
	for (i = 0; i < ODEBUG_POOL_SIZE; i++)
		hlist_add_head(&obj_static_pool[i].node, &obj_pool);
}

该函数在linux内核早期引导期间调用,用于初始化哈希表结构并将静态对象池对象(obj_static_pool)链接到obj_pool中。在debug_objects_early_init()调用之后,对象跟踪器就完全可以运行了。obj_static_pool定义如下:

static struct debug_obj		obj_static_pool[ODEBUG_POOL_SIZE] __initdata;

debug_objects_early_init()函数之后,将调用boot_init_stack_canary()函数(注:该函数是一个架构相关函数),它用-fstack-protector gcc特性的canary值填充当前任务的->stack_canary值。这个操作依赖于CONFIG_CC_STACKPROTECTOR配置选项,如果该选项被禁用,boot_init_stack_canary()不做任何事情。如下代码(/arch/arm/include/asm/stackproctector.h):

static __always_inline void boot_init_stack_canary(void)
{
	unsigned long canary;

	get_random_bytes(&canary, sizeof(canary));
	canary ^= LINUX_VERSION_CODE;

	current->stack_canary = canary;
	__stack_chk_guard = current->stack_canary;
}

接着,start_kernel()函数将调用boot_cpu_init()函数了,该函数定义如下:

static void __init boot_cpu_init(void)
{
	int cpu = smp_processor_id();
	/* Mark the boot cpu "present", "online" etc for SMP and UP case */
	set_cpu_online(cpu, true);
	set_cpu_active(cpu, true);
	set_cpu_present(cpu, true);
	set_cpu_possible(cpu, true);
}

boot_cpu_init()函数用于激活第一个处理器,在该函数中,首先调用smp_processor_id()获取当前CPU的ID,然后, 将启动cpu标记为“present”、“online”、“active”、“possible”四种状态。

接着,会调用:

pr_notice("%s", linux_banner);

打印出linux内核的banner信息。pr_notice()本质是printk()函数的封装,如下代码:

#define pr_notice(fmt, ...) \
    printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)

在linux内核启动过程中,会打印出下图所示的信息,正是由这行代码完成。

二、文末总结

文本描述了start_kernel()函数开始的环节,由于这些函数较短,故把他们都写在了一起。有些函数与具体架构相关,因此在不同架构和不同linux版本下可能会有不同情况,故而需要结合着linux内核源码来分析。


搜索/关注【嵌入式小生】wx公众号,获取更多精彩内容>>>>
请添加图片描述

你可能感兴趣的:(小生聊【linux,kernel】,linux,arm开发,linux,内核,linux,kernel,start_kernel)