第3階段——內核啟動分析之prepare_namespace()如何掛載根文件系統和mtd分區介紹(6)


內核啟動並初始化后,最終目的是像Windows一樣能啟動應用程序,在windows中每個應用程序都存在C盤、D盤等,而linux中每個應用程序是存放在根文件系統里面,那么掛載根文件系統在哪里,怎么實現最終目的運行應用程序?

1.內核運行應用程序步驟:

1.1首先是進入stext函數啟動內核:

ENTRY(stext):         

... ...                           //啟動內核

b start_kernel                    //跳轉start_kernel()

1.2然后進入strat_kernel()初始化:

 

asmlinkage void __init start_kernel(void)
{
   ...
   setup_arch(&command_line);           //解析uboot傳入的啟動參數
   setup_command_line(command_line);    //解析uboot傳入的啟動參數
   ....
   /*查找內核參數*/
   parse_early_param()
   {
     do_early_param();              //從__setup_start到__setup_end查找early非0的函數,后面會分析
   } 
   /*查找內核參數*/
   unknown_bootoption()                 
   {
     obsolete_checksetup();      //從__setup_start到__setup_end查找early為0的函數,后面會分析
   }
   ...
   rest_init();                                //進入rest_init()
}

1.3.進入rest_init()啟動init進程

static void noinline __init_refok rest_init(void)
{
   kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);  //啟動init進程,然后進入kernel_init中啟動應用程序
   ... ...

}

1.4進入kernel_init         

static int __init kernel_init(void * unused)                             
{
   ... ...
   prepare_namespace()              //進入prepare_namespace函數,掛載系統
   {
       ... ...      / /通過解析出來的命令行參數” root=/dev/mtdblock3”來掛接根文件系統 mount_root();   //開始掛載
   } 
  init_post()                               //進入init_post() 函數,運行應用程序
  {
      /*   打開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");
       (void) sys_dup(0);
       (void) sys_dup(0);
... ... run_init_process(
"/sbin/init"); //執行應用程序 run_init_process("/etc/init"); //執行應用程序 run_init_process("/bin/init"); //執行應用程序 run_init_process("/bin/sh"); //執行應用程序 }

接下來我們就開始詳細分析prepare_namespace()如何掛載文件系統 

4 首先分析1.3節的prepare_namespace()函數中怎么掛接的文件系統”root=/dev/mtdblock3”

prepare_namespace()代碼如下    (mtdblock3:mtd分區3(kernel分區))

 void __init prepare_namespace(void)
{ ... ...
if (saved_root_name[0]) //判斷saved_root_name[0]數組是否為空 { root_device_name = saved_root_name; if (!strncmp(root_device_name, "mtd", 3)) { //比較root_device_name數組是否已mtd開頭? mount_block_root(root_device_name, root_mountflags); goto out; } //是mtd,則跳轉到out,直接掛載 ROOT_DEV = name_to_dev_t(root_device_name); if (strncmp(root_device_name, "/dev/", 5) == 0) //比較是不是已/dev/開頭 root_device_name += 5; //是的話,+5找到mtd開頭 } is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; if (initrd_load()) goto out; if (is_floppy && rd_doload && rd_load_disk(0)) ROOT_DEV = Root_RAM0; mount_root(); //將實際文件系統掛載到rootfs的/root目錄 out: sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); security_sb_post_mountroot(); }

從上面代碼得出,saved_root_name數組通過名字可以得出,是用來保存root文件系統的名字” /dev/mtdblock3”.

5 bootags參數又是怎么保存到數組的呢?,通過搜索”saved_root_name”,找到如下代碼:

static int __init root_dev_setup(char *line)
{
    strlcpy(saved_root_name, line, sizeof(saved_root_name));        //復制saved_root_name數組
    return 1;
}
__setup(
"root=", root_dev_setup);

其中root_dev_setup()函數是用來將line數組中的數據統統復制到saved_root_name數組中

__setup("root=", root_dev_setup);中有”root=”,猜測下,這個估計就是用來匹配命令行中以”root=”開頭的字符串,然后再將” root=/dev/mtdblock3”中的”/dev/mtdblock3”放在saved_root_name數組中

6.接下來分析上面的__setup("root=", root_dev_setup)宏定義

我們搜索__setup宏,找到它在include/linux/init.h中定義:

#define __setup_param(str, unique_id, fn, early)          \    //定義__setup_param(str, unique_id, fn, early)
/*定義字符串數組__setup_str_##unique_id[]=str; \表示還在define中     */
static char __setup_str_##unique_id[] __initdata = str;    \      //相當於: __setup_str_ root_dev_setup[]="root="   
/*定義結構體obs_kernel_param型__setup_##unique_id*/
static struct  obs_kernel_param  __setup_##unique_id\             
               __attribute_used__                                   \
                   __attribute__((__section__(".init.setup")))        \    //設置.init.setup段
                   __attribute__((aligned((sizeof(long)))))    \
                   = { __setup_str_##unique_id, fn, early }   //將"root=",root_dev_setup,0 放在 .init.setup段中

 

#define __setup(str, fn)                   \        //定義__setup(str, fn)使用__setup_param(str, fn, fn, 0)
          __setup_param(str, fn, fn, 0)

 最終__setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 };

在.init.setup段中存放了3個成員,第一個成員是字符串數組等於”root=”,第二個成員是一個函數,第三個成員early=0;

其中.init.setup段在vmlinux.lds中使用(.init.setup段用於存放特殊的內容,比如命令行參數).

vmlinux.lds部分代碼如下:

311   . = ALIGN(16);
312   __setup_start = .;
313    *(.init.setup)                 //存放.init.setup段
314   __setup_end = .;

7.接下來分析宏__setup("root=", root_dev_setup);又是怎么被調用的

由於通過宏”__setup("root=", root_dev_setup);”最終被存在了.init.setup段里,

所以首先搜索”__setup_start”,發現在init/main.c中do_early_param函數和obsolete_checksetup函數都使用了它

7.1先來分析do_early_param函數,首先我們看看它被誰調用

搜索do_early_param,發現它被parse_early_param()函數調用,如下圖:

 

然后搜索parse_early_param(),發現它在start_kernel函數中使用,如下圖:

 

得出:在內核啟動start_kernel()中會處理這個do_early_param函數.

7.1.1接下來分析do_early_param源碼:

static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param  *p;                //定義obs_kernel_param結構體指針*p
for (p = __setup_start; p < __setup_end; p++)   //查找.init.setup段的內容
{if (p->early && strcmp(param, p->str) == 0) {  
       if (p->setup_func(val) != 0)                //處理early非0的函數
           printk(KERN_WARNING
           "Malformed early option '%s'\n", param); 
}}
}

上面obs_kernel_param的結構體定義如下,剛好對應了

struct obs_kernel_param {
         const char *str;                //__setup_str_ root_dev_setup[]=”root=”
         int (*setup_func)(char *);        // root_dev_setup(char *line)
         int early;                     // early=0
};

__setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 }中的3個成員。

