• 第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.
    ");
           (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'
    ", 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] == '' || line[n] == '=')
                       had_early_param = 1;
                                }
              else if (!p->setup_func) {                //  处理early为0的函数,
                                         printk(KERN_WARNING "Parameter %s is obsolete,"
                                                " ignored
    ", 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个程序 

  • 相关阅读:
    常用算法编程题目学习与训练的网站
    ES6的JavaScript算法思想实现之分而治之,动态规划,贪心算法和回溯算法
    ES6的JavaScript算法实现之排序、搜索和随机算法
    ES6的JavaScript数据结构实现之图
    ES6的JavaScript数据结构实现之二叉堆和堆排序
    ES6的JavaScript数据结构实现之树(二叉搜索树、AVL树、红黑树)
    ES6的JavaScript数据结构实现之递归
    ES6的JavaScript数据结构实现之字典与散列表
    ES6的JavaScript数据结构实现之集合
    ES6的JavaScript数据结构实现之链表
  • 原文地址:https://www.cnblogs.com/lifexy/p/7366792.html
Copyright © 2020-2023  润新知