接上篇:linux内核启动过程分析(二)
2.linux启动过程C语言阶段
initmain.c -> void start_kernel(void)
本文目的在于理清内核的启动流程,再者好多函数本人也未做了解,所以下面只对部分函数做简要说明。注:部分解释来源于网络。
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
lockdep_init();
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
......
rest_init();
}
Linux中有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)。其中之前的分析的所有启动阶段的代码以及start_kernel代码都是在0号进程下运行的。0号进程最终在《kernelschedidle.c》的cpu_idle_loop中调用一个while(1)死循环用作空闲进程。当无其它进程运行时,系统将调用空闲进程运行。反之空闲进程将被挂起,切换至其它进程运行。
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
smpboot_thread_init();
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();
cpu_startup_entry(CPUHP_ONLINE);
}
上面rest_init函数中系统通过调用kernel_thread来创建kernel_init(pid=1)、kthreadd(pid=2)。所以可以看出1号、2号进程的父进程是0号进程。1号进程最终会调用文件系统中的init进程,init进程不会返回,并且管理着用户空间的所有进程,是所有用户进程都直接或间接的来自init进程的创建。
kernel_init进程《initmain.c》
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
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;
panic("Requested init %s failed (error %d).",
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.");
}
static noinline void __init kernel_init_freeable(void)
{
wait_for_completion(&kthreadd_done);
gfp_allowed_mask = __GFP_BITS_MASK;
set_mems_allowed(node_states[N_MEMORY]);
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
do_basic_setup();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console.n");
(void) sys_dup(0);
(void) sys_dup(0);
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
integrity_load_keys();
load_default_modules();
}
kthreadd进程《kernelkthread.c》是所有内核线程的父线程,用来管理、创建和调度其它内核线程kernel_thread,会循环执行一个kthreadd的函数,该函数的作用就是查询kthread_create_list全局链表中维护的kthread, 当kthread_create_list非空时调用kernel_thread创建相应的内核线程,为空时则进行一次系统调度,因此所有的内核线程都是直接或者间接的以kthreadd为父进程
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_states[N_MEMORY]);
current->flags |= PF_NOFREEZE;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
上面可以看出这个内核进程的创建过程主要集中在kthread_create_list这个全局的链表中,那么这个kthread_create_list链表是在什么地方添加kthread_create_info 信息的呢?在《kernelkthread.c》搜索kthread_create_list可以在文件的开头看见其定义,并且用static进行了修饰,说明kthread_create_list只是当前的文件中被使用,当前文件在继续搜索可以找到在kthread_create_on_node函数中利用list_add_tail将一个kthread_create_info添加到了kthread_create_list的尾部。所有要想创建一个内核进程可以通过调用kthread_create_on_node函数来实现。
kthread_create_list定义的展开
static LIST_HEAD(kthread_create_list);
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name)
struct list_head name = LIST_HEAD_INIT(name)
struct list_head {
struct list_head *next, *prev;
};
展开的结果:
第一层
static struct list_head kthread_create_list = LIST_HEAD_INIT(kthread_create_list)
第二层
static struct list_head kthread_create_list = {
next = &kthread_create_list,
prev = &kthread_create_list,
};
至此内核启动的大致流程已完…



