R2存放的是块内存的基地址,这块内存中存放的是uboot给Linux内核的其他参数。这些参数有内存的起始地址、内存大小、Linux内核启动后挂载文件系统的方式等信息。很明显,参数有多个,不同的参数有不同的内容,为了让Linux内核能精确的解析出这些参数,双方在传递参数的时候要求参数在存放的时猴需要按照双方规定的格式存放。
除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x以后的内核都期望以标记列表(tagged_list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。
标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_ mem32,表示命令行时使用 tag_cmdline。具体代码见arch\arm\include\asm\setup.h。
从上面可以看出,struct_tag结构体由structtag_header 联合体union构成,结构体struct tag_header用来描述每个tag的头部信息,如tag的类型,tag大小。联合体union用来描述每个传递给Linux内核的参数信息。 下面以传递内存标记、传递命令行参数为例来说明参数的传递。 (1)设置开始标记ATAG_CORE
tag->hdr.tag = ATAG_CORE;
tag->hdr.size = tag_size(tag_core);
tag->u.core.flags = params->u1.s.flags & FLAG_READONLY;
tag->u.core.pagesize = params->u1.s.page_size;
tag->u.core.rootdev = params->u1.s.rootdev;
tag = tag_next(tag);
涉及到的结构体定义如下
struct tag_header {
__u32 size;
__u32 tag;
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
__u32 flags; /* bit 0 = read-only */
__u32 pagesize;
__u32 rootdev;
};
其中tag_next,tag_size定义如下,指向当前标记的结尾
#define tag_next(t) ((struct tag *)((u32 *)(t) (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) sizeof(struct type)) >> 2)
(2)设置内存标记
t->hdr.tag = ATAG_MEM;
t->hdr.size = tag_size(tag_mem32);
t->u.mem.start = CFG_GLOBAL_RAM_BASE;
t->u.mem.size = CFG_GLOBAL_RAM_SIZE;
t = tag_next(t);
相关结构体定义如下
#define ATAG_MEM 0x54410002
struct tag_mem32 {
__u32 size;
__u32 start; /* physical start address */
};
(3)设置命令行参数标记
命令行参数是一个字符串,一般用它来告诉内核挂载根文件系统的方式。由uboot的bootargs环境变量提供,它的内容有如下两种格式
root=nfs nfsroot=202.193.61.237:/work/nfs_root/first_fs ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
root=/dev/mtdblock2 ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
名称 | 含义 |
root | 告诉Linux内核挂载根文件系统的方式,nfs表示以NFS服务的方式挂载根文件系统,/dev/mtdblock2表示根文件系统在MTD设置的第二个分区上。 |
nfsroot | 告诉Linux内核,以NFS方式挂载根文件系统时,根文件系统所在主机的P地址和路径 |
ip | 告诉Linux内核,启动后它的p地址 |
init | 告诉Linux内核,启动的第一个应用程序是根目录下的linuxrc程序 |
console | 告诉Linux区内核,控制台为ttySAC0,波特率为115200 |
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (strlen(params->commandline) 3
sizeof(struct tag_header)) >> 2;
strcpy(tag->u.cmdline.cmdline, params->commandline);
tag = tag_next(tag);
相关结构体定义如下
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410003
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
(4)设置结束标记
tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;
我们明白了运行Linux区内核的时候,uboot需要给内核的传递的参数,接下来我们就来看看如何从uboot中跳到Linux内核。
uboot跳转到Linux内核在uboot中可以使用go和bootm来跳转到内核,这两个命令的区别如下:
(1) go命令仅仅修改pc的值到指定地址
格式:go addr
(2) bootm命令是uboot专门用来启动uImage格式的Linux内核,它在修改pc的值到指定地址之前,会设置传递给Linux内核的参数,用法如下:
格式:bootm addr
uboot中bootm命令实现bootm命令在uboot源码common/cmd_bootm.c中实现,它的功能如下:
(1)读取uImage头部,把内核拷贝到合适的地方。
(2)把参数给内核准备好。
(3)引导内核。
当我们使用我们在uboot使用bootm命令后,bootm命令会从uImage头中读取信息后,发现是Linux内核,就会调用do_bootm_linux()函数,函数的具体实现bootm.c中
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & BOOTM_STATE_OS_GO) {
boot_jump_linux(images);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images);
return 0;
}
do_bootm_linux 函数最终会 跳转执行 boot_prep_linux 和 boot_jump_linux 函数,首先分析 boot_prep_linux 函数(位于 bootm.c 文件中):
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("bootargs"); //从环境变量中获取 bootargs 的值
。。。。。。。
setup_board_tags(¶ms);
setup_end_tag(gd->bd); //将 tag 参数保存在指定位置
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
do_nonsec_virt_switch();
}
从代码可以看出来,boot_prep_linux,主要功能是将 tag 参数保存到指定位置,比如 bootargs 环境变量 tag,串口 tag,接下来分析 boot_jump_linux 函数(位于 bootm.c 文件中):
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = gd->bd->bi_arch_number; //获取机器id (在 board/samsung/jz2440/jz2440.c 中设置,为 MACH_TYPE_SMDK2410(193))
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep; //获取 kernel的入口地址,此处应为 30000000
s = getenv("machid"); //从环境变量里获取机器id (本例中还未在环境变量里设置过机器 id)
if (s) { //判断环境变量里是否设置机器id
strict_strtoul(s, 16, &machid); //如果设置则用环境变量里的机器id
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params; //获取 tag参数地址,gd->bd->bi_boot_params在 setup_start_tag 函数里设置
if (!fake) kernel_entry(0, machid, r2); } //进入内核
通过分析可以看出,最终进入内核的函数为 :
kernel_entry(0, machid, r2)
到这里bootm就成功给内核传递了参数,并跳转到了内核。关于go命令的实现可以自己参考内核,在cmd_boot.c文件中,所不同的是,go命令实现的时候没有设置参数,只是简单的跳转执行。如果想要使用go来跳转到Linux内核,我们需要做简单的修改,有兴趣的可以自己研究下,这里就不展开讲了。
至此,uboot就启动了内核。启动内核后就是挂载根文件系统了,下篇将具体介绍是如何挂载根文件系统的。 构建根文件系统
内核镜像格式vmlinuz和zImage和uImage最后插讲下内核的不同映像格式的区别:
(1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。
(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。
(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码。构成了一个压缩格式的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。
(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。
(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。
(6)如果直接在kernel底下去make uImage会提供mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。
通过上面的介绍我们了解了内核镜像的各种格式,如果通过uboot启动内核,Linux必须为uImage格式。
,