zoukankan      html  css  js  c++  java
  • 第4阶段——制作根文件系统之分析init_post()如何启动第1个程序(1)

    本章学习如何启动第一个应用程序

    1.在前面的分析中我们了解到,在init进程中内核挂接到根文件系统之后,会开始启动第一个应用程序:

    kernel_init函数代码如下:

    static int __init kernel_init(void * unused)    //进入init进程                                  
    {    
       prepare_namespace()  //挂载根文件系统
       {
         ... ...      / /通过解析出来的命令行参数” root=/dev/mtdblock3”来挂接根文件系统 mount_root();   //开始挂载
       }
    
       init_post();           //启动应用程序     
    }
    }

    2.接下来开始分析init_post()如何启动应用程序的,代码如下:

    static int noinline init_post(void)
    {
      /*内核已经初始化完成,所以清除__init_begin段到__init_end段之间的数据*/
           free_initmem();
           unlock_kernel();
           mark_rodata_ro();
           system_state = SYSTEM_RUNNING;
           numa_default_policy(); 
    
    /*  打开dev/console控制台设备(串口0),使用户能输入信息, /dev/console即成为kernel_init进程的标准输入源(文件描述符0),
    打开失败则打印Warning: unable to open an initial console.
    */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console. ");

    当我们删除根文件系统的内容再启动内核,发现串口就会打印上面的字符串,如下图:

    会显示打开dev/console失败,是因为根文件系统还是在root=/dev/mtdblock3, 所以能挂载根文件系统,我们擦除了mtd3内容,也就是dev里面的内容,所以无法打开console控制台。 

    接下来继续分析init_post():

    /*调用dup打开/dev/console文件描述符两次, 该控制台设备就也可以供标准输出和标准错误使用(文件描述符1和2),
    kernel_init进程现在就拥有3个文件描述符--标准输入、标准输出以及标准错误。
    */ (void) sys_dup(0); (void) sys_dup(0);
    if (ramdisk_execute_command) { //若 ramdisk_execute_command为0,不运行它 run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s ", ramdisk_execute_command); }

    搜索上面ramdisk_execute_command,发现它是一个char型全局数组,找到它被用在init_setup()中,代码如下:

    static int __init rdinit_setup(char *str)
    {
             unsigned int i;
      
       /* 使ramdisk_execute_command数组等于str  *、
             ramdisk_execute_command = str;     
    
             /* See "auto" comment in init_setup */
             for (i = 1; i < MAX_INIT_ARGS; i++)
                       argv_init[i] = NULL;
             return 1;
    }
    
    __setup("rdinit=", rdinit_setup);
    ramdisk_execute_command

    发现上面__setup和我们上节分析的挂载根文件系统的__setup都是一样的

    它是匹配命令行中以” rdinit=”开头的字符串,由于我们uboot的命令行参数中没有”rdint=”,所以ramdisk_execute_command=0,不执行if判断

    接下来继续分析init_post():

    if (execute_command) {   // execute_command不为0, 运行它
    
    /* run_init_process 运行目标程序成功后会一直死循环*/          
    run_init_process(execute_command);  
    
    /*run_init_process运行失败退出后,打印Failed to execute /linuxrc.  Attempting defaults...  */
                  printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                       "defaults...
    ", execute_command);
                          }

    搜索上面execute_command,发现它是一个char型全局数组,找到它被用在init_setup()中,代码如下:

    static int __init init_setup(char *str)
    {
           unsigned int i;
     
         /*execute_command =str*/
           execute_command = str;     
           for (i = 1; i < MAX_INIT_ARGS; i++)
            argv_init[i] = NULL;
           return 1;
    }
    __setup("init=", init_setup);
    execute_command

    发现上面__setup和我们上节分析的挂载根文件系统的__setup都是一样的

    显然这里就是用来匹配命令行中以” init=”开头的字符串,然后再将命令行参数bootargs中的” init=/linuxrc”中的” /linuxrc”放在execute_command数组中.

    (init=/linuxrc:指定内核启动后运行的第一个脚本是当前目录下linuxrc脚本)     

    最终__setup("init=", init_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 };

    然后放在.init.setup段中,在内核启动后进入start_kernel()函数中使用这个宏,并将” /linuxrc”放在execute_command数组中.

    当文件系统被擦除后,就会运行linuxrc应用程序失败,打印执行linuxrc失败,如下图:

     

    接下来继续分析init_post():

    /*运行应用程序失败后,从下面3个地方查找可能出现 init应用程序的所有地方*/
           run_init_process("/sbin/init");
           run_init_process("/etc/init");
           run_init_process("/bin/init");
    
     
    
    /*试图建立/bin/sh 来代替应用程序 */
           run_init_process("/bin/sh");
    
    
      /*如上图所示,当前面的所有情况都失败时,调用panic。这样内核就会试图同步磁盘,确保其状态一致。
    如果超过了内核选项中定义的时间,它也可能会重新启动机器。
    */ panic("No init found. Try passing init= option to kernel."); }

    在这里init_post函数就分析完毕了.

    3.当在内核中,能输入数据时,表示根文件系统的应用程序启动完毕

    比如输入ps查看进程,如下图,(ps-process status)

     

    接下来开始分析init进程,知道命令是怎么来的

  • 相关阅读:
    DapperExtensions 使用教程
    C#事件订阅及触发例子
    01、类与结构的区别
    内网信息搜集
    Anydesk拿下远程桌面
    C++对注册表的操作
    Linux下常见流编辑器的使用
    Typecho1.1反序列化漏洞复现
    Nmap脚本编写
    Github学习
  • 原文地址:https://www.cnblogs.com/lifexy/p/7405838.html
Copyright © 2011-2022 走看看