LK(little kernel)第一行代码链接位置分析及lk启动过程

LK是(L)ittle (K)ernel的缩写,是一个功能及其强大的bootloader开源项目,但现在只支持arm和x86平台。
LK的一个显著的特点就是它实现了一个简单的线程机制(thread),和对高通处理器的深度定制和使用。因此高通平台android普遍采用LK作为其bootloader。但是,LK只是整个系统的引导部分。
1,lk的代码链接方式以及第一行代码的位置
本文以高通平台为例,编译lk的命令是make aboot,编译后生成一个emmc_appsboot.mbn image文件。mbn格式是高通包含了特定运营商定制的一套efs,nv的集成包文件。大致格式可以认为和elf相似。
确定bootloader/lk第一行执行的代码
整个系统的启动顺序是PBL加载运行SBL1,SBL1加载运行LK,LK加载运行kernel,kernel启动android。
关于这部分更详细的讲解可以参考linux驱动由浅入深系列:PBL-SBL1-(bootloader)LK-Android启动过程详解之一(高通MSM8953启动实例)
其中我们知道LK的image是 emmc_appsboot.mbn,它被PBL加载进内存后,其代码段第一条指令就是emmc_appsboot.mbn在编译链接时有ld链接脚本确定的。
查看lk相应的链接脚本发现
bootable\bootloader\lk\arch\arm目录下有 system-onesegment.ld、system-twosegment.ld两个链接脚本,这就是lk第一阶段和第二阶段的两个链接脚本。
整个lk第一行代码的位置由第一阶段链接脚本确定,查看system-onesegment.ld内容如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
	. = %MEMBASE%;
	/* text/read-only data */
	.text.boot : { *(.text.boot) }
	.text :	{ *(.text .text.* .glue_7* .gnu.linkonce.t.*) } =0x9090
	.interp : { *(.interp) }
	.hash : { *(.hash) }
	.dynsym : { *(.dynsym) }
	.dynstr : { *(.dynstr) }
	.rel.text : { *(.rel.text) *(.rel.gnu.linkonce.t*) }
	.rela.text : { *(.rela.text) *(.rela.gnu.linkonce.t*) }
	.rel.data : { *(.rel.data) *(.rel.gnu.linkonce.d*) }
	.rela.data : { *(.rela.data) *(.rela.gnu.linkonce.d*) }
	.rel.rodata : { *(.rel.rodata) *(.rel.gnu.linkonce.r*) }
	.rela.rodata : { *(.rela.rodata) *(.rela.gnu.linkonce.r*) }
	.rel.got : { *(.rel.got) }
	.rela.got : { *(.rela.got) }
	.rel.ctors : { *(.rel.ctors) }
	.rela.ctors : { *(.rela.ctors) }
	.rel.dtors : { *(.rel.dtors) }
	.rela.dtors : { *(.rela.dtors) }
	.rel.init : { *(.rel.init) }
	.rela.init : { *(.rela.init) }
	.rel.fini : { *(.rel.fini) }
	.rela.fini : { *(.rela.fini) }
	.rel.bss : { *(.rel.bss) }
	.rela.bss : { *(.rela.bss) }
	.rel.plt : { *(.rel.plt) }
	.rela.plt : { *(.rela.plt) }
	.init : { *(.init) } =0x9090
	.plt : { *(.plt) }
	.rodata : { 
		*(.rodata .rodata.* .gnu.linkonce.r.*)
		. = ALIGN(4);
		__commands_start = .;
		KEEP (*(.commands))
		__commands_end = .;
		. = ALIGN(4);
		__apps_start = .;
		KEEP (*(.apps))
		__apps_end = .;
		. = ALIGN(4); 
		__rodata_end = . ;		
	}

	/* writable data  */
	__data_start_rom = .;	/* in one segment binaries, the rom data address is on top of the ram data address */
	__data_start = .;
	.data : SUBALIGN(4) { *(.data .data.* .gnu.linkonce.d.*) }
	__ctor_list = .;
	.ctors : { *(.ctors) }
	__ctor_end = .;
	__dtor_list = .;
	.dtors : { *(.dtors) }
	__dtor_end = .;
	.got : { *(.got.plt) *(.got) }
	.dynamic : { *(.dynamic) }
	__data_end = .;
	/* unintialized data (in same segment as writable data) */
	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss .bss.*) }
	. = ALIGN(4); 
	_end = .;
	. = %MEMBASE% + %MEMSIZE%;
	_end_of_ram = .;
	/* Strip unnecessary stuff */
	/DISCARD/ : { *(.comment .note .eh_frame) }
}
链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的内存排布。其中用ENTRY定义了入口位置为_start。
我们在bootable\bootloader\lk\arch\arm\crt0.S中
.globl _start
_start:
	b	reset
