zoukankan      html  css  js  c++  java
  • linux内核启动分析(2)

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

    主要介绍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.
    ");
    
        /*复制两次标准输入(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
    ",         
                                    ramdisk_execute_command);
        }
        //如果execute_command有指定的init程序,就执行它。
        if (execute_command) {
                    run_init_process(execute_command);
                    printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                            "defaults...
    ", execute_command);
        }
        /*在检查完ramdisk_execute_command和execute_command为空的情况下,
         *顺序执行以下初始化程序:如果都没有找到就打印错误信息。
         *这也是我们做系统移植的时候经常碰到的错误信息,出现这个信息很有可能是:
         *1、你的启动参数配置有问题,通过 指定了init程序,但是没有找到,
         *    且默认的那四个程序也不在文件系统中。
         *2、文件系统挂载有问题,文件不存在
         *3、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,这个函数也做了很多内核和驱动的初始化工作,详解如下:

    /*
     * 好了, 设备现在已经初始化完成。 但是还没有一个设备被初始化过,
     * 但是 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子系统
    }
  • 相关阅读:
    HashMap深度解析(二)(转)
    HashMap深度解析(一)(转)
    GeoHash核心原理解析(转)
    spring boot 策略模式实践
    Java中CAS详解(转)
    springMVC请求流程详解(转)
    7 vi 编辑器
    Linux 命令行快捷键
    Java
    3 Eclipse 查看不了源码
  • 原文地址:https://www.cnblogs.com/morphling/p/3612579.html
Copyright © 2011-2022 走看看