Linux内核启动过程分析
Linux内核启动过程分析
这次我们使用gdb跟踪Linux内核的启动来分析其启动过程,内核版本3.18.6
Linux内核的启动从src/init/main.c的start_kernel函数开始,因此使用gdb在start_kernel函数下断点并进行跟踪
start_kernel的代码比较多,近200行,大多数是各个系统模块的初始化,本文不叙述这部分内容,本文需要注意的代码位于start_kernel函数的开头部分和结尾部分,粘贴如下
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
set_task_stack_end_magic(&init_task);
........
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
set_task_stack_end_magic(&init_task);
这句是内核启动的第一步,init_task是一个声明在init_task.c中的全局变量,具体定义位于init_task.h,其内容就是一个完整的进程控制块,这句中的函数名可以推测出其作用,即标记处进程栈的底部,其具体实现位于fork.c和sched.c中,sched.c中实现的是设置进程栈底的功能
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
}
到这儿就不难理解了。init_task这个手动设置出来的进程控制块实际上就是0号进程,也就是Linux后续系统进程和用户进程的起点
设置完0号进程以后,start_kernel执行了大量运行环境的初始化,包括中断门初始化,SMP结构初始化,内存分页初始化,终端初始化等等,到最后一行的rest_init,在rest_init中,Linux内核将启动第一个用户态进程,即init进程
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
rest_init首先初始化了调度器,随后以kernel_init作为处理函数构造出了一个内核线程,接着在启动调度器进行一次调度,在代码注释里面说的很清楚
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
而cpu_startup_entry这个函数最终会进入cpu_idle_loop();这样一个死循环,
kernel_init也就是1号init进程,即一切用户态进程的祖先
static int __ref kernel_init(void *unused)
{
......
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d). Attempting defaults...\n",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
可以看出kernel_init和linux的initrd机制有关联,最后按照/sbin/init/;/etc/init/;/bin/init/;/bin/sh;的顺序寻找init执行,我们之前的menuOS就是通过initrd来执行的,可以将menuos换成其他C代码作为init执行
吴韬 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000