第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

时间:2022-10-21 15:18:42

内核启动并初始化后,最终目的是像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, ) < )
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup();
(void) sys_dup();
... ...
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[]) //判断saved_root_name[0]数组是否为空
{
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", )) { //比较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/", ) == ) //比较是不是已/dev/开头
root_device_name += ; //是的话,+5找到mtd开头
} is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (initrd_load())
goto out;
if (is_floppy && rd_doload && rd_load_disk())
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 ;
}
__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, )

最终__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部分代码如下:

   . = ALIGN();
__setup_start = .;
*(.init.setup) //存放.init.setup段
__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()函数调用,如下图:

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

然后搜索parse_early_param(),发现它在start_kernel函数中使用,如下图:

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

得出:在内核启动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) == ) {
if (p->setup_func(val) != ) //处理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 ()函数调用:

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

然后搜索unknown_bootoption (),发现它在start_kernel函数中使用, 如下图:

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

得出:在内核启动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 = ;
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 = ;
}
else if (!p->setup_func) { // 处理early为0的函数,
printk(KERN_WARNING "Parameter %s is obsolete,"
" ignored\n", p->str);
return ; } else if (p->setup_func(line + n)) //处理early为0的函数
return ;
}
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一样,它也是在内核代码中已经写好了的,

在内核中可以通过启动内核,从串口上可以看到分区表,如下图:

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

从上面得出,在flash中定义了4大分区:
bootloader :存放u-boot 
boot parameters :存放一些可以设置的参数,供u-boot使用
kernel :存放内核区
root filesystem  :根文件系统,挂载(mount)后才能使用文件系统中的应用程序

8.1它们又是在内核代码中哪里体现出来的呢?

1. 在linux-2.6.22.6目录下通过 grep
"\"bootloader\"" * -nR 搜索分区代码,如下图

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)

由于使用的是ARM架构,CPU2440,所以找到上面红线处的行, 才是我们需要的。

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

static struct mtd_partition smdk_default_nand_part[] = {
[] = { // mtdblock0
.name = "bootloader",
.size = 0x00040000,
.offset = ,
}, [] = { // mtdblock1
.name = "params",
.offset = MTDPART_OFS_APPEND, //表示紧跟着前面的地址后面,为偏移地址,= 0x00040000
.size = 0x00020000,
}, [] = { // mtdblock2
.name = "kernel",
.offset = MTDPART_OFS_APPEND, //表示紧跟着前面的地址后面,为偏移地址,= 0x00060000
.size = 0x00200000,
}, [] = { // mtdblock3
.name = "root",
.offset = MTDPART_OFS_APPEND, //表示紧跟着前面的地址后面,为偏移地址,= 0x00260000
.size = MTDPART_SIZ_FULL,
} };

接下来开始分析init_post()如何启动第1个程序

第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6)的更多相关文章

  1. 第3阶段——内核启动分析之start&lowbar;kernel初始化函数&lpar;5&rpar;

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...

  2. 第3阶段——内核启动分析之创建si工程和分析stext启动内核函数&lpar;4&rpar;

    目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析stext函数如何启动内核:  (3.1) ...

  3. 第3阶段——内核启动分析之make uImage编译内核&lpar;3&rpar;

    目标: 通过分析makefile,明白make uImage如何编译内核 把整个内核的makefile分成三类(makefile资料文档在linux-2.6.22.6/Documentation/bu ...

  4. 第3阶段——内核启动分析之make menuconfig内核配置&lpar;2&rpar;

    目标: 分析make menuconfig内核配置过程 在上1小结中(内核编译试验)讲到了3种不同的配置: (1)通过make menuconfig 直接从头到尾配置.config文件 (2) 通过m ...

  5. mkimage工具 加载地址和入口地址 内核启动分析

    第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...

  6. tms320dm6446内核启动分析

    关于达芬奇DM6446,里面内部有两个部分,一个是ARM926ejs的核,还有一个是C64+DSP的视频处理核,而我需要关心的重点是arm926ejs的核(bootload和linux内核) 从boo ...

  7. linux-2&period;6&period;22&period;6内核启动分析之head&period;S引导段代码

    学习目标: 了解arch/arm/kernel/head.S作为内核启动的第一个文件所实现的功能! 前面通过对内核Makefile的分析,可以知道arch/arm/kernel/head.S是内核启动 ...

  8. linux内核启动分析(2)

    -----以下内容为从网络上整理所得------ 主要介绍kernel_init线程(函数),这个线程在rest_init函数中被创建,kernel_init函数将完成设备驱动程序的初始化,并调用in ...

  9. Linux内核启动分析过程-《Linux内核分析》week3作业

    环境搭建 环境的搭建参考课件,主要就是编译内核源码和生成镜像 start_kernel 从start_kernel开始,才真正进入了Linux内核的启动过程.我们可以把start_kernel看做平时 ...

随机推荐

  1. 注意HTML的语言编码charset

    注意HTML的语言编码的重要性 目录 charset编码重要性 charset在html什么地方 charset标签 编码种类 charset utf-8介绍 charset GB2312介绍 推荐网 ...

  2. asp&period;net core 之静态文件目录的操作

    文章前言 之前写了一篇关于模拟登录的文章,自我感觉内容不太丰富,今天的这篇文章,希望在内容上能丰富些.本人缺少写文章的经验,技术上也是新手,但我会努力的,希望大家多多支持小弟. asp.net cor ...

  3. Android应用开发基础之四:网络编程(一)

    网络图片查看器 确定图片的网址 发送http请求 URL url = new URL(address); //获取连接对象,并没有建立连接 HttpURLConnection conn = (Http ...

  4. Centos7 配置网络步奏详解

    Centos7 配置网络步奏详解 编辑网卡配置文件 vi /etc/sysconfig/network-script/ifcfg-ens01 备注:这里的ens01不是所有系统都叫这个,有的可能叫其他 ...

  5. Android Studio打包未签名包

    Android Studio打包未签名包 好久没有写技术博客了,真有点懈怠了,作为35岁的程序员,转行重新捡起这些知识,还是挺犹豫纠结的,不过没啥其它办法,一点一滴开始吧,今天这开篇就小结点前几天工作 ...

  6. java 多线程 CountDownLatch用法

    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 主要方法 public CountDownLatch(int count); pu ...

  7. hdu 4975 A simple Gaussian elimination problem&period;(网络流,推断矩阵是否存在)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4975 Problem Description Dragon is studying math. One ...

  8. Struts 2 初学的复习巩固

    Q:使用Struts2 开发程序的基本步骤? A: 1)加载Struts2类库: 2)配置web.xml文件,定义核心Filter来拦截用户请求: 3)开发视图层页面,即JSP页面: 4)定义处理用户 ...

  9. 关于ThinkPHP执行长时间任务可能导致PHP使用内存越来越大的问题

    ThinkPHP执行长时间任务时,可能导致PHP使用内存越来越大,最后因为内存超出配置限额而程序挂掉. 其实这在很久以前就无意之中发现的一个问题. 3.x之前有这个问题,5.0以后的,应该是已修复了的 ...

  10. Open Source CRM

    https://www.odoo.com/zh_CN/page/crm 试用: https://none53.odoo.com/web#home https://none.mypscloud.com/ ...