定製我的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       
     
      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章