Linux内核启动过程的分析

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)
",
               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...
",
            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

原文地址:https://www.cnblogs.com/current/p/4357885.html