uboot--参数-->kernel


生成的内核镜象有两种zImageuImage

其中zImage下载到目标板中后,能直接用uboot的命令go来进行直接跳转。这时候内核直接解压启动。不过无法挂载文件系统

因为go命令没有将内核需要的相关的启动参数传递给内核

传递启动参数我们必须使用命令bootm来进行跳转。Bootm命令跳转只处理uImage的镜象。


uboot原始码的tools/目录下有mkimage工具,这个工具能用来制作不压缩或压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置及映象名是什么

 ===========================================================================================================

一:启动参数的传递过程
启动参数是包装在数据结构里的,在linux kernel启动的时候,bootloader把这个数据结构拷贝到某个地址, 在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,下面这句话就是uboot跳向linux kernel的代码(bootm命令) 
theKernel (0,   bd->bi_arch_number,   bd->bi_boot_params);


thekernel其实不是个函数,

而是指向内核入口地址的指针,

把它强行转化为带三个参数的函数指针,会把三个 参数保存到通用寄存器中,实现了向kernel传递信息的功能

 

在这个例子里,会把R0赋值为0,

                              R1赋值为机器号

                              R2赋值为启动参数数据结构的首地址 
因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般约定俗成是内存首地址+100dex) 
  
二:启动参数的数据结构
启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的 
但是,到目前为止,2.6的内核也可以兼容前一种结构,两种数据结构具体定义如下(arm cpu): 
  
struct  param_struct
     union { 
     struct { 
         unsigned long page_size;                /*  0 */ 
         unsigned long nr_pages;                  /*  4 */ 
         unsigned long ramdisk_size;           /*  8 */ 
         unsigned long flags;                          /* 12 */ 
#define FLAG_READONLY    1 
#define FLAG_RDLOAD    4 
#define FLAG_RDPROMPT    8 
         unsigned long rootdev;                       /* 16 */ 
         unsigned long video_num_cols;        /* 20 */ 
         unsigned long video_num_rows;       /* 24 */ 
         unsigned long video_x;                       /* 28 */ 
         unsigned long video_y;                       /* 32 */ 
         unsigned long memc_control_reg;    /* 36 */ 
         unsigned char sounddefault;              /* 40 */ 
         unsigned char adfsdrives;                  /* 41 */ 
         unsigned char bytes_per_char_h;    /* 42 */ 
         unsigned char bytes_per_char_v;    /* 43 */ 
         unsigned long pages_in_bank[4];    /* 44 */ 
         unsigned long pages_in_vram;         /* 60 */ 
         unsigned long initrd_start;                  /* 64 */ 
         unsigned long initrd_size;                  /* 68 */ 
         unsigned long rd_start;                       /* 72 */ 
         unsigned long system_rev;                /* 76 */ 
         unsigned long system_serial_low;    /* 80 */ 
         unsigned long system_serial_high;   /* 84 */ 
         unsigned long mem_fclk_21285;      /* 88 */ 
     } s
     char unused[256]; 
     } u1
     union { 
     char paths[8][128]; 
     struct { 
         unsigned long magic; 
         char n[1024 - sizeof(unsigned long)]; 
     } s; 
     } u2; 
     char commandline[COMMAND_LINE_SIZE]; 
};

param_struct只需要设置cmmandline,     u1.s.page_size,     u1.s.nr_pages三个域。

 

对于tag来说,在实际使用中是一个struct tag组成的列表,

在tag->tag_header中,一项是u32 tag(重名,注意类型)   其值用宏:      

                  ATAG_CORE,

                  ATAG_MEM,

                  ATAG_CMDLINE,

                  ATAG_NONE

                  等等来表示,   此时下面union就会使用与之相关的数据结构


同时,规定tag列表中

          第一项必须是ATAG_CORE,

          最后一项必须是ATAG_NONE,

比如在linux代码中,找到启动参数之后 
         首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct 结构,然后调用函数来转换.

         在tag->tag_header中,另一项是u32 size,表示tag的大小,tag组成列表的方式就是 
指针+size,实际使用中用tag_next (params).

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;           //Acorn specific 
         struct tag_omap          omap;          //OMAP specific 
         struct tag_memclk      memclk;       //DC21285 specific 
     } u; 
};

需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数
如果实际使用中不一致的话就不能正常传递,可以自行修改
  


三:通过两种数据结构传递参数的具体例子
  
1:
例子一:

通过param_struct让uboot中的go命令可以传递参数 
分析:  go的代码在common/cmd_boot.c中,里面并没有拷贝启动参数的代码,转向内核的时候也没有传送 
启动参数所在的地址,因此添加如下代码用于拷贝参数,可以看到,对于param_struct只需要设置

cmmandline 
u1.s.page_size,