由於__setup("root=", root_dev_setup)的early=0,所以if (p->early && strcmp(param, p->str) == 0)永遠不成立.

所以在內核啟動strat_kernel()函數中會通過do_early_param函數是處理early不為0的函數。

7.2然后分析obsolete_checksetup函數,首先我們看看它被誰調用

搜索obsolete_checksetup,發現它被unknown_bootoption ()函數調用:

 

然后搜索unknown_bootoption (),發現它在start_kernel函數中使用, 如下圖:

 

得出:在內核啟動start_kernel()中會處理這個do_early_param函數.

7.2.1接下來分析obsolete_checksetup源碼:

static int __init obsolete_checksetup(char *line)
{
struct obs_kernel_param *p;         //定義obs_kernel_param型結構體指針
int had_early_param = 0;
  p = __setup_start;
do {
         int n = strlen(p->str);
         if (!strncmp(line, p->str, n))            //確定是否有內容
        {
         if (p->early) {                          //early非0,則不執行函數         
                   if (line[n] == '\0' || line[n] == '=')
                   had_early_param = 1;
                            }
          else if (!p->setup_func) {                //  處理early為0的函數,
                                     printk(KERN_WARNING "Parameter %s is obsolete,"
                                            " ignored\n", p->str);
                                        return 1; }

           else if (p->setup_func(line + n))             //處理early為0的函數
                                     return 1;
         }
                  p++;
   } while (p < __setup_end);        //從__setup_start到__setup_end查找
         return had_early_param;
}

通過上面代碼分析得出:

 __setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 }中的第三個成員early=0, 會執行root_dev_setup()函數,然后將文件系統目錄拷貝到全局變量saved_root_name[]數組中,使后面的函數來掛載文件系統.

所以在內核啟動strat_kernel()函數中會通過obsolete_checksetup函數處理early為0的函數。

 

8 root=/dev/mtdblock3 分析:

在flash中沒有分區表,在內核中,mtdblock3又在哪里體現出來的?

和uboot一樣,它也是在內核代碼中已經寫好了的,

在內核中可以通過啟動內核,從串口上可以看到分區表,如下圖:

 

從上面得出,在flash中定義了4大分區:
bootloader :存放u-boot 
boot parameters :存放一些可以設置的參數,供u-boot使用
kernel :存放內核區
root filesystem  :根文件系統,掛載(mount)后才能使用文件系統中的應用程序

8.1它們又是在內核代碼中哪里體現出來的呢?

1. 在linux-2.6.22.6目錄下通過 grep "\"bootloader\"" * -nR 搜索分區代碼,如下圖

 

由於使用的是ARM架構,CPU2440,所以找到上面紅線處的行, 才是我們需要的。

然后進入arch/arm/plat-s3c24xx/common-smdk.c中,找到120行,代碼如下:

static struct mtd_partition smdk_default_nand_part[] = {
 [0] = {                                      // mtdblock0
     .name   = "bootloader",
     .size   = 0x00040000,
     .offset         = 0,
         },

[1] = {                                      // mtdblock1
     .name   = "params",
     .offset = MTDPART_OFS_APPEND,  //表示緊跟着前面的地址后面,為偏移地址,= 0x00040000
     .size   = 0x00020000,
         },

[2] = {                                     // mtdblock2
     .name   = "kernel",
     .offset = MTDPART_OFS_APPEND, //表示緊跟着前面的地址后面,為偏移地址,= 0x00060000
     .size   = 0x00200000,
         },

[3] = {                                     // mtdblock3
     .name   = "root",
     .offset = MTDPART_OFS_APPEND, //表示緊跟着前面的地址后面,為偏移地址,= 0x00260000
     .size   = MTDPART_SIZ_FULL,
         }

};

 接下來開始分析init_post()如何啟動第1個程序 


免責聲明!

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



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