reset:
#ifdef ENABLE_TRUSTZONE
	/*Add reference to TZ symbol so linker includes it in final image */
	ldr r7, =_binary_tzbsp_tzbsp_bin_start
#endif
	/* do some cpu setup */
#if ARM_WITH_CP15
        /* Read SCTLR */
	mrc		p15, 0, r0, c1, c0, 0
		/* XXX this is currently for arm926, revist with armv6 cores */
		/* new thumb behavior, low exception vectors, i/d cache disable, mmu disabled */
	bic		r0, r0, #(1<<15| 1<<13 | 1<<12)
	bic		r0, r0, #(1<<2 | 1<<0)
		/* disable alignment faults */
	bic		r0, r0, #(1<<1)
	/* Enable CP15 barriers by default */
#ifdef ARM_CORE_V8
	orr		r0, r0, #(1<<5)
#endif
        /* Write SCTLR */
	mcr		p15, 0, r0, c1, c0, 0
#ifdef ENABLE_TRUSTZONE
  /*nkazi: not needed ? Setting VBAR to location of new vector table : 0x80000      */
 ldr             r0, =0x00080000
 mcr             p15, 0, r0, c12, c0, 0
#endif
#endif
	bl		kmain ////////////////跳转到c函数kmain,离开汇编世界 
其中.global _start定义了_start这个全局符号。.global 使得连接程序(ld)能够识别 symbl
声明symbol是全局可见的。标号_start是GNU链接器用来指定第一个要执行指令所必须的,同样的是全局可见的(并且只能出现在一个模块中)。
2,lk代码的简要分析
LK 代码结构
           +app            // 应用相关
           +arch           // arm 体系 
           +dev            // 设备相关
           +include      // 头文件
           +kernel        // lk系统相关   
           +platform    // 相关驱动
           +projiect     // makefile文件
           +scripts      // Jtag 脚本
           +target        // 具体板子相关

1,整个lk image的第一行代码就是crt0.S文件中的_start标号所在的行。

从上面的代码片段可知_start指示的是b reset,即先执行开机reset,初始时cpu等芯片相关逻辑,最后

bl        kmain跳转到c函数kmain,之后进入c语言世界。
2,kmain函数
kmain函数是被汇编调用的c函数,从下面代码第一行注释中也可以看到
bootable/bootloader/lk/kernel/main.c
/* called from crt0.S */
void kmain(void) __NO_RETURN __EXTERNALLY_VISIBLE;
void kmain(void)
{
	thread_t *thr;
	// get us into some sort of thread context
	thread_init_early();////////////lk中简单线程相关结构体初始化
	// early arch stuff
	arch_early_init();//////////////arm平台相关初始化,关闭cache、是能mmu等
	// do any super early platform initialization
	platform_early_init();///////////硬件板子相关初始化,初始化串口等
	// do any super early target initialization
	target_early_init();
	dprintf(INFO, "welcome to lk\n\n");
	bs_set_timestamp(BS_BL_START);
	// deal with any static constructors
	dprintf(SPEW, "calling constructors\n");
	call_constructors();
	// bring up the kernel heap
	dprintf(SPEW, "initializing heap\n");
	heap_init();///////////////////堆栈初始化
	__stack_chk_guard_setup();
	// initialize the threading system
	dprintf(SPEW, "initializing threads\n");
	thread_init();/////////////////线程相关初始化
	// initialize the dpc system
	dprintf(SPEW, "initializing dpc\n");
	dpc_init();
	// initialize kernel timers
	dprintf(SPEW, "initializing timers\n");
	timer_init();/////////////////timer相关初始化
#if (!ENABLE_NANDWRITE)
	// create a thread to complete system initialization
	dprintf(SPEW, "creating bootstrap completion thread\n");
	thr = thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);//////////启动bootstrap2线程
	if (!thr)
	{
		panic("failed to create thread bootstrap2\n");
	}
	thread_resume(thr);
	// enable interrupts
	exit_critical_section();
	// become the idle thread
	thread_become_idle();
