Linux启动流程_LK流程_Kmain(0)

深入,并且广泛
				-沉默犀牛

此篇博客原博客来自freebuf,原作者SetRet。原文链接:https://www.freebuf.com/news/135084.html

写在前面的话

写这篇文章之前,我只好假定你所知道的跟我一样浅薄(针对本文这一方面),所以如果你看到一些实在是太小儿科的内容,请你多加担待,这确实就是我目前的水平,谢谢。

首先要说的一点是,之前我总以为 LK = Uboot = Bootloader,其实它们的关系是这样的:
Bootloader是linux的启动引导程序,是一种概念。LK 和 Uboot 是两个实实在在的程序
它们的关系可以类比为:“主食(Bootloader)中有大米(LK)和白面(Uboot)”

LK是高通平台通用的Bootloader,并且根据平台的不同,LK的执行也稍有不同
(比如A平台走了A函数,而B平台可能不会走A函数)

这里就开始了!

这一系列分为四个部分(Kmain之前的部分不讲了):

  1. Kmain
  2. bootstrap2
  3. aboot_init(包含fastboot)
  4. recovery/normal boot(也是在aboot_init函数中的,只是太重要了,分出来写)

大致描述Kmain

Kmain函数路径为bootloader/lk/kernel/main.c
在Kmain之中所做的事情可以分为两类:

 1. 偏向于硬件的初始化,这是后续运行的基本要求

	 1.1 thread_init_early		(完成早期线程初始化工作)
	 1.2 arch_early_init		(这个函数的代码也主要是针对 CPU 的一些特性做设置)
	 1.3 platform_early_init	(初始化平台的相关硬件设备包括主板,时钟,中断控制器,scm 等为 lk 的启动和
							     运行提供硬件环境)
	 1.4 target_early_init		(为开启调试 uart 调试接口)

 2. 偏向于软件环境,和具体的硬件信息没有很强的关联性
	 2.1 bt_set_timestamp		(读取当前的 TimeTick 然后保存到 bootstate 中)
	 2.2 call_constructors		(主要是为了调用 c++ 代码的构造函数,单纯的 lk 中这个函数并没有作用)
	 2.3 heap_init				(这个函数的作用就是初始化堆空间)
 	 2.4 thread_init			(关于线程的初始化在 thread_init_early 中已经完成,thread_init 只是一个空接口)
	 2.5 dpc_init				(就是创建并启动一个名为 dpc 的线程)
 	 2.6 timer_init 			(主要的作用创建 lk 中的定时器链表和定时器处理函数)
 	 
 3. 创建bootstrap2

细化上述函数_硬件初始化

thread_init_early()

void thread_init_early(void)
{
	int i;
	
	for (i=0; i < NUM_PRIORITIES; i++)			//初始化run_queue(运行队列)
		list_initialize(&run_queue[i]);

	list_initialize(&thread_list);				//初始化线程链表

	thread_t *t = &bootstrap_thread;
	init_thread_struct(t, "bootstrap");			//创建第一个线程bootstrap

	t->priority = HIGHEST_PRIORITY;				//为这个线程赋值,下同
	t->state = THREAD_RUNNING;
	t->saved_critical_section_count = 1;
	list_add_head(&thread_list, &t->thread_list_node);
	current_thread = t;							//current_thread赋值为当前thread
}

这里面涉及到的几个重要结构体:
1.运行队列 run_queue :作为多线程的调度中心存在,数组不同的下标对应不同的 运行优先级
2.线程链表 thread_list :全局的线程链表,保存了所有创建的线程信息
3.线程结构体thread_t:每个线程的上下文信息都通过 thread_t 结构体保存
4.current_thread :是一个全局变量,保存了当前运行的线程的信息


arch_early_init

这个函数太偏向于硬件,设置方法都是按住 CPU 手册来进行,所以只需要知道开启了以下几个功能即可:
1.设置异常向量基地址为 0x8F600000
2.开启 cpu mmu(memory manager unit) 内存管理单元。
3.开启一些协处理器特性


platform_early_init

整个 platform_early_init 的作用就是初始化平台的相关硬件设备
包括主板(board),时钟(clk),通用中断控制器(qgic),安全(scm) 等,为 lk 的启动和运行提供硬件环境

void platform_early_init(void)
{
	board_init();			// 主要工作是获取主板的相关信息并填充到相关结构中
	platform_clock_init();	//就是初始化平台的一系列时钟
	qgic_init();			//主要的作用就是初始化 QGIC
	qtimer_init();			//
	scm_init();				//检查 scm 是否能够使用
}

接下来分别再进一步解释以上五个函数

board_init
struct board_data {
  uint32_t platform;
  uint32_t foundry_id;
  uint32_t chip_serial;
  uint32_t platform_version;
  uint32_t platform_hw;
  uint32_t platform_subtype;
  uint32_t target;
  uint32_t baseband;
  struct board_pmic_data pmic_info[MAX_PMIC_DEVICES];
  uint32_t platform_hlos_subtype;
  uint32_t num_pmics;
  uint32_t pmic_array_offset;
  struct board_pmic_data *pmic_info_array;
};

填充的就是这样的以上结构体,获取信息的具体来源涉及到 sbl1 等其他模块。

platform_clock_init

这个函数根据定义好的数据结构定义一系列时钟,数据结构体(在msm8953平台下)形如:

static struct clk_lookup msm_clocks_8953[] =
{
	CLK_LOOKUP("sdc1_iface_clk", gcc_sdcc1_ahb_clk.c),
	CLK_LOOKUP("sdc1_core_clk",  gcc_sdcc1_apps_clk.c),

	CLK_LOOKUP("sdc2_iface_clk", gcc_sdcc2_ahb_clk.c),
	CLK_LOOKUP("sdc2_core_clk",  gcc_sdcc2_apps_clk.c),

	CLK_LOOKUP("uart1_iface_clk", gcc_blsp1_ahb_clk.c),
	CLK_LOOKUP("uart1_core_clk",  gcc_blsp1_uart1_apps_clk.c),

	CLK_LOOKUP("uart2_iface_clk", gcc_blsp1_ahb_clk.c),
	CLK_LOOKUP("uart2_core_clk",  gcc_blsp1_uart2_apps_clk.c),

	CLK_LOOKUP("usb30_iface_clk", gcc_pc_noc_usb30_axi_clk.c),
	CLK_LOOKUP("usb30_master_clk", gcc_usb30_master_clk.c),
	CLK_LOOKUP("usb30_pipe_clk", gcc_usb30_pipe_clk.c),
	CLK_LOOKUP("usb30_aux_clk", gcc_usb30_aux_clk.c),
	CLK_LOOKUP("usb2b_phy_sleep_clk", gcc_usb2a_phy_sleep_clk.c),
	CLK_LOOKUP("usb30_phy_reset", gcc_usb30_phy_reset.c),
	CLK_LOOKUP("usb30_mock_utmi_clk", gcc_usb30_mock_utmi_clk.c),
	CLK_LOOKUP("usb_phy_cfg_ahb_clk", gcc_usb_phy_cfg_ahb_clk.c),
	CLK_LOOKUP("usb30_sleep_clk", gcc_usb30_sleep_clk.c),

	CLK_LOOKUP("mdp_ahb_clk",          mdp_ahb_clk.c),
	CLK_LOOKUP("mdss_esc0_clk",        mdss_esc0_clk.c),
    	CLK_LOOKUP("mdss_esc1_clk",        mdss_esc1_clk.c),
	CLK_LOOKUP("mdss_axi_clk",         mdss_axi_clk.c),
	CLK_LOOKUP("mdss_vsync_clk",       mdss_vsync_clk.c),
	CLK_LOOKUP("mdss_mdp_clk_src",     mdss_mdp_clk_src.c),
	CLK_LOOKUP("mdss_mdp_clk",         mdss_mdp_clk.c),

