定制我的Nexus系统之boot.img的前世今生

        今天,我们来看一下boot.img的生成,作用,和修改方法。

 1.首先是build/core/main.mk

.PHONY: bootimage
bootimage: $(INSTALLED_BOOTIMAGE_TARGET)
        如果我们把out/target/product/crespo/boot.img去掉,再运行make bootimage, 那么会重新生成boot.img。然后我们看build/core/Makefile里

INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

ifeq ($(TARGET_BOOTIMAGE_USE_EXT2),true)
tmp_dir_for_image := $(call intermediates-dir-for,EXECUTABLES,boot_img)/bootimg
INTERNAL_BOOTIMAGE_ARGS += --tmpdir $(tmp_dir_for_image)
INTERNAL_BOOTIMAGE_ARGS += --genext2fs $(MKEXT2IMG)
$(INSTALLED_BOOTIMAGE_TARGET): $(MKEXT2IMG) $(INTERNAL_BOOTIMAGE_FILES)
        $(call pretty,"Target boot image: $@")
        $(hide) $(MKEXT2BOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) --output $@

else # TARGET_BOOTIMAGE_USE_EXT2 != true

$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES)
        $(call pretty,"Target boot image: $@")
        $(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) --output $@
        $(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE),raw)
endif # TARGET_BOOTIMAGE_USE_EXT2

        可知INSTALLED_BOOTIMAGE_TARGET的对象就是out/target/product/crespo/boot.img, 有可能用2种方式生成,一种是ext2格式,另外一种则不是。根据添加调试打印,crespo采用的不是ext2文件格式,因此我们来看一下$(MKBOOTIMG)。

$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES)
        $(call pretty,"Target boot image: $@")
        $(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) --output $@
        $(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE),raw)

展开来就是

out/target/product/crespo/boot.img:out/host/linux-x86/bin/mkbootimg out/target/product/crespo/kernel out/target/product/crespo/ramdisk.img
$(call pretty, "Target boot image: $@")
$(hide) --kernel out/target/product/crespo/kernel --ramdisk out/target/product/crespo/ramdisk.img --cmdline "console=ttyFIQ0 no_console_suspend" --base 0x30000000 --pagesize 4096 --output $@

其中各变量的含义分别为:

INSTALLED_BOOTIMAGE_TARGET:out/target/product/crespo/boot.img
MKBOOTIMG:out/host/linux-x86/bin/mkbootimg
INTERNAL_BOOTIMAGE_FILES:out/target/product/crespo/kernel out/target/product/crespo/ramdisk.img
INTERNAL_BOOTIMAGE_ARGS: --kernel out/target/product/crespo/kernel --ramdisk out/target/product/crespo/ramdisk.img --cmdline "console=ttyFIQ0 no_console_suspend" --base 0x30000000 --pagesize 4096
BOARD_BOOTIMAGE_PARTITION_SIZE:
       因此,其实boot.img是由两个文件out/target/product/crespo/kernel和out/target/product/crespo/ramdisk.img两个文件组成的。


2.我们接着看mkbootimg这个工具

        这个工具的源码为system/core/mkbootimg/mkbootimg.c, 其include了system/core/mkbootimg/bootimg.h, 我们先来看下bootimg.h的其中一段说明

/*
** +-----------------+ 
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages  
** +-----------------+
** | ramdisk         | m pages  
** +-----------------+
** | second stage    | o pages
** +-----------------+
**
** n = (kernel_size + page_size - 1) / page_size
** m = (ramdisk_size + page_size - 1) / page_size
** o = (second_size + page_size - 1) / page_size
**
** 0. all entities are page_size aligned in flash
** 1. kernel and ramdisk are required (size != 0)
** 2. second is optional (second_size == 0 -> no second)
** 3. load each element (kernel, ramdisk, second) at
**    the specified physical address (kernel_addr, etc)
** 4. prepare tags at tag_addr.  kernel_args[] is
**    appended to the kernel commandline in the tags.
** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
** 6. if second_size != 0: jump to second_addr
**    else: jump to kernel_addr
*/
        显然boot.img由header,kernel,ramdisk,second stage组成,每个段都要补全为page_size的整数倍大小。其中header指明了包含了什么呢?看一下以下的代码( system/core/mkbootimg/bootimg.h):

typedef struct boot_img_hdr boot_img_hdr;

