概述
最近刚刚接触Android的系统开发,先从分析bootloader开始,参考的是TCC的Android中的lk包,下面是我的一点成果,晒出来,同时这也是我的第一片博客,请大家多多指教!
1. Boot.img结构
android/bootable/bootloader/lk/app/aboot/bootimg.h中可以得知boot.img的结构如下:
[cpp] view plaincopyprint?
1. /*
2. ** +-----------------+
3. ** | boot header | 1 page
4. ** +-----------------+
5. ** | kernel | n pages
6. ** +-----------------+
7. ** | ramdisk | m pages
8. ** +-----------------+
9. ** | second stage | o pages
10. ** +-----------------+
11. **
12. ** n = (kernel_size + page_size - 1) / page_size
13. ** m = (ramdisk_size + page_size - 1) / page_size
14. ** o = (second_size + page_size - 1) / page_size
15. **/
主要分析boot header
这个头部信息包含了我们的内核启动的参数信息,由结构体boot_img_hdr定义
[cpp] view plaincopyprint?
1. struct boot_img_hdr
2. {
3. unsigned char magic[BOOT_MAGIC_SIZE];
4.
5. unsigned kernel_size; /* size in bytes */
6. unsigned kernel_addr; /* physical load addr */
7.
8. unsigned ramdisk_size; /* size in bytes */
9. unsigned ramdisk_addr; /* physical load addr */
10.
11. unsigned second_size; /* size in bytes */
12. unsigned second_addr; /* physical load addr */
13.
14. unsigned tags_addr; /* physical addr for kernel tags */
15. unsigned page_size; /* flash page size we assume */
16. unsigned unused[2]; /* future expansion: should be 0 */
17.
18. unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
19.
20. unsigned char cmdline[BOOT_ARGS_SIZE];
21.
22. unsigned id[8]; /* timestamp / checksum / sha1 / etc */
23. };
2. Lk如何解析boot header
Bootloader的作用是用来引导内核启动的,或者通过按键等控制进入recovery模式。这其中重要的一步就是如何解析boot.img和recovery.img的头部信息,提取这两部分的参数,传递给内核。本部分以TCC8900的Android源码包里面的lk为例,详细说明头部信息的解析过程。
在这之前先简要描述bootloader运行之后与boot.img和recovery有关的初始化工作。可以参照下面的流程图:
(由于博客对普通用户暂不支持图片功能,以后会补贴上)
android/bootable/bootloader/lk/app/aboot/aboot.c中的boot_linux()函数主要实现了内核引导参数参数的处理过程。
[cpp] view plaincopyprint?
1. void boot_linux(void *kernel, unsigned *tags,
2. const char *cmdline, unsigned machtype,
3. void *ramdisk, unsigned ramdisk_size)
4. {
5. unsigned *ptr = tags;
6. void (*entry)(unsigned,unsigned,unsigned*) = kernel;
7. struct ptable *ptable;
8. int cmdline_len = 0;
9. int have_cmdline = 0;
10. /* CORE */
11. *ptr++ = 2;
12. *ptr++ = 0x54410001;
13. if (ramdisk_size) {
14. *ptr++ = 4;
15. *ptr++ = 0x54420005;
16. *ptr++ = (unsigned)ramdisk;
17. *ptr++ = ramdisk_size;
18. }
19. ptr = target_atag_mem(ptr);
20. ……
21. if (cmdline && cmdline[0]) {
22. cmdline_len = strlen(cmdline);
23. have_cmdline = 1;
24. }
25. ……
26. if (cmdline_len > 0) {
27. const char *src;
28. char *dst;
29. unsigned n;
30. /* include terminating 0 and round up to a word multiple */
31. n = (cmdline_len + 4) & (~3);
32. *ptr++ = (n / 4) + 2;
33. *ptr++ = 0x54410009;
34. dst = (char *)ptr;
35. if (have_cmdline) {
36. src = cmdline;
37. while ((*dst++ = *src++));
38. }
39. ……
40. ptr += (n / 4);
41. }
42. /* END */
43. *ptr++ = 0;
44. *ptr++ = 0;
45. ……
46. entry(0, machtype, tags);
47. }
1) void (*entry)(unsigned,unsigned,unsigned*) = kernel;
此处定义了内核入口函数entry(),将kernel地址传给函数指针。
2) 在boot_linux_from_flash()函数中调用的boot_linux()进行传参:
[cpp] view plaincopyprint?
1. /* TODO: create/pass atags to kernel */
2. /*开始给内核传递atags参数,start boot_linux*/
3. dprintf(INFO, "nBooting Linuxn");
4. boot_linux((void *)hdr->kernel_addr, (void *)TAGS_ADDR, (const char *)cmdline, board_machtype(),
5. (void *)hdr->ramdisk_addr, hdr->ramdisk_size);
此处重点分析hdr指针:
[cpp] view plaincopyprint?
1. int boot_linux_from_flash(void)
2. {
3. struct boot_img_hdr *hdr = (void*) buf;
4. unsigned n;
5. struct ptentry *ptn;
6. struct ptable *ptable;
7. unsigned offset = 0;
8. struct fbcon_config *fb_display = NULL;
9. char* data;
10. ……
11. }
可以看到hdr是由buf指针传过来的,而buf定义为
[cpp] view plaincopyprint?
1. static unsigned char buf[16384]; //Equal to max-supported pagesize
也就是说,这是一段缓冲区,那么这段缓冲区是何时填充的呢,而且初步猜想,这个buf缓冲区存放的就是boot.img的heaer信息。继续看代码:
[cpp] view plaincopyprint?
1. ptable = flash_get_ptable();
这个函数调用/lk/platform/tcc_shared/nand.c里面的
[cpp] view plaincopyprint?
1. struct ptable *flash_get_ptable(void)
2. {
3. return flash_ptable;
4. }
返回flash_ptable,这是个全局变量,定义并实现在lk/target/init.c中,通过启动的时候执行/lk/kernel/init/main.c中的target_init(),函数 flash_ptable()将MTD的分区信息拷贝到flash_ptable结构体中。具体实现如下:
[cpp] view plaincopyprint?
1. static struct ptable flash_ptable;
2.
3. static struct ptentry board_part_list[] = {
4. {
5. .start = 0,
6. .length = 10, /* 10MB */
7. .name = "boot",
8. },
9. {
10. .start = 10,
11. .length = 5, /* 5MB */
12. .name = "kpanic",
13. },
14. {
15. .start = 15,
16. .length = 150, /* 150MB */
17. .name = "system",
18. },
19. {
20. .start = 165,
21. .length = 4, /* 4MB */
22. .name = "splash",
23. },
24. {
25. .start = 169,
26. .length = 40, /* 40MB */
27. .name = "cache",
28. },
29. {
30. .start = 209,
31. .length = VARIABLE_LENGTH,
32. .name = "userdata",
33. },
34. {
35. .start = DIFF_START_ADDR,
36. .length = 10, /* 10MB */
37. .name = "recovery",
38. },
39. {
40. .start = DIFF_START_ADDR,
41. .length = 1, /* 1MB */
42. .name = "misc",
43. },
44. {
45. .start = DIFF_START_ADDR,
46. .length = 1, /* 1MB */
47. .name = "tcc",
48. }
在lk/target/tcc8900_evm的init.c里面有一个target_init()函数,这个函数在执行kmain()的时候执行,
[cpp] view plaincopyprint?
1. // initialize the target
2.
3. dprintf(SPEW, "initializing targetn");
4.
5. target_init();
在target_init()函数中会执行:
if (flash_get_ptable() == NULL) 函数,判断flash_get_ptable()返回的是否是一个空的值,此时去调用/lk/platform/tcc_shared/nand.c里面的struct ptable *flash_get_ptable(void)时,返回的flash_ptable在当前文件下是一个static变量(static struct ptable *flash_ptable = NULL),符合执行初始化的条件,则会通过执行:
[cpp] view plaincopyprint?
1. ptable_init(&flash_ptable);
2. for( i = 0; i < num_parts; i++ )
3. {
4. ptable_add(&flash_ptable, sPartition_List.parts[i].name, flash_info->offset + sPartition_List.parts[i].start,sPartition_List.parts[i].length, sPartition_List.parts[i].flags);
5. }
6. flash_set_ptable(&flash_ptable);
先将MTD的分区信息复制给init.c下面的flash_ptable结构体,再调用flash_set_ptable(&flash_ptable)将上面的flash_ptable传递给nand.c中的flash_ptable结构体,那么这以后,aboot.c里面的
[cpp] view plaincopyprint?
1. ptable = flash_get_ptable();/*就拥有了MTD的分区信息了*/
2. ptn = ptable_find(ptable, "boot");
3. if (flash_read(ptn, offset, buf, page_size)) {
4. dprintf(CRITICAL, "ERROR: Cannot read boot image headern");
5. return -1;
6. }
通过flash_read(ptn, offset, buf, page_size)就可以将boot header的相关信息读取到buf缓冲区里面去了。此处的page_size可以自己手动指定,例如可以是2k字节。那么这时,buf里面就是boot.img的头部信息,数据以boot_img_hdr结构封装,那么,就可以使用hdr指针来访问kernel_add、kernel_size等信息了。
3) 处理boot header信息
获得了boot header信息后,经过boot_linux()函数将boot头部信息提取出来,封装成tag结构体。boot_linux()传入的参数tags的值为TAGS_ADDR,这个值在
android/bootable/bootloader/lk/target/tcc8900_evm/rules.mk中被定义为0x40000100。该地址将会作为参数传给内核入口函数entry(0, machtype, tags)。上面这个函数主要功能就是将boot_img_hdr中的内容一项项填到tag表中。下面列出了tag的结构体,是一TLV(Tag-Length-Value)结构。联合中列举了不同参数的值(Value)的结构。
[cpp] view plaincopyprint?
1. struct tag {
2. struct tag_header hdr;
3. union {
4. struct tag_core core;
5. struct tag_mem32 mem;
6. struct tag_videotext videotext;
7. struct tag_ramdisk ramdisk;
8. struct tag_initrd initrd;
9. struct tag_serialnr serialnr;
10. struct tag_revision revision;
11. struct tag_videolfb videolfb;
12. struct tag_cmdline cmdline;
13. struct tag_acorn acorn;
14. struct tag_memclk memclk;
15. } u;
16. };
该结构体定义在/arch/arm/include/asm/setup.h中。然后,系统起来之后,会运行/init/main.c中的start_kernel()函数中的setup_arch(char **cmdline_p)函数:
[cpp] view plaincopyprint?
1. void __init setup_arch(char **cmdline_p)
2. {
3. ......
4. mdesc = setup_machine(machine_arch_type);
5. ......
6.
7. if (__atags_pointer)
8. tags = phys_to_virt(__atags_pointer);
9. else if (mdesc->boot_params)
10. tags = phys_to_virt(mdesc->boot_params);
11.
12. ......
13.
14. if (tags->hdr.tag == ATAG_CORE) {
15. if (meminfo.nr_banks != 0)
16. squash_mem_tags(tags);
17. save_atags(tags);
18. parse_tags(tags);
19. }
20.
21. ......
22. }
从上面的代码中可以知道内核参数tags为__atags_pointer或者mdesc->boot_params。而全局变量__atags_pointer在arch/arm/kernel/head-common.S中被赋值为entry(0, machtype, tags)中的tags。
最后
以上就是包容长颈鹿为你收集整理的Android bootloader—LK的分析之如何解析boot.img【转载】的全部内容,希望文章能够帮你解决Android bootloader—LK的分析之如何解析boot.img【转载】所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复