	CLK_LOOKUP("ce1_ahb_clk",  gcc_ce1_ahb_clk.c),
	CLK_LOOKUP("ce1_axi_clk",  gcc_ce1_axi_clk.c),
	CLK_LOOKUP("ce1_core_clk", gcc_ce1_clk.c),
	CLK_LOOKUP("ce1_src_clk",  ce1_clk_src.c),
};
qgic_init

qgic 是Qualcomm generic interrupt controller 的简写 ,也就是高通通用中断控制器
其中做了两步:
1.qgic 分配器的初始化
2.qgic cpu 控制器的初始化

qtimer_init

为timer设置频率,在很多平台下都是19200000

scm_init

这个函数检查了scm是否能够使用,scm 是 secure channel manager的简称,负责normal world 和 secure world 之间的通信 , secure world 就是trustzone。

scm_init -> scm_arm_support_available() -> is_scm_call_available() -> scm_call2()

最终会调用到scm_call2()这个函数,它其实就是 TrustZone 开放给普通世界的 API 接口,这个函数的两个输入参数很重要:
scmcall_arg ( 这个结构想当于一个数据包,负责携带需要传递给 TrustZone 的参数信息)

typedef struct { 				
	uint32_t x0;/* command ID details as per ARMv8 spec :
					0:7 command, 8:15 service id
					0x02000000: SIP calls
					30: SMC32 or SMC64
					31: Standard or fast calls*/
	uint32_t x1; /* # of args and attributes for buffers
				  * 0-3: arg #
				  * 4-5: type of arg1
				  * 6-7: type of arg2
				  * :
				  * :
				  * 20-21: type of arg8
				  * 22-23: type of arg9
				  */
	uint32_t x2; /* Param1 */
	uint32_t x3; /* Param2 */
	uint32_t x4; /* Param3 */
	uint32_t x5[10]; /* Indirect parameter list */
	uint32_t atomic; /* To indicate if its standard or fast call */
} scmcall_arg;

scmcall_ret (这个结构则存储着 TrustZone 返回的数据信息,但是只有数据小于 12 字节才用这个结构返回,其他的数据应该在参数中放入一个返回用的 buffer 和长度)

typedef struct
{
	uint32_t x1;
	uint32_t x2;
	uint32_t x3;
} scmcall_ret;


target_early_init

这个函数的主要作用是为平台开启调试 uart 调试接口

void target_early_init(void)
{
#if WITH_DEBUG_UART
	uart_dm_init(1, 0, BLSP1_UART0_BASE);
#endif
}

可见定义了WITH_DEBUG_UART宏,就可以开启uart调试接口,比如可以开启串口打印


细化上述函数_软件初始化

bs_set_timestamp

这个函数的调用过程有些复杂,不好贴代码
描述一下函数功能好了:就是读取当前的 TimeTick 然后保存到 bootstate 中。

读取TimeTick的地址为:

#define MPM2_MPM_SLEEP_TIMETICK_COUNT_VAL  0x004A3000   //平台不同地址可能不同

保存到bootstate的地址为:

#define MSM_SHARED_IMEM_BASE               0x08600000
#define BS_INFO_OFFSET                     (0x6B0)
#define BS_INFO_ADDR                       (MSM_SHARED_IMEM_BASE + BS_INFO_OFFSET)

bootstate 的地址其实就是一个数据结构,每个成员存储了对应的时间信息,具体的成员对应的含义有以下定义:

enum bs_entry {
	BS_BL_START = 0,
	BS_KERNEL_ENTRY,
	BS_SPLASH_SCREEN_DISPLAY,
	BS_KERNEL_LOAD_TIME,
	BS_KERNEL_LOAD_START,
	BS_KERNEL_LOAD_DONE,
	BS_DTB_OVERLAY_START,
	BS_DTB_OVERLAY_END,
	BS_MAX,
};

当前设置的就是 BS_BL_START 的时间,代表了 bootloader 的启动时间


call_constructors

