1. 程式人生 > >Linux啟動流程_LK流程_Kmain(0)

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