linux内核启动分析(2)

-----如下内容为从网络上整理所得------node

 

主要介绍kernel_init线程(函数),这个线程在rest_init函数中被建立,kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。程序员

static int __init kernel_init(void * unused)
{
    lock_kernel();
  //锁住内核
    set_mems_allowed(node_states[N_HIGH_MEMORY]);
  //init能够在任何节点(node)分配到内存页
    set_cpus_allowed_ptr(current, cpu_all_mask);
  //init能够在任何CPU上运行.
    init_pid_ns.child_reaper = current;
    //把当前进程设为接受其余孤儿进程的进程
    
    cad_pid = task_pid(current);

    /*SMP系统作准备,激活全部CPU,并开始SMP系统的调度*/
    smp_prepare_cpus(setup_max_cpus);
    do_pre_smp_initcalls();
    start_boot_trace();
    smp_init();
    sched_init_smp();

    do_basic_setup();
    //驱动程序和内核子系统的通常初始化,下面会详解

    //检查是否有早期用户空间的init程序。若是有,让其执行
    if (!ramdisk_execute_command)ramdidk_execute_command="/init";
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)
    {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }
    //最后调用init_post,启动进程负责用户空间的初始化
    init_post();
    return 0;
}

 

在内核init线程的最后执行了init_post函数,在这个函数中真正启动了用户空间进程init,详解以下:网络

static noinline int init_post(void)
        __releases(kernel_lock)
{
    /* need to finish all async __init code before freeing the memory */
    async_synchronize_full();
    free_initmem();
    unlock_kernel();
    
    mark_rodata_ro();
    //经过修改页表,保证只读数据段为只读属性。大部分构架为空函数。

    system_state = SYSTEM_RUNNING;
    //设置系统状态为运行状态

    numa_default_policy();
    //设定NUMA系统的内存访问策略为默认

    //打开根文件系统中的 /dev/console , 此处不可失败
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    /*复制两次标准输入(0)的文件描述符
     *它是上面打开的/dev/console,也就是系统控制台):
     *一个做为标准输出(1)
     *一个做为标准出错(2)
     *如今标准输入、标准输出、标准出错都是/dev/console了。
     *这个console在内核启动参数中能够配置为某个串口(ttySn、ttyOn等等),
     *也能够是虚拟控制台(tty0)。
     *因此咱们就在串口或者显示器上看到了以后的系统登陆提示。
    **/
    (void) sys_dup(0);
    (void) sys_dup(0);
    
    //设置当前进程(init)为不能够杀进程(忽略致命的信号)
    current->signal->flags |= SIGNAL_UNKILLABLE;
    
    //若是ramdisk_execute_command有指定的init程序,就执行它。
    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
        printk(KERN_WARNING "Failed to execute %s\n",         
                                ramdisk_execute_command);
    }
    //若是execute_command有指定的init程序,就执行它。
    if (execute_command) {
                run_init_process(execute_command);
                printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                        "defaults...\n", execute_command);
    }
    /*在检查完ramdisk_execute_command和execute_command为空的状况下,
     *顺序执行如下初始化程序:若是都没有找到就打印错误信息。
     *这也是咱们作系统移植的时候常常碰到的错误信息,出现这个信息颇有多是:
     *一、你的启动参数配置有问题,经过 指定了init程序,可是没有找到,
     *    且默认的那四个程序也不在文件系统中。
     *二、文件系统挂载有问题,文件不存在
     *三、init程序没有执行权限
    **/
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

    panic("No init found.  Try passing init= option to kernel.");
}

 

在基本分析完内核启动流程的以后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup。在内核init线程中调用了do_basic_setup,这个函数也作了不少内核和驱动的初始化工做,详解以下:async

/*
 * 好了, 设备如今已经初始化完成。 可是尚未一个设备被初始化过,
 * 可是 CPU 的子系统已经启动并运行,
 * 且内存和处理器管理系统已经在工做了。
 *
 * 如今咱们终于能够开始作一些实际的工做了..
 */
static void __init do_basic_setup(void)
{
    rcu_init_sched(); /* needed by module_init stage. */
    init_workqueues(); //初始化工做队列

    /*针对SMP系统,初始化内核control group的cpuset子系统。
     *若是非SMP,此函数为空。
     *cpuset是在用户空间中操做cgroup文件系统来执行进程
     *与cpu和进程与内存结点之间的绑定。
     *本函数将cpus_allowed和mems_allwed更新为在线的cpu和在线的内存结点,
     *并为内存热插拨注册了钩子函数,最后建立一个单线程工做队列cpuset。
     */
    cpuset_init_smp();

    /*建立一个单线程工做队列khelper。
     *运行的系统中只有一个,主要做用是指定用户空间的程序路径和环境变量, 
     *最终运行指定的user space的程序,属于关键线程,不能关闭。
     */
    usermodehelper_init();

    //初始化驱动模型中的各子系统,可见的现象是在/sys中出现的目录和文件
    driver_init();

    //在proc文件系统中建立irq目录,并在其中初始化系统中全部中断对应的目录。
    init_irq_proc();

    /*调用连接到内核中的全部构造函数,也就是连接进.ctors段中的全部函数。
     *在内核启动和模块挂载时,调用构造函数(gcc生成的类初始化函数)。
     *构造函数就是好比用于初始化gcov数据的函数
     */
    do_ctors();

    /*调用全部编译内核的驱动模块中的初始化函数。
     *这里就是驱动程序员须要关心的步骤,其中按照各个内核模块初始化函数
     *所自定义的启动级别(1~7),按顺序调用器初始化函数。
     *对于同一级别的初始化函数,安装编译是连接的顺序调用,
     *也就是和内核Makefile的编写有关。
     *
     *在编写内核模块的时候须要知道这方面的知识,好比你编写的模块使用
     *的是I2C的API,那你的模块的初始化函数的级别必须低于I2C子系统
     *初始化函数的级别(也就是级别数(1~7)要大于I2C子系统)。
     *若是编写的模块必须和依赖的模块在同一级,那就必须注意内核Makefile的修改了。
     */
    do_initcalls();
}

 

上面的函数调用了driver_init函数,做用是驱动模型子系统的初始化,对于内核驱动工程师来讲比较重要,详解以下:函数

/**
 * driver_init - 初始化驱动模型.
 *
 * 调用驱动模型初始化函数来初始化它们的子系统。
 * 由早期的init/main.c中调用。
 */
void __init driver_init(void)
{
    /* These are the core pieces */
    /*初始化驱动模型中的部分子系统和kobject:
     *devices
     *dev
     *dev/block
     *dev/char
     */
    devices_init();
    buses_init();//初始化驱动模型中的bus子系统
    classes_init();//初始化驱动模型中的class子系统
    firmware_init();//初始化驱动模型中的firmware子系统
    hypervisor_init();//初始化驱动模型中的hypervisor子系统

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    platform_bus_init();//初始化驱动模型中的bus/platform子系统
    system_bus_init();//初始化驱动模型中的devices/system子系统
    cpu_dev_init();//初始化驱动模型中的devices/system/cpu子系统
    memory_dev_init();//初始化驱动模型中的devices/system/memory子系统
}
相关文章
相关标签/搜索