u1.s.nr_pages三个域 


         char *commandline = getenv("bootargs"); 
         struct param_struct*lxy_params=(struct param_struct *)0x80000100; 
  
         printf("setup linux parameters at 0x80000100\n"); 
         memset(lxy_params,   0,   sizeof(struct param_struct)); 

        // 腾出了 struct param_struct   's mm space

        //  then init struct
         lxy_params->u1.s.page_size=(0x1<<12);                 //4K 这个是必须有的,否则无法启动
         lxy_params->u1.s.nr_pages=(0x4000000)>>12;     //64M 这个是必须有的,否则无法启动
         memcpy(lxy_params->commandline,     commandline,       strlen(commandline)+1); 
         printf("linux command line is: \"%s\"\n",lxy_params->commandline);

然后还要向内核传递参数地址,将下面一行代码修改: 
rc = ((ulong(*)(int, char *[]))addr) (--argc, &argv[1]);          //需要被修改的代码
rc = ((ulong(*)(int,int,uint))addr) (0,   gd->bd->bi_arch_number,   gd->bd->bi_boot_params);    //修改之后的代码

  
2:例子二:bootm命令中通过拷贝tag传递参数 
为方便阅读,进行了少许修改,但功能不变,该函数参数为存放启动参数的地址 
static void  setup_linux_tag(ulong param_base) 

  struct tag *params = (struct tag *)param_base; 
  char *linux_cmd; 
  char *p; 
  
  memset(params, 0, sizeof(struct tag)); 
  
  /* step1: setup start tag */
  params->hdr.tag = ATAG_CORE; 
  params->hdr.size = tag_size(tag_core); 
  params->u.core.flags = 0; 
  params->u.core.pagesize = LINUX_PAGE_SIZE; 
  params->u.core.rootdev = 0; 
  params = tag_next(params); 
  
  /* step2: setup cmdline tag */
  params->hdr.tag = ATAG_CMDLINE; 
  linux_cmd = getenv("bootargs"); 
  /* eat leading white space */
  for (p=linux_cmd; *p==' '; p++) {/* do nothing */;} 
  params->hdr.size = (sizeof(struct tag_header)+strlen(linux_cmd)+1+4) >> 2; 
  memcpy(params->u.cmdline.cmdline, linux_cmd, strlen(linux_cmd)+1); 
  params = tag_next(params); 
  
  /* step3: setup end tag */
  params->hdr.tag = ATAG_NONE; 
  params->hdr.size = 0; 
}

  
四:其他
在uboot中,进行设置tag的函数都在lib_arm/armlinux.c中,在这些函数前面是有ifdef的 
#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)

因此,如果你的bootm命令不能传递内核参数,就应该是在你的board的config文件里没有对上述的宏进行设置,定义一下即可

http://forum.byr.edu.cn/article/Embedded_System/3584

 

========================================================================================

 

UBOOT引导Linux内核及向内核传递参数的方式

   在UBOOT中,引导内核最常用的方法是bootm命令,bootm命令可以引导“UBOOT格式”的内核。

 什么是“UBOOT格式”:用UBOOT自带的mkimage命令生成的内核称为"UBOOT"格式的内核。以下面这条命令为例:
   mkimage -n "Kernel 2.4.18" -A arm -O linux -T kernel -C none -a 30007fc0 -e 30008000 -d 4020.bin vmlinux-

2.4.18.img
     其中与内核引导最密切的是-e 30008000,也就是内核的入口地址。其它参数可以参考帮助信息。其它UBOOT格式的内核与

原来相比,只是进行(可选)了压缩,并在前面加了一个0x40大小的头。
 这个头里放了内核的位置(0x30007fc0)和入口地址(0x30008000)和其它信息。
   bootm命令执行时,先对头部信息等进行校验,
 然后把头信息放到一个结构里面。
 最后根据内核类型调用相应的启动函数。
 对于Linux而言就是do_bootm_linux,在启动函数里面,有这么一个操作:
  theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
 这是最关键的一个操作,将内核的入口地址0x30008000赋给了theKernel,在启动函数的最后,使用theKernel (0, bd-

>bi_arch_number, bd->bi_boot_params);启动内核。

   根据传参规范,三个变量分别用r0,r1,r2传给内核,这样就巧妙地利用了函数指针进行了参数传递,实在是精妙!

   上面讲完了内核的引导及传参,需要引起注意的就是在使用mkimage命令生成内核时,-e后面的地址要比-a后面的地址偏

移0x40,原因很简单,就不在细说了。


====
插曲
====
 先后排除了几个问题,总算将ZY版的Linux2.4.18引导起来了。
 引导使用的是bootm命令,原来不成功是因为bootargs没有设好,Linux已经正常启动,串口却没有输出。反复试验,把能

排除的原因都排除之后,才发现是bootargs不对。由于对Linux启动不了解,浪费了不少时间...

 

----------------------------------------------------------------------------------------
为什么串口输出那么多waiting?

我印象中是在等时钟滴答的时候才会有,是不是UBOOT中使用了Timer0,你在使用Timer0的时候没有对它进行复位?
Linux version 2.4.18-rmk7 () (gcc version 2.95.3 20010315 (release)) #28 Thu Apr 24

11:15:47 CST 2008
CPU: ARM ARM720T revision 2
Machine: SEP3221 BASED
Kernel command line: root=/dev/ram0 console=tty0 console=ttyS0,9600 init=/linuxrc
Console: colour dummy device 80x30
Calibrating delay loop...