#else
        bootstrap_nandwrite();
#endif
}
kmain中完成了必要的初始化后,调用thread_create启动了bootstrap2线程
3,bootstrap2线程
bootstrap2线程的函数体也在
bootable/bootloader/lk/kernel/main.c
static int bootstrap2(void *arg)
{
	dprintf(SPEW, "top of bootstrap2()\n");
	arch_init();
	// XXX put this somewhere else
#if WITH_LIB_BIO
	bio_init();
#endif
#if WITH_LIB_FS
	fs_init();
#endif
	// initialize the rest of the platform
	dprintf(SPEW, "initializing platform\n");
	platform_init();//////////////时钟clk相关处理
	// initialize the target
	dprintf(SPEW, "initializing target\n");
	target_init();//////////////分区表相关处理
	dprintf(SPEW, "calling apps_init()\n");
	apps_init();///////////////启动lk中的app了
	return 0;
}
bootstrap2线程中处理了clk和分区表配置后,启动了lk这个小型内核上的应用程序apps。
4,lk上的app
apps_init函数实现如下
void apps_init(void)
{
	const struct app_descriptor *app;
	/* call all the init routines */
	for (app = &__apps_start; app != &__apps_end; app++) {
		if (app->init)
			app->init(app);
	}
	/* start any that want to start on boot */
	for (app = &__apps_start; app != &__apps_end; app++) {
		if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
			start_app(app);
		}
	}
}
其中调用所有位于__apps_start与__apps_end之间的函数。
__apps_start段我们需要去ld文件中找了,在system-onesegment.ld中我们找到如下定义:
		__apps_start = .;
		KEEP (*(.apps))
		__apps_end = .;
可见是要所有放在了*.apps段中的函数了
在app.h中我们找到了*.apps段的一个宏
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
搜索发现使用APP_START添加了__SECTION段名的函数有这些:
bootloader/lk/app/aboot/aboot.c中
APP_START(aboot)
	.init = aboot_init,
APP_END
bootloader/lk/app/aboot/shell.c中
APP_START(shell)
	.init = shell_init,
	.entry = shell_entry,
APP_END
5,aboot
aboot.c中的aboot_init是较为重要的一个函数,其他暂时忽略了。
bootloader/lk/app/aboot/aboot.c
void aboot_init(const struct app_descriptor *app)
{
	unsigned reboot_mode = 0;
	int boot_err_type = 0;
	int boot_slot = INVALID;
	if (target_is_emmc_boot())
	{
		page_size = mmc_page_size();/////////////////设置NAND、EMMC读取的页大小
		page_mask = page_size - 1;
		mmc_blocksize = mmc_get_device_blocksize();
		mmc_blocksize_mask = mmc_blocksize - 1;
	}
	else
	{
		page_size = flash_page_size();
		page_mask = page_size - 1;
	}
	read_allow_oem_unlock(&device);/////////////////OEM锁
#if ENABLE_WBC
		/* Wait if the display shutdown is in progress */
		while(pm_app_display_shutdown_in_prgs());
		if (!pm_appsbl_display_init_done())
			target_display_init(device.display_panel);
		else
			display_image_on_screen();//////////////lk中显示开机logo
#else
		target_display_init(device.display_panel);
#endif
	/* Check if we should do something other than booting up */
	if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))////////判断开机时按键状态进入不同模式
	{
	#if 1
		boot_into_ffbm = true;
		strcpy(ffbm_mode_string, "ffbm-00");
	#else
		dprintf(ALWAYS,"dload mode key sequence detected\n");
		reboot_device(EMERGENCY_DLOAD);
		dprintf(CRITICAL,"Failed to reboot into dload mode\n");
		boot_into_fastboot = true;
	#endif
	}
	if (!boot_into_fastboot && !boot_into_ffbm)
	{
		if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
		{
			if (target_pause_for_battery_charge())
				reboot_device(EMERGENCY_DLOAD);
			else
				boot_into_recovery = 1;
		}
		if (!boot_into_recovery &&
			(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))
			boot_into_fastboot = true;
	}
	#if NO_KEYPAD_DRIVER
	if (fastboot_trigger())
		boot_into_fastboot = true;
	#endif
#if USE_PON_REBOOT_REG
	reboot_mode = check_hard_reboot_mode();
#else
	reboot_mode = check_reboot_mode();
#endif
	if (reboot_mode == RECOVERY_MODE)
	{
		boot_into_recovery = 1;
	}
	else if(reboot_mode == FASTBOOT_MODE)
	{
		boot_into_fastboot = true;
	}
	else if(reboot_mode == ALARM_BOOT)
	{
		boot_reason_alarm = true;
	}
#if VERIFIED_BOOT
	else if (VB_V2 == target_get_vb_version())
	{
		if (reboot_mode == DM_VERITY_ENFORCING)
		{
			device.verity_mode = 1;
			write_device_info(&device);
		}
#if ENABLE_VB_ATTEST
		else if (reboot_mode == DM_VERITY_EIO)
#else
		else if (reboot_mode == DM_VERITY_LOGGING)
#endif
		{
			device.verity_mode = 0;
			write_device_info(&device);
		}
		else if (reboot_mode == DM_VERITY_KEYSCLEAR)
		{
			if(send_delete_keys_to_tz())
				ASSERT(0);
		}
	}
#endif
normal_boot:
	if (!boot_into_fastboot)
	{
		if (target_is_emmc_boot())
		{
			if(emmc_recovery_init())
				dprintf(ALWAYS,"error in emmc_recovery_init\n");
			if(target_use_signed_kernel())
			{
				if((device.is_unlocked) || (device.is_tampered))
				{
				#ifdef TZ_TAMPER_FUSE
					set_tamper_fuse_cmd();
				#endif
				#if USE_PCOM_SECBOOT
					set_tamper_flag(device.is_tampered);
				#endif
				}
			}
retry_boot:
			/* Trying to boot active partition */
			if (partition_multislot_is_supported())
			{
				boot_slot = partition_find_boot_slot();
				partition_mark_active_slot(boot_slot);
				if (boot_slot == INVALID)
					goto fastboot;////////////////////进入fastboot
			}
			boot_err_type = boot_linux_from_mmc();
			switch (boot_err_type)
			{
				case ERR_INVALID_PAGE_SIZE:
				case ERR_DT_PARSE:
				case ERR_ABOOT_ADDR_OVERLAP:
					if(partition_multislot_is_supported())
						goto retry_boot;
					else
						break;
				case ERR_INVALID_BOOT_MAGIC:
				default:
					break;
				/* going to fastboot menu */
			}
		}
		else
		{
			recovery_init();
	#if USE_PCOM_SECBOOT
		if((device.is_unlocked) || (device.is_tampered))
			set_tamper_flag(device.is_tampered);
	#endif
			boot_linux_from_flash();//////////////////////////正常启动linux内核
		}
		dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting "
			"to fastboot mode.\n");
	}
fastboot:
	/* We are here means regular boot did not happen. Start fastboot. */
	/* register aboot specific fastboot commands */
	aboot_fastboot_register_commands();
	/* dump partition table for debug info */
	partition_dump();
	/* initialize and start fastboot */
	fastboot_init(target_get_scratch_address(), target_get_max_flash_size());
#if FBCON_DISPLAY_MSG
	display_fastboot_menu();
#endif
}
至此lk启动过程简要分析结束!!

猜你喜欢

转载自blog.csdn.net/RadianceBlau/article/details/78393112