#define BOOT_MAGIC "ANDROID!"
#define BOOT_MAGIC_SIZE 8
#define BOOT_NAME_SIZE 16
#define BOOT_ARGS_SIZE 512

struct boot_img_hdr
{
    unsigned char magic[BOOT_MAGIC_SIZE];

    unsigned kernel_size;  /* size in bytes */
    unsigned kernel_addr;  /* physical load addr */

    unsigned ramdisk_size; /* size in bytes */
    unsigned ramdisk_addr; /* physical load addr */

    unsigned second_size;  /* size in bytes */
    unsigned second_addr;  /* physical load addr */

    unsigned tags_addr;    /* physical addr for kernel tags */
    unsigned page_size;    /* flash page size we assume */
    unsigned unused[2];    /* future expansion: should be 0 */

    unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */

    unsigned char cmdline[BOOT_ARGS_SIZE];

    unsigned id[8]; /* timestamp / checksum / sha1 / etc */
};
可知header里包括了各个段的解压出来后存放的地址,和各个段的大小等信息,那么这些值又是在哪里赋值的呢?我们来看一下 system/core/mkbootimg/mkbootimg.c里的main函数片段

int main(int argc, char **argv)
{
   ......
           /* default load addresses */
    hdr.kernel_addr =  0x10008000;
    hdr.ramdisk_addr = 0x11000000;
    hdr.second_addr =  0x10F00000;
    hdr.tags_addr =    0x10000100;
    
    while(argc > 0){
        char *arg = argv[0];
        char *val = argv[1];
        if(argc < 2) {
            return usage();
        }
        argc -= 2;
        argv += 2;
        if(!strcmp(arg, "--output") || !strcmp(arg, "-o")) {
            bootimg = val;
        } else if(!strcmp(arg, "--kernel")) {
            kernel_fn = val;
        } else if(!strcmp(arg, "--ramdisk")) {
            ramdisk_fn = val;
        } else if(!strcmp(arg, "--second")) {
            second_fn = val;
        } else if(!strcmp(arg, "--cmdline")) {
            cmdline = val;
        } else if(!strcmp(arg, "--base")) {
            unsigned base = strtoul(val, 0, 16);
            hdr.kernel_addr =  base + 0x00008000;
            hdr.ramdisk_addr = base + 0x01000000;
            hdr.second_addr =  base + 0x00F00000;
            hdr.tags_addr =    base + 0x00000100;
        } else if(!strcmp(arg, "--board")) {
            board = val;
        } else if(!strcmp(arg,"--pagesize")) {
            pagesize = strtoul(val, 0, 10);
            if ((pagesize != 2048) && (pagesize != 4096)) {
                fprintf(stderr,"error: unsupported page size %d\n", pagesize);
                return -1;
            }
        } else {
            return usage();
        }
    }
    hdr.page_size = pagesize;
    ........

}
由上可知,如果有传参数--base, 那么  tag:base + 0x100 --> kernel:base +0x80000 -> second:base + 0x00f0_0000 -> ramdisk:base + 0x0100_0000, 如果没有传参数,则相当于base=0x1000_0000。结合上面的打印参数,crespo把base_addr设为0x3000_0000, pagesize设为4096。所以,我们不妨打开boot.img文件, 看到下面的内容:

0000000: 414e 4452 4f49 4421 602d 3300 0080 0030  ANDROID!`-3....0
0000010: 787f 0200 0000 0031 0000 0000 0000 f030  x......1.......0
0000020: 0001 0030 0010 0000 0000 0000 0000 0000  ...0............
0000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000040: 636f 6e73 6f6c 653d 7474 7946 4951 3020  console=ttyFIQ0
0000050: 6e6f 5f63 6f6e 736f 6c65 5f73 7573 7065  no_console_suspe
0000060: 6e64 0000 0000 0000 0000 0000 0000 0000  nd..............
0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
可以知道一下信息:

magic[BOOT_MAGIC_SIZE]       =  "ANDROID!"
kernel_size                  =   0x0033_2d06
kernel_addr                  =   0x3000_8000        
ramdisk_size                 =   0x0002_7f78
ramdisk_addr                 =   0x3100_0000
second_size                  =   0x0
second_addr                  =   0x30f0_0000
tags_addr                    =   0x3000_0100
page_size                    =   0x0000_1000
与我们之前的分析是一致的。


3.这些地址的意义

        首先,这个BASE_ADDR是在哪里定义的呢?我们从build/core/Makefile看到下面:

ifdef BOARD_KERNEL_BASE
  INTERNAL_BOOTIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
endif
         所以 --base指定的addr是由宏BOARD_KERNEL_BASE定义的,我们查找这个宏,可以得知在device/samsung/crespo/BoardConfigCommon.mk这个文件里定义了的:

BOARD_NAND_PAGE_SIZE := 4096
BOARD_NAND_SPARE_SIZE := 128 

BOARD_KERNEL_BASE := 0x30000000
BOARD_KERNEL_PAGESIZE := 4096
BOARD_KERNEL_CMDLINE := console=ttyFIQ0 no_console_suspend
        这是一个关于板子的定义,定义了dram的地址,还定义了cpu abi类型,wifi的驱动,板子名字等等跟板子相关的信息。

        其次,这里简单地说明一下,这里的kernel_addr, ramdisk_addr, tags_addr分别对应了我们平时对于Linux系统所涉及的ZTEXTADDR,INITRD_PHYS,PARAMS_PHYS。


4.ramdisk.img如何解压重做

        ramdisk.img其实是 文件系统 -> cpio -> gzip的过程,所以解压的话可以运行一下命令,则ramdisk.img被解压到ramdisk目录

cp ramdisk.img ramdisk.cpio.gz
gzip -d ramdisk.cpio.gz
mkdir ramdisk
cd ramdisk
cpio -i -F ../ramdisk.cpio
        修改ramdisk文件后重做ramdisk.img,可以运行一下命令

cd ramdisk
cpio -i -t -F ../ramdisk.cpio > ../list
cpio -o -H newc -O ../ramdisk_new.cpio < list
cd ../
gzip ramdisk_new.cpio
mv ramdisk_new.cpio.gz ramdisk_new.img
        上面是鉴于ramdisk里没有新建文件的前提。如果有新建或者删除文件,则需要在重新cpio之前修改list里的文件就可,或者用find来生成说明ramdisk里所有打包文件的文件也可以。


5. boot.img的作用:

        要将boot.img的作用,首先要看下flash的分区和android系统的启动流程。首先用adb登上shell, 在cat /proc/mtd,可以看见:

shell@android:/ $ cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00200000 00040000 "bootloader"
mtd1: 00140000 00040000 "misc"
mtd2: 00800000 00040000 "boot"
mtd3: 00800000 00040000 "recovery"
mtd4: 1d580000 00040000 "cache"
mtd5: 00d80000 00040000 "radio"
mtd6: 006c0000 00040000 "efs"
        “bootloader”是存放bootloader代码,这段代码在启动中最先运行,负责把boot的东西解包拷贝到ddr里然后把舞台让给boot里的kernel     

        “misc”区是有Bootloader Control Block (BCB) ,用于存放recovery引导信息。(猜是在进入recovery模式才挂载)

        “boot”区是存放引导手机启动时的必要的系统,包含一些硬件底层的驱动,主要分为ramdisk和linux内核两大块。(在系统启动后挂载到根目录下)。

        “recovery”区与boot的相似,但是恢复时启动的系统,比boot多了些恢复用的程序与资源。(只在进入recovery模式才挂载)

        “cache"区是缓存空间,程序或系统用到的缓存数据和指令就存放在这,cpu在调用和执行指令时会优先调用这里的。(在系统启动后会挂载到cache/目录)。

        “radio”和“efs”分区还不清楚其作用。

         “system”区保存了android系统目录的所有数据,机器启动后的全部系统主要都在这里(在系统启动后会挂载到system/目录),这里没有显示,我猜可能挂到mmc里了
       “userdata”区将保存了android数据目录中的所有数据(在系统启动后会挂载到data/目录,里面是会有很多应用数据以及用户的preference之类的配置数据,我们在手机设置里看到的手机内存空间就是指这里)。我猜也有可能是挂到mmc里了。

         而andoird的启动流程,是这样的:bootloader 把boot分区里的kernel和ramdisk解压到ddr -> 运行Kernel, 进入built-in驱动程序,然后进入init process,切换到user-space,结束kernel循环,进入process scheduling -> android的init process启动,读取init.rc, Native服务启动,开servicemanager,Zygote和system server

        所以到这里,boot.img就很清楚了,boot.img就是boot分区的镜像,用于启动的第二部分,即kernel和一个最小文件系统ramdisk       
     
      

你可能感兴趣的:(定制我的Nexus系统之boot.img的前世今生)