waiting...waiting...waiting...waiting...waiting...waiting...waiting...waiting...waiting...waiting...waiting...wait

ing...waiting...waiting...waiting...waiting...

(以下省略N千个waiting...)


为了避免上面出现的情况,在int cleanup_before_linux (void)函数中,添加以下代码:
  T1CR = 0x0;
  T1LCR = 0x0;
达到复位Timer0的目的,果然再次用bootm命令引导Linux的时候没有再出现这么多的waiting...。


----------------------------------------------------------------------------------------


引导RAMFS方法
  假设将文件系统下载到0x30300000.
  用BOOTM引导内核和文件系统时,不仅需要将内核进行mkimage处理,文件系统也需要进行相应处理.
  文件系统处理命令:
  mkimage -n "RAMFS" -A arm -O linux -T ramdisk -C none -a 30300000 -e 30300040 -d initrd.bin initrd.img
  进行处理后,与内核处理结果相似,会在原来的文件系统映像前面加上一个64字节的头,这个头里包含了幻数,CRC校验信息和最重要的:文件系统起始地址和长度.

  如果使用UBOOT引导内核,同时希望将文件系统一并引导,bootm命令后面需要跟两个参数,第一个参数是内核所在的地址,第二个参数是文件系统所在的位置,如bootm 30007fc0 30300000
  上面说的引导文件系统是利用了bootloader向内核标准传参方法,如果已经在内核将诸如机器号,文件系统地址等全部写死的话,则不需要进行上述操作,只需将PC切换到内核所在地址就可以了.

----------------------------------------------------------------------------------------

之前提到,bootloader巧妙地利用函数指针及传参规范将R0:0x0,R1:机器号,R2:参数地址传递给内核.由于R0,R1比较简单,不需要再作说明.需要花点时间了解的是R2寄存器.

  R2寄存器传递的是一个指针,这个指针指向一个TAG区域.UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,如command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递.TAG区域存放的地址,也就是R2的值,是在/board/yourboard/youboard.c里的board_init 函数中初始化的,如在UB4020中初始化为:gd->bd->bi_boot_params =0x30000100;,这是一个绝对地址.
  TAG区的结构比较简单,可以视为一个一个TAG的排列(数组?),每一个TAG传递一种特定类型的参数.各种系统TAG的定义可

以参考./include/asm-arm/setup.h.
  下面是一个TAG区的例子:
  0x30000100      00000005 54410001 00000000 00000000
  0x30000110      00000000 0000000F 54410009 746F6F72
  0x30000120      65642F3D 61722F76 7220306D 6F632077
  0x30000130      6C6F736E 74743D65 2C305379 30303639
  0x30000140      696E6920 6C2F3D74 78756E69 EA006372
  0x30000150      00000004 54420005 30300040 00200000
  0x30000160      00000000 00000000

  我们可以看到一共有三个TAG:
      第一个TAG的长度是5个字,类型是ATAG_CORE(54410001),有三个元素,均为全零.TAG区必须以这个TAG开头.
      第二个TAG的长度是F个字,类型是ATAG_CMDLINE(54410009),这是一个字符串,是向内核传递的kernel command line
      第三个TAG的长度是4个字,类型是ATAG_INITRD2(54410005),有两个元素,
  第一个是start:30300040(30300000+40),
  第二个是size:200000(2M)
   如果说还有第四个TAG,那就是末尾的两个全零,这是TAG结束的标志.

  这些TAG是在./lib_arm/arm_linux.c中的do_bootm_linux函数中建立起来的.具体建立哪些TAG,由相应的控制宏决定.具体可以参考相应代码.例子中第一个TAG是起始TAG,如果环境变量中有bootargs,则建立第二个TAG,如果bootm有两个参数(引导文件系统),则会读取文件系统头部的必要信息,建立第三个TAG.
  内核启动后,将根据R2寄存器的值找到这些TAG,并根据TAG类型,调用相应的处理函数进行处理,从而获取内核运行的必要信息.

 

http://www.armfans.net/thread-109-1-1.html

===========================================================================================================

 

cramfs这种只读的压缩格式,linux内核是直接支持的,

yaffs必须要自己来打补丁才能让内核支持,在yaffs2的目录内执行sh patch-ker.sh c $KERNEL 就可以让内核支持yaffs格式了。

 

至于根文件系统个人觉得cramfs要好些,即省空间又是只读的,对于产品来说安全性稍高一点点

      首先需你有两个分区,一个cramfs和一个yaffs,这个要通过bootloader来实现。当根文件系统设置为cramfs后,修改一下初始化的脚本liunxrc,在里面加上一句mount /dev/mtdblockX /my 其中X为的yaffs分区number,即可实现两种分区共存。TE2440的板子出厂时/dev/mtdblock/3是cramfs格式_dev/mtdblock/4是yaffs的,内核启动后用mount /dev/mtdblock/4 /root之后,就可以操纵那一块yaffs的空间了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值