1.从我们的start_armboot开始讲起

  u-boot整体由汇编段和C语言段外加连接脚本组成。关于汇编段请看我之前的博客《u-boot源码汇编段简要分析》,好,让我们进入start_armboot函数。

  这里首先涉及到的是一个叫做gd_t的结构体指针。给它分配了一块内存。位于CFG_GBL_DATA_SIZE。我们把它打开来看:

  从作者的注释我们可以知道,这个结构体的作用是针对某些初始化较早的内存空间。在设置存储控制器之前,系统初始化的时候进行一些全局变量的定义。看里面的内容,涉及到了环境地址变量、环境检测变量、帧缓冲区的基地址、展示模式、CPU时钟、总线时钟、RAM大小、复位状态寄存器等等。回到start_armboot函数,我们接着往下看:

  在这里涉及到了init_sequence结构体,打开来看:

  这个结构体里面包含了cpu初始化、单板初始化、中断初始化、环境初始化、波特率初始化、串行通信、CPUINFO、BOARDINFO、RAM块等等一系列初始化函数。回到start_armboot函数接着往下看:

  在这里红框圈起来的是对Flash的初始化,flash_init()是对Nor Flash初始化,nand_init()是对Nand Flash初始化。中间是对LCD或者VFD进行初始化,并分配内存。接下来是一个 env_relocate ()函数,这个函数是对环境变量进行初始化。所谓的环境变量就是我们在用u-boot的时候从哪里读出内核,从哪里启动内核等一系列信息,如下图:

  其中bootcmd就表示你把内核读到哪里。这里明显是把内核读到0x30007FC0地址上,然后bootm 0x0007FC0,也就是启动该地址上的内核。这里还有波特率、IP地址、bootargs等u-boot环境变量。

  最后我们进入了一个循环:main_loop();这是最重要的一个函数。

2.main_loop()函数

  

char *s;

s = getenv ("bootcmd");

# ifndef CFG_HUSH_PARSER
{
printf("Booting Linux ...\n");
run_command (s, 0);
} len = readline (CFG_PROMPT);
rc = run_command (lastcommand, flag);

  u-boot代码量很大,对于各种硬件初始化函数特别多,所以这里博主把最关键的语句摘出来。方便大家分析。这里的getenv是获取环境变量,u-boot在启动时会首先获取环境变量,所谓的环境变量我们上面通过u-boot打印出的信息就可以知道。这里的环境变量有两种,如果u-boot获取到了FLASH上的环境变量则应用此环境变量启动,否则使用默认的环境变量启动。如果在u-boot启动期间我们没有按下空格键,则在串口打印出“Booting Linux...”,并执行函数run_command启动内核。如果我们按下了空格键,则进入下面的分支语句,进入u-boot界面,readline函数获取我们输入的命令,run_command执行命令。所以run_command这个函数作用很大。

  

if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
} /* found - check max args */
if (argc > cmdtp->maxargs) {
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}

  在run_command函数中,我们能够看到,首先,parse_line函数是对我们输入的命令进行解析。一般我们在u-boot输入的命令大概是这样的:“命令,参数0,参数1,...”,所以由parse_line函数进行解析:argv[0]="命令字符串",argv[1]="参数1",argv[2]="参数2",...等等。而find_cmd则相当于一个switch,它把我们输入的命令与在u-boot中定义的各种命令相比对,若吻合则执行相应的函数操作。若遍历所有的命令名均没有我们输入的命令,则报错。在u-boot中,我们不是通过简单的switch进行命令的选择和函数的执行,而是将每个命令定义成结构体。我们进入find_cmd进行分析:

    /*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd); for (cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */ cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}

  这是find_cmd函数的主要代码,这里根据作者的注释我们可以知道,和我们刚刚讲的一样,就是进行命令名的比对。strncmp大家都很熟悉这个字符串比较函数,若比对成功则返回这个命令的结构体指针,否则继续遍历,若最终没有找到则报错。这里的__u_boot_cmd_start和__u_boot_cmd_end是在u-boot.lds连接脚本里定义。

__u_boot_cmd_start = .;
 .u_boot_cmd : { *(.u_boot_cmd) }
 __u_boot_cmd_end = .;

  对于这里的u_boot_cmd大家可能不知道它是干什么的,在哪里定义的。通过查找,我们可以发现在Command.h中有一段相关的定义,是一个结构体:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

  现在我们还是不知道它是干嘛的,在此搜索bootm,在Cmd_bootm.c中,我们看到了这样的一个定义:

U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);

  这里其实就定义了u_boot_cmd的这条指令的具体参数。不过此刻我们还是不明白这些参数的具体含义,再次查找u_boot_cmd,在Command.h中我们终于找到了对于bootm这条指令的宏定义:

  #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
  cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

  现在开始,我们解析bootm这条命令:

  name:命令名,这里将##name替换成bootm。

  Struct_Section:结构体段,根据上面我们找到的宏定义,替换成__attribute__ ((unused,section (".u_boot_cmd")))。

  #name:命令名,替换成"bootm"。

  maxargs:最大参数,替换成CFG_MAXARGS。

  rep:是否可重复,替换成1(可重复)。

  cmd:命令,替换成do_bootm。

  这里的usage表示短命令,help表长命令。对应U_BOOT_CMD中的字符串。经过一系列替换,这条语句最后变成了:

  #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

  cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {"bootm", CFG_MAXARGS, 1, do_bootm, usage, help}

  其实,经过刚才的分析,现在我们可以自己写u-boot命令了。这里是博主自己写的u-boot命令:

  命令名是"CCJ",执行这条命令显示的信息为:"Hi! I am CrazyCatJack!"并对输入的命令及参数进行输出。还有长信息和短信息如图所示。

  

#include <common.h>
#include <watchdog.h>
#include <command.h>
#include <image.h>
#include <malloc.h>
#include <zlib.h>
#include <bzlib.h>
#include <environment.h>
#include <asm/byteorder.h> int do_CCJ (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i;
printf (" Hi!I am CrazyCatJack! %d\n",argc);
for(i=0;i<argc;i++)
{
printf("%s\n",argv[i]);
}
return 0;
} U_BOOT_CMD(
CCJ, CFG_MAXARGS, 1, do_CCJ,
"CCJ -CCJ short information.\n",
"CCJ,long information...........................................................\n"
);

  这里是博主写的源代码,大家感兴趣的可以自己分析,自己写一个u-boot命令试试。

3.读出内核启动内核

  经过上述一系列准备,现在终于可以读出内核,并启动它了。真是相当的不容易。真的感觉佩服u-boot的作者。还记得我们读出内核和启动内核的命令吗?

bootcmd=nand read.jffs2 0x30007FC0 kernel; 

bootm 0x30007FC0

  类比于PC上的硬盘,我们硬盘上都有各种各样的分区,而我们的FLASH则是将这种分区写到了代码里,换言之,FLASH里的分区是不能动态更改的。通过u-boot打印的信息,我们了解到kernel的分区,我的kernel位于0x00060000,而且有2M的大小。

  

#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \
"128k(params)," \
"2m(kernel)," \
"-(root)"

  这是在具体我们板子型号的头文件中对FLASH的分区。256K装bootloader,128K装各种环境变量参数,2M的kernel,最后是root。在Cmd_nand.c中的do_nand函数将会对nand进行一系列细致的初始化。现在我们已经分析完第一条指令,读出内核,下一步是启动内核。我们的内核实际上是由一个头部+内核组成。这里的头部主要包含了内核的加载地址和入口地址:

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;

  这就是我们内核的头部,其中最重要的就是加载地址ih_load:Image文件存放地址。还有入口地址ih_ep:内核真正开始执行的地址。

  在我们启动内核的时候,如果内核被放到了入口地址上,则无需移动内核文件,否则需执行搬运函数memmove (&header, (char *)addr, sizeof(image_header_t));将内核转移到入口地址上。

  接下来是执行do_bootm_linux函数,它主要的作用就是初始化TAG并启动内核。所谓的TAG就是u-boot需要将一些参数传递给内核,但由于二者文件格式不同,所以不能够直接进行通信。最直接的方法就是创建一个二者都能够读取的文件格式,约定好读取规则。在TAG中u-boot向即将启动的内核传递了初始化相关的信息。

#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)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (&params);
#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
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 (); // cancled by www.100ask.net
}
#endif cleanup_before_linux (); theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

  其中的TAG就是u-boot传递的各项参数,最后执行theKernel (0, bd->bi_arch_number, bd->bi_boot_params);内核启动完成!u-boot到此结束!接下来就是内核相关的操作了。

