嵌入式linux开发uboot启动内核的机制(二)
一、嵌入式系统的分区
嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader、kernel、rootfs的分区是不同的。三星S5PV210规定启动设备的分区方案如下:
SD/MMC设备的分区方案:
NandFlash设备的分区方案:
嵌入式系统在启动时,uboot、kernel、rootfs不能随意存放,必须存放在规划好的相应分区,在启动过程中uboot、kernel会到相应分区加载相应内容,确保正常启动,因此嵌入式系统中,uboot和kernel规划的分区和启动设备中uoot、kernel、rootfs的实际存储分区是一致的。在嵌入式系统的启动过程中,开机时uboot运行在SoC内部的SRAM中,uboot会在BL1阶段将整个uboot拷贝到SDRAM中0xC3E00000并远跳转到SDRAM中的BL2运行。Uboot启动kernel时,同样会将kernel从启动设备拷贝到SDRAM中的指定kernel链接位置,最终跳转到kernel运行。
二、uboot支持的内核kernel格式
Linux krenel经过编译后会生成名称为vmlinux或vmlinuz的ELF格式文件,嵌入式系统在部署时烧录的文件格式需要用objcopy工具去制作成烧录镜像格式文件Image。但由于Image太大,因此linux kernel项目对Image进行了压缩,并且在p_w_picpath压缩后的文件的前端附加了一部分解压缩代码,构成压缩镜像格式zImage。同时,uboot自身为了支持linux kernel的启动,在zImage压缩镜像的基础上增加了64字节的头信息,使用uboot自带的mkp_w_picpath工具生成了uboot自身的压缩镜像格式uImage。在SMDK210的uboot版本中是同时支持zImage和uImage两种压缩镜像格式的。
三、uboot启动内核流程源码分析
uboot启动内核时实际是用\common\cmd_bootm.c中的do_bootm函数来实现的。
do_bootm函数中通过使用条件编译控制CONFIG_ZIMAGE_BOOT宏开关来实现uboot对于zImage压缩镜像的支持与取消支持。
1、do_bootm对zImage的解析
通过读取zImage的头部的第36字节开始的四个字节与LINUX_ZIMAGE_MAGIC(0x016f2818)标志位对比,如果相等则当前的kernel是zImage。如果当前kernel是zImage,则打印出”Boot with zImage”,并对当前uboot启动内核镜像的变量p_w_picpaths进行赋值,指定内核版本和内核入口地址。
#define LINUX_ZIMAGE_MAGIC0x016f2818
if (argc < 2) {
addr = load_addr;//如果不指定内核地址,则使用CFG_LOAD_ADDR
} else {
addr = simple_strtoul(argv[1], NULL, 16);//使用指定内核地址
}
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {//如果当前kernel是zImage
printf("Boot with zImage\n");//打印信息
addr = virt_to_phys(addr);//将虚拟地址转换为物理地址
hdr = (p_w_picpath_header_t *)addr;//将内核地址指定到结构体种?
hdr->ih_os = IH_OS_LINUX;//指定内核版本
hdr->ih_ep = ntohl(addr);//指定内核入口
memmove (&p_w_picpaths.legacy_hdr_os_copy, hdr, sizeof(p_w_picpath_header_t));//将内核的信息赋值给当前uboot启动内核的p_w_picpaths变量
p_w_picpaths.legacy_hdr_os = hdr;
p_w_picpaths.legacy_hdr_valid = 1;
goto after_header_check;
}
2、do_bootm对uImage的支持
使用boot_get_kernel函数校验uImage的头信息,将其中的标志位与
IH_MAGIC(0x27051956)对比,如果相等则当前kernel是uImage,打印信息
## Booting kernel from Legacy Image at XXXXXX。将得到的uImage头信息对当前uboot启动内核镜像的变量p_w_picpaths进行赋值,指定内核版本和内核入口地址。
case IMAGE_FORMAT_LEGACY:
type = p_w_picpath_get_type (os_hdr);
comp = p_w_picpath_get_comp (os_hdr);
os = p_w_picpath_get_os (os_hdr);
p_w_picpath_end = p_w_picpath_get_p_w_picpath_end (os_hdr);
load_start = p_w_picpath_get_load (os_hdr);
break;
3、do_bootm_linux启动内核
do_bootm_linux函数位于uboot/lib_arm/bootm.c中,函数的主要功能是获取环境变量中的内核传递参数,获取当前uboot启动kernel的p_w_picpaths变量中的kernel入口地址,获取uboot中的机器码,准备向kernel传递的参数,最后跳转到kernel执行,uboot执行完毕。uboot在执行完成前打印了”Starting kernel ...”信息。如果uboot实际启动kernel过程中打印出了”Starting kernel ...”信息,则表明uboot在加载、校验kernel是正确的。如果uboot最终启动kenel失败,则大部分原因是uboot向内核传递参数错误导致的。
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
bootm_headers_t *p_w_picpaths)
{
ulonginitrd_start, initrd_end;
ulongep = 0;
bd_t*bd = gd->bd;
char*s;
intmachid = bd->bi_arch_number;//uboot的机器码
void(*theKernel)(int zero, int arch, uint params);
intret;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");//获取环境变量中的内核传递参数变量
#endif
if (p_w_picpaths->legacy_hdr_valid) {
ep = p_w_picpath_get_ep (&p_w_picpaths->legacy_hdr_os_copy);//获取p_w_picpaths变量中的内核入口地址
} else {
puts ("Could not find kernel entry point!\n");
goto error;
}
theKernel = (void (*)(int, int, uint))ep;
s = getenv ("machid");//环境变量中的机器码
if (s) {//如果环境变量中定义了机器码变量,使用环境变量中的机器码
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
ret = boot_get_ramdisk (argc, argv, p_w_picpaths, IH_ARCH_ARM,
&initrd_start, &initrd_end);
if (ret)
goto error;
show_boot_progress (15);
//uboot准备给kernel传递参数
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD) || \
defined (CONFIG_MTDPARTITION)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
#ifdef CONFIG_MTDPARTITION
setup_mtdpartition_tag();
#endif
setup_end_tag (bd);
#endif
printf ("\nStarting kernel ...\n\n");//打印信息Starting kernel ...
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
theKernel (0, machid, bd->bi_boot_params);//跳转到kernel执行
return;
error:
do_reset (cmdtp, flag, argc, argv);
return;
}
四、uboot的传参机制
1、uboot传参机制
uboot使用tag方式传参,tag是一种数据结构,与linux kernel中的tag是相同的数据结构。tag结构体包含tag_header和tag_xxxx成员,tag_header结构体包含tag的大小和类型编码,kernel接收到tag参数后根据头信息中类型编码确定tag的类型。
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
struct tag_memclk memclk;
struct tag_mtdpart mtdpart_info;
} u;
};
struct tag_header {
u32 size;
u32 tag;
};
uboot传参源码:
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || CONFIG_MTDPARTITION
setup_start_tag (bd);
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#ifdef CONFIG_MTDPARTITION
setup_mtdpartition_tag();
#endif
setup_end_tag (bd);
#endif
起始tag是ATAG_CORE类型,结束tag是ATAG_NONE类型,其他类型的tag是有效信息,环境变量bootargs传递给CONFIG_CMDLINE_TAG类型tag。环境变量mtdpart存放的启动设备分区表信息传递给ATAG_MTDPART类型tag。
uboot通过调用theKernel (0, machid, bd->bi_boot_params)函数启动内核,theKernel函数的三个参数通过寄存器来传参,第1个参数0存放在R0中,第2个参数机器码存放在R1中,第3个参数bd->bi_boot_params(传参tag的首地址)存放在R2中。
2、bootargs参数解读
bootargs是uboot中最重要的变量,uboot的环境变量的设置最终都是通过bootargs传递给kernel的。
bootargs参数各部分解读如下:
A、root
用来指定rootfs的位置, 常见的情况有:
root=/dev/ram rw
root=/dev/ram0 rw
root=/dev/mtdx rw
root=/dev/mtdblockx rw
root=/dev/nfs,在文件系统为基于nfs的文件系统的时候使用。指定root=/dev/nfs后,需要指定nfsroot=serverip:nfs_dir。
B、rootfstype
和root配合使用,一般如果根文件系统是ext2,选项可有可无,如果是jffs2,squashfs等文件系统的话,就需要rootfstype指明文件系统的类型,不然无法挂载根分区。
C、console
console=tty<n> 使用虚拟串口终端设备 <n>
console=ttyS<n>[,options] 使用特定的串口<n>,options可以是这样的形式bbbbpnx,这里bbbb是指串口的波特率,p是奇偶校验位,n是指的bits。
console=ttySAC<n>[,options] 同上面。
D、mem
mem=xxM 指定内存的大小,非必须选项
E、ramdisk_size
ramdisk=xxxxx 不推荐
ramdisk_size=xxxxx 推荐
ramdisk 驱动,创建的ramdisk的size,默认情况下是4M
F、 initrd, noinitrd
如果没有使用ramdisk启动系统,则需要使用noinitrd参数。如果使用了ramdisk启动系统,就需要指定initrd=r_addr,size, r_addr表示initrd在内存中的位置,size表示initrd的大小。
G、init
init指定内核启动后,进入系统中运行的第一个脚本,一般init=/linuxrc, 或者init=/etc/preinit,preinit的内容一般是创建console,null设备节点,运行init程序,挂载一些文件系统等等操作。
H、mtdparts
mtdparts=fc000000.nor_flash:1920k(linux),128k(fdt),20M(ramdisk),4M(jffs2),38272k(user),256k(env),384k(uboot)
mtdparts参数需要内核中的mtd驱动支持,即内核配置时需要选上Device Drivers ---> Memory Technology Device (MTD) support ---> Command line partition table parsing
mtdparts的格式如下:
mtdparts=<mtddef>[;<mtddef]
<mtddef> := <mtd-id>:<partdef>[,<partdef>]
<partdef> := <size>[@offset][<name>][ro]
<mtd-id> := unique id used in mapping driver/device
<size> := standard linux memsize OR "-" to denote all remaining space
<name> := (NAME)
mtdparts使用的设置格式:
mtdparts=mtd-id:<size1>@<offset1>(<name1>),<size2>@<offset2>(<name2>)
mtd-id 必须跟当前平台的flash的mtd-id一致,否则整个mtdparts会失效
size设置的时候可以为实际的size(xxM,xxk,xx),也可以用'-'表示剩余的所有空间。
假设flash 的mtd-id是sc1200,那么你可以使用下面的方式来设置:
mtdparts=sc1200:- → 只有一个分区
mtdparts=sc1200:256k(ARMboot)ro,-(root) → 有两个分区
I、ip
指定系统启动后网卡的ip地址,如果使用了基于nfs的文件系统,必须要指定ip参数。设置ip有两种方法:
ip = ip addr
ip=ip addr:server ip addr:gateway:netmask::which netcard:off
which netcard 是指开发板上的网卡,而不是主机上的网卡。
几种常见的bootargs的使用设置如下:
假设文件系统是ramdisk,且直接就在内存中,bootargs的设置如下:
setenv bootargs ‘initrd=0x32000000,0xa00000 root=/dev/ram0 console=ttySAC0 mem=64M init=/linuxrc’
假设文件系统是ramdisk,且在flash中,bootargs的设置如下:
setenv bootargs ‘mem=32M console=ttyS0,115200 root=/dev/ram rw init=/linuxrc’
需要在bootm命令中指定ramdisk在flash中的地址,如bootm kernel_addr ramdisk_addr (fdt_addr)
假设文件系统是jffs2类型的,且在flash中,bootargs的设置应该如下
setenv bootargs ‘mem=32M console=ttyS0,115200 noinitrd root=/dev/mtdblock2 rw rootfstype=jffs2 init=/linuxrc’
假设文件系统是基于nfs的,bootargs的设置应该如下
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5:192.168.0.3:192.168.0.3:255.255.255.0::eth0:off’
或者
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5’
bootargs参数的设置极其灵活,需要根据平台灵活设置。
嵌入式linux开发uboot启动内核的机制(二)的更多相关文章
- 嵌入式linux开发uboot启动过程源码分析(一)
一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言 ...
- linux的几个内核镜像格式Image 和 u-boot启动内核和文件系统时的一些环境变量的设置
关于编译powerpc linux的几个Image参考原文 http://blog.sina.com.cn/s/blog_86a30b0c0100wfzt.html 转载▼ PowerPC架构 L ...
- 用Windows+VirtualBox搭建嵌入式Linux开发环境
Windows+VirtualBox的嵌入式Linux开发环境的搭建 最近一直在学习Linux的设备驱动编写,一直是在物理机上安装的Ubuntu进行的,但是在Ubuntu12.04的系统中,已经不能用 ...
- 嵌入式Linux开发教程:Linux常见命令(上篇)
摘要:这是对周立功编著的<嵌入式Linux开发教程>的第7期连载.本期刊载内容有关LinuxLinux常见命令中的导航命令.目录命令和文件命令.下一期将连载网络操作命令.安装卸载文件系统等 ...
- 嵌入式Linux开发——内容介绍与开发环境的搭建
嵌入式Linux开发步骤 设计自己的硬件系统 编写Bootloader 裁剪自己的Linux内核 开发移植设备驱动 构建根文件系统 开发应用程序 嵌入式Linux学习要点 熟练使用开发工具和相关指令集 ...
- 《嵌入式Linux开发实用教程》
<嵌入式Linux开发实用教程> 基本信息 作者: 朱兆祺 李强 袁晋蓉 出版社:人民邮电出版社 ISBN:9787115334831 上架时间:2014-2-13 出版日期: ...
- 嵌入式linux开发环境构建
2.1硬件环境构建 2.1.1主机与目标板结合的交叉开发模式 在主机上编辑.编译软件,然后再目标办上运行.验证程序. 对于S3C2440.S3C2410开发板,进行嵌入式Linux开发时一般可以分为以 ...
- 嵌入式Linux开发板
嵌入式Linux开发板开发介绍: iTOP-4412嵌入式Linux开发板搭载三星Exynos四核处理器,配备1GB内存,4GB固态硬盘EMMC存储,独家配备三星S5M8767电源管理,配备Andro ...
- 嵌入式Linux开发
嵌入式Linux的开发和研究是Linux领域研究的一个热点,目前已开发成功的嵌入式系统有一半以上都是Linux.Linux到底有什么优势,使之取得如此辉煌的成绩呢?本文分为两大部分:Linux的优点. ...
随机推荐
- [LeetCode] 457. Circular Array Loop 环形数组循环
You are given a circular array nums of positive and negative integers. If a number k at an index is ...
- oracle--delete truncate drop的区别
一.delete 1.delete是DML,执行delete操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在redo和undo表空间中以便进行回滚(rollback)和重做操作,但要注意表 ...
- Mybatis成为Java互联网时代首选持久框架的原因
持久层可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏(大部分的重要数据都会有相关的备份机制),在断电或者其他情况下,重新开启系统仍然可以读取这些数据.一般执行持久任务的都是数据库系统.持久 ...
- 第十一次作业 LL(1)文法的判断,递归下降分析程序
1. 文法 G(S): (1)S -> AB (2)A ->Da|ε (3)B -> cC (4)C -> aADC |ε (5)D -> b|ε 验证文法 G(S)是不 ...
- freemarker中8个常用的指令
这里列举出Freemarker模板文件中8个常用的指令. 1. assign assign指令用于创建或替换一个顶层变量,assign指令的用法有多种,包括创建或替换一个顶层变量,创建或替换多个变量等 ...
- jsplumb 初识
Jsplumb官网:https://jsplumbtoolkit.com GitHub:https://github.com/sporritt/jsplumb/ 有很多官网示例 基础教程:htt ...
- Java 8——保存参数名称
一.详述 在很多情况下,程序需要保存方法参数名称,如Mybatis中的mapper和xml中sql的参数绑定.但是在java 8之前的编译器是不支持保存方法参数名至class文件中的. 所以很多框架都 ...
- C++ 中 string和char* 的区别
C++ 中 string和char* 的区别 1.定义: string:string是STL当中的一个容器,对其进行了封装,所以操作起来非常方便. char*:char *是一个指针,可以指向一个字符 ...
- 【08】Kubernets:Service
写在前面的话 在 K8S 第一节的时候我们简单提到过 Service 的工作模式有三种:userspace / iptables / ipvs.并且已经知道在目前新版本中默认是 ipvs,前提是在按照 ...
- JAVA设计模式工厂模式
工厂模式: – 实现了创建者和调用者的分离. – 详细分类: • 简单工厂模式 • 工厂方法模式 • 抽象工厂模式• 面向对象设计的基本原则: – OCP(开闭原则,Open-Closed Princ ...