【转】UBOOT——启动内核
转自:https://www.cnblogs.com/biaohc/p/6403863.html
1:什么是UBOOT,为什么要有UBOOT?
UBOOT的主要作用是用来启动linux内核,因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如时钟、串口、dram等;
如要想让CPU启动linux内核,只能通过另外的程序,进行必要的初始化工作,在把linux内核中代码复制到内存中,并执行这块内存中的代码,即可启动linux内核;一般情况下,我们把linux
镜像储存在块设备中如SD卡、iNand、Nandflash等块设备中,首先执行UBOOT带码,在UBOOT中把块设备中的内核代码复制到内存地址0x30008000地址处,然后在执行bootm 0x30008000
命令来执行内核代码;
整个过程大致如上述所讲,下面我们详细分析一下UBOOT启动内核的代码:
2:在启动UBOOT时候会出现看机倒计时,如果没有按键按下,会自动启动内核,我们来看一下这个是如何实现的:
下面这段代码是在main_loop函数中:作用是执行完倒数计时函数以后启动linux内核,启动方式是 s = getenv ("bootcmd");我们假定不使用HUAH_PARSER的情况下 run_command (s, 0);
实际上就是读取环境变量bootcmd,然后执行这个命令;
s = getenv ("bootcmd"); debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (bootdelay >= 0 && s && !abortboot (bootdelay)) { #ifndef CFG_HUSH_PARSER
run_command (s, 0);
#else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
看一下bootcmd命令:bootcmd=movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000
movi read kernel 30008000 以及 bootm 30008000
这两个命令来完成linux内核启动的:
movi read kernel 30008000是把sd卡中kernel分区复制到30008000内存地址处,bootm 30008000即到内存地址处执行代码;
下面详细分一下bootm这个命令对应的函数
代码一步步分析:
下面这段代码的作用是判断内核镜像是zImage、uImage、设备树
在这里要解释一下zImage、uImage的区别:
linux内核代码经过编译链接以后生成一个elf文件名叫vmlinuz文件,这个文件在经过arm-linux-objcopy编译以后会生成一个Image镜像文件,vmlinuz elf文件大小为70M以上
而Image镜像文件为7M左右,然后Image文件在进一步经过压缩生成zImage文件,当zImage文件作为启动镜像来启动时,首先要解压这个文件,这个解压过程可以由uboot解压
或者zImage文件本身可以自解压,zImage中除了linux内核的镜像以外,还有一些头文件以及这部分解压代码,所以内核实际上在addr地址中在加一个偏移量的位置;
uImage是uboot自己专用的启动内核镜像,相对于zImage他们之间头文件有一定区别可以详细看代码是如何判断的;uImage现在基本上要属于过时的技术了,新一点的技术为
设备树的启动方式;
我们时这么使用bootm命令的:bootm 0x30008000
走的是addr = simple_strtoul(argv[1], NULL, 16);
addr中的值为0x30008000
接下来判断0x30008000右偏移36字节以后,这个地址中的值如果为 0x016f2818这个魔数的话,说明启动镜像为zImage则 输出boot with zImage,
hdr->ih_os = IH_OS_LINUX; zImage header中 IH_os 赋值为 IH_OS_LINUX;
hdr->ih_ep = ntohl(addr); ih_ep 中存放的是point address 这个值实际上就是真正内核代码的地址;
在看下面这句代码
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
把hdr中的值复制一份到 image.legacy_hdr_os_copy中,即把内存地址0x30008000处设置好的zImage头复制一份到uboot的data段,
因为static bootm_headers_t images; images为uboot内定义的一个bootm_header_t格式的全局变量;
看一下bootm_header_t类型为一个结构体,包含一个image_header_t类型的指针,这个指针最后指向了0x30008000处的zImage header
还包含一个image_header_t类型的结构体,就是用上面那句代码把0x30008000处的zImage header在酯类复制了一份;
还包含一个标志位 legacy_hdr_valid如果上面两个赋值以后,把legacy_hdr_valid赋值为1;
typedef struct bootm_headers { image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */
ulong legacy_hdr_valid;
}
uint8_t ih_os; /* Operating System */
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC 0x016f2818
/* find out kernel image address */
if (argc < 2) {
addr = load_addr;
debug ("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
addr = simple_strtoul(argv[1], NULL, 16);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
} if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
printf("Boot with zImage\n");
addr = virt_to_phys(addr);
hdr = (image_header_t *)addr;
hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr); memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t)); /* save pointer to image header */
images.legacy_hdr_os = hdr; images.legacy_hdr_valid = 1; goto after_header_check;
}
#endif
直接跳转到after_header_check处,os为IH_OS_LINUX
下面判断操作系统,然后调用do_bootm_linux函数;
do_bootm_linux (cmdtp, flag, argc, argv, &images);
#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
os = hdr->ih_os;
#endif switch (os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv, &images);
break; case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
break; #ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
break;
#endif case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv, &images);
break; #if defined(CONFIG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
break; case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
break;
#endif #ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv, &images);
break;
#endif
}
show_boot_progress (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
if (iflag)
enable_interrupts();
return 1;
}
下面看一下do_bootm_linux都做了哪些事情
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
首先获取环境变量bootargs:
if (images->legacy_hdr_valid) {
ep = image_get_ep (&images->legacy_hdr_os_copy)
else {
puts ("Could not find kernel entry point!\n");
goto error;
}
在判断全局变量images中的legacy_hdr_valid是否为1,如果为1 获取ep 值;如果为1读出ep的值,如果不为1则erro
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);
}
把ep强制类型换换为函数指针类型复制给thekernel;
从环境变量中读取machid的值,赋值给s,如果s不空 则machid = 环境变量中machid的值,并打印machid;
在看一下uboot如何给内核传参:
传参主要是uboot把与硬件有关的信息传给linux内核,如memory信息几bank size 起始地址、命令行信息、lcd 串口、initrd、MTD等信息
#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 /* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n"); #ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif cleanup_before_linux (); theKernel (0, machid, bd->bi_boot_params);
/* does not return */
首先:如要定义了任意一个CONFIG_XXXXX的话
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; /*
* Acorn specific
*/
struct tag_acorn acorn; /*
* DC21285 specific
*/
struct tag_memclk memclk; struct tag_mtdpart mtdpart_info;
} u;
};
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core); params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0; params = tag_next (params);
}
struct tag_header {
u32 size;
u32 tag;
};
首先要setup_start_tag(bd); 这个函数的作用
params = (struct tag *) bd->bi_boot_params; 给params赋值,gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
这句代码的作用就是把uboot全局变量中设定好的bi_boot_params内存地址处强制转换为stuct tag* 类型赋值给params
分析一下struct tag结构体:它是由一个stuct tag_header类型的结构体加上一个由一系列结构体组成的union联合体组成;
这一系列结构体中存放的就是与board有关的参数;
把PHYS_SDRAM_1+0x100这个地址设置为传参的起始地址;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
hdr.tag 与hdr.size赋值;
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
然后对联合体中的结构体参数赋值;
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
params = tag_next (params);
在把params向右移动sizeof(tag_core)大小
继续赋值:
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
这段代码是传递内存参数:
把内存每个bank的信息放到这里:第一个扇区的起始地址和大小,第二个扇区的起始地址和大小
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32); params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size; params = tag_next (params);
}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
接着是传递命令行参数
1 static void setup_commandline_tag (bd_t *bd, char *commandline)
2 {
3 char *p;
4
5 if (!commandline)
6 return;
7
8 /* eat leading white space */
9 for (p = commandline; *p == ' '; p++);
10
11 /* skip non-existent command lines so the kernel will still
12 * use its default command line.
13 */
14 if (*p == '\0')
15 return;
16
17 params->hdr.tag = ATAG_CMDLINE;
18 params->hdr.size =
19 (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
20
21 strcpy (params->u.cmdline.cmdline, p);
22
23 params = tag_next (params);
}
前面我们分析了commandline是一个char *类型,指向环境变量中的bootargs的值;
#define CONFIG_BOOTARGS "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC0,115200"
最后setup_end_tag (bd);结束传参
再看最后uboot中最后一句代码
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return;
通过执行thekernel函数直接启动linux内核,传递三个参数0、machid、传参的首地址;
这三个参数是通过r0、r1、r2三个寄存器来传递了,r0传递0、r1传递machid、r2传递传参的首地址;
这样就启动起来linux内核了;
-----------------------------------------------------------------------------------------------------------------------------------
下面我们再来总结一下uboot启动linux内核的整个流程:
开机时会出现倒计时,当没有按键按下的时候,uboot会读取出bootcmd这个环境变量,并使用rum_command函数来执行这个命令;
实质是执行了movi read kernel 30008000;以后在执行bootm 30008000
moviread kernel的作用是把sd卡中的kernel分区赋值到30008000内存处
bootm 30008000就是真正的传参以及跳转到linux内核中执行;
bootm 首先要做的事情是判断这个内核镜像为zImage、uImage、设备树
通过对镜像文件的头文件的验证,确定是哪种内核镜像,然后再把必须的信息储存起来如是linux操作系统,ep的值等;
确定好以后调用do_bootm_linux函数来对内核传参并且启动内核;
【转】UBOOT——启动内核的更多相关文章
- UBOOT启动内核过程
1.摘要 (1).启动4步骤第一步:将内核搬移到DDR中第二步:校验内核格式.CRC等第三步:准备传参第四步:跳转执行内核(2).涉及到的主要函数是:do_bootm和do_bootm_linux(3 ...
- U-boot 启动内核
1:什么是UBOOT,为什么要有UBOOT? UBOOT的主要作用是用来启动linux内核,因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作, ...
- linux的几个内核镜像格式Image 和 u-boot启动内核和文件系统时的一些环境变量的设置
关于编译powerpc linux的几个Image参考原文 http://blog.sina.com.cn/s/blog_86a30b0c0100wfzt.html 转载▼ PowerPC架构 L ...
- 嵌入式linux开发uboot启动内核的机制(二)
一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: ...
- 嵌入式Linux驱动学习之路(六)u-boot启动内核
内核启动是需要必要的启动参数.不能开机自动完全从0开始启动,需要uboot帮助内核实现重定位并提供参数. 首先,uboo会从Kernel分区中读取bootcmd环境变量,根据环境变量可自动启动. 分区 ...
- 使用Uboot启动内核并挂载NFS根文件系统
配置编译好内核之后,将生成的内核文件uImage拷贝到/tftpboot/下,通过tftp服务器将内核下载到开发板,使用命令:tftp 31000000 uImage.下载完成之后配置bootargs ...
- uboot启动内核的实现
前面我们分析了uboot 的整个流程,我们知道uboot启动以后所有功能都是通过命令来实现的,启动kernel就是执行了bootcmd里面的命令.命令执行过程在uboot中是非常重要的现在我们就来看u ...
- tiny4412学习(一)之从零搭建linux系统(烧写uboot、内核进emmc+uboot启动内核)【转】
本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74080109 版权声明:本文为博主原创文章,转载请注明http://blog.c ...
- u-boot学习(五):u-boot启动内核
u-boot的目的是启动内核.内核位于Flash中,那么u-boot就要将内核转移到内存中.然后执行命令执行之.这些操作是由bootcmd命令完毕的. bootcmd=nand read.jffs2 ...
随机推荐
- CDH安装时,部分节点不受管控
解决方案: /opt/cm-5.12.0/etc/init.d/cloudera-scm-agent stop cd /opt/cm-5.12.0/lib/cloudera-scm-agent/ rm ...
- 2018-2019-2 网络对抗技术 20165220 Exp 8 Web基础
2018-2019-2 网络对抗技术 20165220 Exp 8 Web基础 实验任务 (1).Web前端HTML(0.5分) 能正常安装.启停Apache.理解HTML,理解表单,理解GET与PO ...
- Window下PHP环境配置使用Redis总结
什么是Redis? Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value 数据库,并提供多种语言的API.它和Memcached类似,它支持存储的 ...
- maven的配置及使用
Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. Apache官网下载maven 解压缩,配置环境变量: path路径:E:\apache-ma ...
- windows 开启/关闭本地连接的批处理程序
::命令前加@符号,表示不显示@后面的命令. @echo off title Open / Close Network ::本地网络适配器名称 set name=以太网 ::查看网络状态,后反向设定 ...
- 中国MOOC_面向对象程序设计——Java语言_第2周 对象交互_1有秒计时的数字时钟
第2周编程题 查看帮助 返回 第2周编程题,在课程所给的时钟程序的基础上修改 依照学术诚信条款,我保证此作业是本人独立完成的. 温馨提示: 1.本次作业属于Online Judge题目,提交后由系 ...
- C# 文件打开对话框 图片fitter
"All Image Files|*.bmp;*.ico;*.gif;*.jpeg;*.jpg;*.png;*.tif;*.tiff|""Windows Bitmap(* ...
- Spring获取request、session以及servletContext
获取request: HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestA ...
- es为什么要取消type? 或者为什么一个index下多个type会有问题
同一个index下的不同的type下的相同的filed,在同一个index下其实会被认为是同一个filed. 否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降
- Cocos2d-X网络编程(2) Cocos2d中的网络通信协议——http协议
HTTP协议也叫超文本传输协议.是互联网广泛使用的通信协议,常用于B/S架构中. HTTP连接使用的是短连接形式,也就是"请求-响应"的方式,不仅在请求时需要先建立连接,而且需要客 ...