(注)本文源码基于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公众号,获取更多精彩内容>>>>