第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.\n
*/ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n");

當我們刪除根文件系統的內容再啟動內核,發現串口就會打印上面的字符串,如下圖:

會顯示打開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\n", 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...\n", 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進程,知道命令是怎么來的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM