标签:u-boot val 内核 file 解析 根文件系统 on() goto ini
内核启动并初始化后,最终目的是像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,
}
};
第3阶段——内核启动分析之挂载根文件系统和mtd分区介绍(6)
标签:u-boot val 内核 file 解析 根文件系统 on() goto ini
原文地址:http://www.cnblogs.com/lifexy/p/7366792.html