uoot启动过程的更多相关文章

  1. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

  2. [原] KVM 虚拟化原理探究(2)— QEMU启动过程

    KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...

  3. Openfire的启动过程与session管理

    说明   本文源码基于Openfire4.0.2.   Openfire的启动       Openfire的启动过程非常的简单,通过一个入口初始化lib目录下的openfire.jar包,并启动一个 ...

  4. 探索 Linux 系统的启动过程

    引言 之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉.我是真的在日常生活和工作中都使用 Linux,比如在 ...

  5. Linux内核启动过程概述

    版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...

  6. SpringMVC启动过程详解(li)

    通过对SpringMVC启动过程的深入研究,期望掌握Java Web容器启动过程:掌握SpringMVC启动过程:了解SpringMVC的配置文件如何配置,为什么要这样配置:掌握SpringMVC是如 ...

  7. Android源码——Activity组件的启动过程

    根Activity启动过程 Launcher启动MainActivity的过程主要分为6个步骤: 一.Launcher向ActivityManagerService发送一个启动MainActivity ...

  8. Netty源码分析之客户端启动过程

    一.先来看一下客户端示例代码. public class NettyClientTest { public void connect(int port, String host) throws Exc ...

  9. linux源码分析(二)-启动过程

    前置:这里使用的linux版本是4.8,x86体系. 这篇是 http://home.ustc.edu.cn/~boj/courses/linux_kernel/1_boot.html 的学习笔记. ...

随机推荐

  1. Thinkphp单字母快捷键

    在ThinkPHP中有许多使用简便的单字母函数(即快捷方法),可以很方便开发者快速的调用,但是字母函数却不方便记忆,本文将所有的字母函数总结一下,以方便以后查找. 1.U() URL组装 支持不同UR ...

  2. Google 面试题和详解

    Google的面试题在刁钻古怪方面相当出名,甚至已经有些被神化的味道.这个话题已经探讨过很多次,而科技博客 BusinessInsider这两天先是贴出15道Google面试题并一一给出了答案,其中不 ...

  3. Auty自动化测试框架第五篇——框架内部的调用支持、自动化安装库与配置说明

    [本文出自天外归云的博客园] 本次对Auty自动化测试框架做些收尾工作,由于在scripts文件夹中的脚本会需要调用其他包结构文件夹中的脚本,所以这里需要添加一下框架对于内部脚本间互相调用的支持,这里 ...

  4. Android开发之MediaRecorder类详解

    MediaRecorder类介绍: MediaRecorder类是Android sdk提供的一个专门用于音视频录制,一般利用手机麦克风采集音频,摄像头采集图片信息. MediaRecorder主要函 ...

  5. [问题2015S05] 复旦高等代数 II(14级)每周一题(第六教学周)

    [问题2015S05]  设 \(A\) 是 \(n\) 阶复方阵, 证明: \(A\) 可对角化的充分必要条件是 \(A\) 相似于某个如下的循环矩阵: \[C=\begin{pmatrix} a_ ...

  6. O(1) Check Power of 2 - LintCode

    examination questions Using O(1) time to check whether an integer n is a power of 2. Example For n=4 ...

  7. 关于F12的有效利用

    1.之前仅用F12查看页面的代码: 2.现在发现用F12可以查看css文件: 3.比如看到页面背景为黑色,然后在css里面把背景从黑色调成白色:

  8. java实现LIS算法,出操队形问题

    假设有序列:2,1,3,5,求一个最长上升子序列就是2,3,5或者1,3,5,长度都为3. LIS算法的思想是: 设存在序列a. ① 如果只有一个元素,那么最长上升子序列的长度为1: ② 如果有两个元 ...

  9. socket详解(一)《转》

    在客户/服务器通信模式中, 客户端需要主动创建与服务器连接的 Socket(套接字), 服务器端收到了客户端的连接请求, 也会创建与客户连接的 Socket. Socket可看做是通信连接两端的收发器 ...

  10. Java开发高薪之路__大纲篇

    本人小白,现对java开发做出以下总结,内容将从初级开始,逐步完善与摸索. 基础篇 网页篇 Android篇 高级建设篇 数据篇 系统篇