这个函数是 lk 和 c++ 联合编译使用的特性,主要是为了调用 c++ 代码的构造函数,单纯的 lk 中这个函数并没有作用

static void call_constructors(void)
{
	void **ctor;

	ctor = &__ctor_list;
	while(ctor != &__ctor_end) {
		void (*func)(void);

		func = (void (*)())*ctor;

		func();
		ctor++;
	}
}

heap_init

这个函数的作用就是初始化堆空间

void heap_init(void)
{
	LTRACE_ENTRY;

	// set the heap range
	theheap.base = (void *)HEAP_START;
	theheap.len = HEAP_LEN;

	LTRACEF("base %p size %zd bytes\n", theheap.base, theheap.len);

	// initialize the free list
	list_initialize(&theheap.free_list);

	// create an initial free chunk
	heap_insert_free_chunk(heap_create_free_chunk(theheap.base, theheap.len));

	// dump heap info
//	heap_dump();

//	dprintf(INFO, "running heap tests\n");
//	heap_test();
}

其中涉及到的最重要的全局变量就是 theheap, 这个变量保存了 lk 所使用的堆空间的信息,其结构如下:

struct list_node {
  struct list_node *prev;
  struct list_node *next;
};

struct heap {
	void *base;
	size_t len;
	struct list_node free_list;
};

static struct heap theheap;

theheap.basetheheap.len 对应的宏定义如下:

// end of the binary
extern int _end;

// end of memory
extern int _end_of_ram;

#define HEAP_START ((unsigned long)&_end)
#define HEAP_LEN ((size_t)&_end_of_ram - (size_t)&_end)

_end 和 _end_of_ram 这两个符号都是在链接的时候由链接器来确定的。_end 表示程序代码尾地址, _end_of_ram 表示 lk 内存尾地址,也就是说 lk 堆空间就是程序代码尾部到内存尾部所有空间
theheap.free_list 维护着一个堆链表,其中保存着堆中所有空闲的堆块,现在的初始化阶段,只有一块完整的堆空间。


thread_init

thread_init 函数位于 kernel/thread.c 文件中,关于线程的初始化在 thread_init_early 中已经完成, thread_init 只是一个空接口


dpc_init

创建并启动一个名为 dpc 的线程, dpc 的全称是 deferred procedure call, 就是延期程序调用的意思
它的作用是可以在其中注册函数,然后在触发 event 时调用函数

void dpc_init(void)
{
	thread_t *thr;

	event_init(&dpc_event, false, 0);

	thr = thread_create("dpc", &dpc_thread_routine, NULL, DPC_PRIORITY, DEFAULT_STACK_SIZE);
	if (!thr)
	{
		panic("failed to create dpc thread\n");
	}
	thread_resume(thr);
}

timer_init

主要的作用创建 lk 中的定时器链表和定时器处理函数

void timer_init(void)
{
	list_initialize(&timer_queue);										//创建 lk 中的定时器链表

	/* register for a periodic timer tick */
	platform_set_periodic_timer(timer_tick, NULL, 10); /* 10ms */		
}

其中全局链表 timer_queue 的作用就是存储定时器,而 timer_tick 函数的作用则是遍历 timer_queue 来处理其中注册的定时器回调函数

每个定时器都存储在 struct timer_t 类型的结构体中:

typedef struct timer {
  int magic;
  struct list_node node;

  time_t scheduled_time;
  time_t periodic_time;

  timer_callback callback;
  void *arg;
} timer_t;

创建bootstrap2

	dprintf(SPEW, "creating bootstrap completion thread\n");
	thr = thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
	if (!thr)
	{
		panic("failed to create thread bootstrap2\n");
	}
	thread_resume(thr);

就是创建了线程bootstrap2


原文中有关使用线程的部分,我单开一篇博客来写。
最后再强调一遍,此博客只是再次整理加工了一下别人的文章,作为记录,方便自己和正在看文章的你以后查看,原文作者:SetRet 原文链接:https://www.freebuf.com/news/135084.html

你可能感兴趣的:(Linux,bootloader)