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之前的部分不講了):
- Kmain
- bootstrap2
- aboot_init(包含fastboot)
- 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.base
和 theheap.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