技术开发 频道

自己动手编写嵌入式Bootloader---基本功能流程

  3. main 函数

  串口初始化,以便能够向用户输出一些信息;网口初始化,以便能够从主机下载内核映像;输出一些菜单,以便用户选择执行所需要的功能。比如,用户可以选择从串口或网口下载内核映像到RAM中某个地址,然后运行这个内核。关于下载内核映像的实现,在后文会详细介绍。这里只看当内核映像已经存在于RAM中时,怎样才能把这个内核启动起来。

  4. 启动参数的传递

  启动Linux内核之前需要设置好一些必要的启动参数,这些参数以TAG列表的形式传递给内核。所谓TAG列表,就是多个TAG在内存空间中按顺序排列。每个TAG,其实都是一个结构体,每个结构体中又包含了一个头部结构体和一个内容结构体称。头部结构体指明了本TAG的类型、占用空间大小;所谓TAG的类型,就是一个宏定义,用一个确定的整数来识别该标记。内容结构体包含了该TAG的具体内容。

  下面以具体的例子做说明。

  在atag.h中就有:

#define ATAG_CORE 0x54410001
#define ATAG_MEM 0x54410002
#define ATAG_CMDLINE 0x54410009
#define ATAG_NONE 0x00000000

  这些都是TAG的类型,注意这些整数跟地址没有关系,只是一个用来识别标记类型的符号而已。

  每个Tag都用结构体表示,包含TagHeader 头结构体以及随后的参数值数据结构。如 ATAG_CORE:

struct Atag {

             struct TagHeader  stHdr;

             struct TagCore stCore;

};

  其中包含两个结构体。第一个结构体TagHeader含两个整型变量,用以表示本结构体的长度、标记类型;nSzie赋值为头部TagHeader和数据TagCore的大小之和,注意是以字(即4字节)为单位;ulTag 就赋值为先前定义的宏ATAG_CORE。第二个结构体就是实际的数据了。

struct TagHeader {
UINT32 nSize;
UINT32 ulTag;
};


struct TagCore {
UINT32 ulFlags;
UINT32 nPageSize;
UINT32 ulRootDev;
};

  由于每个Tag都由一个TagHeader加一个数据部分组成,因此通常的做法是使用Struct和Union相结合来定义:

struct Atag {
           struct TagHeader stHdr;
           union {
                  struct TagCore stCore;
                  struct TagMem32 stMem;
                  struct TagVideoText stVideoText;
                  struct TagRamDisk stRamDisk;
                  struct TagInitrd stInitRd;
                  struct TagSerialnr stSerialNr;
                  struct TagRevision stRevision;
                  struct TagVideolfb stVideoLfb;
                  struct TagCmdline stCmdLine;
           };
};

  其中涉及到的所有数据结构均可在 Linux 内核源码的include/asm/setup.h 头文件找到,我们把这些定义放在Bootloader的头文件atag.h中。

  启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中,在我们的S-Boot中对应的头文件为 atag.h。

  在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

  向内核传递参数的方法,先在内存中某个起始地址开始,连续存放多个Tag, 组成Tag列表。列表中的每个Tag包括头部TagHeader和数据结构体。按规定,第一个Tag必须是ATAG_CORE, 最末一个Tag必须是ATAG_NONE,而且中间必须包含至少一个ATAG_MEM。 注意的是末尾的ATAG_NONE只包括头部,没有数据内容。如图所示。

  在编程时先定义好起始地址,然后用一个指针,每设置完毕一个Tag的内容就向后移动相应的长度,然后设置下一个Tag内容,以保证各个Tag的连续存放。

  下面具体说明几个关键Tag的数据区域内容的设置。struct TagCore结构体已经在前面列出,它包含三个整型变量,ulFlags一般设为零,nPageSize表示分页内存管理中每一页的大小,一般为4096字节,ulRootDev是系统启动的设备号,设为零即可,因为通常在后面的命令行参数Cmdline中覆盖这个设置。Struct TagMem用来描述系统的物理内存地址空间,定义如下:

struct atag_mem {
            UINT32 nSize;
/* size of the area */
            UINT32 ulStart;
/* physical start address */
};

  其中nSzie表示内存的总大小,ulStart为内存的起始物理地址,二者结合告诉内核系统可用的物理内存空间是哪些。Struct TagCmdline结构体的定义就更简单了,只是一个字符数组,初始长度为1,如下所示:

struct TagCmdline {
           char cCmdLine[
1]; /* this is the minimum size */
};

 

  实际上命令行参数不可能只有一个字节,我们通常使用strcpy函数把命令行参数拷贝到cCmdLine地址处,在结尾附加一个字符串结束符’\0’,然后用strlen函数获得cCmdLine数组的实际长度(包括字符串结束符)。常见的命令行参数如:root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200 mem=65536。我们知道的是,Bootloader以标记列表的形式向内核传递的参数,大概有10种不同类型的Tag,而命令行参数只是其中的一种。其它需要设置的Tag包括ATAG_RAMDISK、ATAG_INITRD等,此处不再详细介绍。

  在我们的S-Boot中设置了ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE 四项。其中CmdLine 使用的是:

  const char *CmdLine = "root=/dev/nfs nfsroot=192.168.1.249:/home/hongwang/mkrootfs/rootfs ip=192.168.1.252:192.168.1.249:192.168.1.1:255.255.255.0:hwlee.net:eth0:off console=ttySAC0,115200 init=/linuxrc mem=65536K console=tty1 fbcon=rotate:2";

  这里root=/dev/nfs表示使用NFS做根文件系统,注意并不真的存在/dev/nfs这个设备,它只是一个符号而已,告诉内核使用NFS而不是使用真正的设备做根文件系统。

  nfsroot=[:][,]

  nfsroot=192.168.1.249:/home/hongwang/mkrootfs/rootfs是NFS服务器地址及要挂载的目录。

  ip=::::::

  ip=192.168.1.252:192.168.1.249:192.168.1.1:255.255.255.0:hwlee.net:eth0:off

  只说明一下autoconf,这一个选项指明开发板使用的自动配置IP地址的方法,有时开发板可以设置成通过DHCP或者BOOTP等协议从服务器获取IP地址。off 或 none 表示不使用自动配置,使用指定的静态IP地址信息。

  console=ttySAC0,115200 串口控制台

  console=tty1 fbcon=rotate:2 液晶屏Framebuffer控制台,如果内核支持,可以在LCD屏幕上显示Linux内核启动过程,起点结束后在LCD屏幕上进入Shell控制台供用户操作。fbcon=rotate:2表示控制台旋转180度,若为1表示旋转90度,3旋转270度,0不旋转。

0
相关文章