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

内核启动并初始化后,最终目的是像Windows一样能启动应用程序

在windows中每个应用程序都存在C盘、D盘等

而linux中每个应用程序是存放在根文件系统里面

那么挂载根文件系统在哪里,怎么实现最终目的运行应用程序?

1.进入stext函数启动内核

2.进入strat_kernel():

...

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的函数,后面会分析

}

...

3.进入rest_init();

3.1 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //设置1号线程kernel_init

3.1.1进入kernel_init

3.1.1.1进入prepare_namespace()

{

... ...      / /通过解析出来的命令行参数” root=/dev/mtdblock3”来挂接根文件系统 mount_root();   //开始挂载

}

3.1.1.2返回到kernel_init ()/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");

run_init_process("/sbin/init");        //执行应用程序

run_init_process("/etc/init");         //执行应用程序

run_init_process("/bin/init");        //执行应用程序

run_init_process("/bin/sh");         //执行应用程序

}

4分析prepare_namespace()函数中怎么挂接的文件系统”root=/dev/mtdblock3” (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它又是怎么保存到数组的呢?,通过搜索”saved_root_name”,找到如下代码:

static int __init root_dev_setup(char *line)

{

strlcpy(saved_root_name, line, sizeof(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宏定义

我们搜索__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;  */

static char __setup_str_##unique_id[] __initdata = str;    \

/*定义结构体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 }

#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函数都使用了它

代码如下:

1. do_early_param函数分析

1.1首先我们看看该函数被谁使用了

搜索do_early_param,发现它被parse_early_param()函数调用,如下图:

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

得出:在内核启动start_kernel()中会处理这个do_early_param函数.

1.2接下来分析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的结构体定义如下,刚好对应了

__setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 }中的3个成员:

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)的early=0,所以if (p->early && strcmp(param, p->str) == 0)永远不成立.

所以在内核启动strat_kernel()函数中会通过do_early_param函数是处理early不为0的函数。

2. obsolete_checksetup函数分析

2.1 首先我们看看该函数被谁使用了

搜索obsolete_checksetup,发现它被unknown_bootoption ()函数调用:

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

得出:在内核启动start_kernel()中会处理这个do_early_param函数.

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的函数。

root=/dev/mtdblock3 分析:

在flash中没有分区表,在内核中,mtdblock3又在哪里体现出来的?

和uboot一样,它也是在内核代码中已经写好了的,

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

从上面得出,在flash中定义了4大分区:

| bootloader | :存放u-boot

|boot parameters | :存放一些可以设置的参数,供u-boot使用

| kernel | :存放内核区

|root filesystem | :根文件系统,挂载(mount)后才能使用文件系统中的应用程序

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

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,

}

};

时间: 2024-10-17 21:38:09

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

第3阶段——内核启动分析之start_kernel初始化函数(5)

内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数真正理解需要对linux相关体系有很深的了解后才能明白 代码如下: asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __sto

第3阶段——内核启动分析之创建si工程和启动内核分析(3)

目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的 (3) 分析stext函数如何启动内核 1 创建内核source sight 工程 1.1 点击 "add all" 添加所有文件,后面再慢慢删去Arch目录和Include目录中与2440芯片没用的文件. 1.2 点击Remove Tree 删除Arch文件夹,再添加与2440相关的硬件核心代码以及其它公用的代码 Arch:包含了平台,处理器相

第3阶段——内核启动分析之make uImage编译内核(3)

目标: 通过分析makefile,明白make uImage如何编译内核 把整个内核的makefile分成三类(makefile资料文档在linux-2.6.22.6/Documentation/build/makefiles.txt) <1>各级子目录makefile(每个子目录都有makefile)<2>/arch/arm/Makefile(架构相关的makefile)<3>顶层目录makefile 在顶层目录makefile中auto.conf和/arch/arm

第3阶段——内核启动分析之make menuconfig内核配置(2)

目标: 分析make menuconfig内核配置过程 在上1小结中(内核编译试验)讲到了3种不同的配置: (1)通过make menuconfig 直接从头到尾配置.config文件 (2) 通过make s3c2410_deconfig 命令在默认的配置上进行自动修改.config文件 (3)使用厂家提供的配置config_ok文件覆盖.config文件 所以,所有的配置结果都是配置.config文件 1 在linux下通过vi指令查看.config内核配置文件 如上图所示:就是一堆配置项:

linux-2.6.22.6内核启动分析之Makefile文件

学习目标 分析Makefile文件,了解内核中的哪些文件被编译,如何被编译,连接时顺序如何确定! Linux内核源码中包含很多的Makefile文件,这些Makefile文件又包含其它的一些文件,比如配置信息.通用规则等等.我们可以把内核中的Makefile文件分为5类,如下表所示: 顶层Makefile 所有Makefile文件的核心,从总体控制内核的编译.连接 .config 配置文件,在执行配置命令时生成.所有Makefile文件都根据.config来决定如何使用哪些文件 arch/$(A

Linux内核启动及加载根文件系统

</pre></h1><p><span style="font-family:KaiTi_GB2312;font-size:18px;">上接博文<<a target=_blank href="http://blog.csdn.net/gqb_driver/article/details/8931775" style="text-decoration: none; font-family: 'Mi

I.MX6Q(TQIMX6Q/TQE9)学习笔记——内核启动与文件系统挂载

经过前面的移植,u-boot已经有能力启动内核了,本文主要来看下如何通过之前移植的u-boot来启动内核.如果按照前面的文章完成了LTIB 的编译,那么,Linux的内核应该就会出现rpm/BUILD/目录下,接下来,我们就开始移植这个3.0.35版本的内核到TQIMX6Q. 内核的编译 为了简化内核编译的过程,可以在内核目录下创建编译脚本,命名为build.sh,内容如下: [cpp] view plaincopy #!/bin/sh export ARCH=arm export CROSS_

Linux内核启动分析

张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlou.com/courses/reports/986221 在这里我们用的是linux-3.18.6版本,以下简写成linux. start_kernel在 /linux/init/main.c中定义: 这个函数是内核由引导程序引导以后,由自解压程序解压以后执行的第一个函数,可以认为是整个内核的入口函数,以后我

内核启动时在挂载ubi文件系统时提示UBIFS error (ubi0:0 pid 1): ubifs_read_superblock: min. I/O unit mismatch

一.背景 1.1 笔者机器的内核错误信息如下: UBIFS error (ubi0:0 pid 1): ubifs_read_superblock: min. I/O unit mismatch: 2048 in superblock, 8 real 1.2 笔者为ubi文件提供的flash分区大小为32MiB 二.解决方法 修改在制作ubi文件系统时的页面大小参数 mkfs.ubifs的-m是用来指定页面大小参数的,当然其它参数也是需要适当调整的 如笔者原来的参数为:-m 2048 -e 12