AliOS Things的啓動過程分析(二)
在AliOS Things的啓動過程分析(一)中分析了developerkit從系統上電到調用main函數所經歷的一些步驟,接下來詳細分析一下main函數的一些工作,主要是內核的相關初始化工作。main函數所處的位置位於 platform\mcu\stm32l4xx_cube\aos\aos.c文件中。下面是main函數的代碼。
int main(void)
{
sys_start();
return 0;
}
static void sys_start(void)
{
aos_heap_set(); //設置堆的起始地址和長度
aos_init(); //即內核初始化,在其中完成了必要的初始化(內存初始化、內核對象初始化)、新建立了幾個默認的任務(定時器任務、空閒任務、工作隊列等)
//建立sys_init任務,進入應用層代碼
krhino_task_create(&demo_task_obj, "aos-init", 0,AOS_DEFAULT_APP_PRI,
0, demo_task_buf, AOS_START_STACK, (task_entry_t)sys_init, 1);
aos_start(); //即內核啓動。代表開始任務調度
}
void aos_heap_set(void)
{
g_mm_region[0].start = (uint8_t*)&__bss_end__; //在ld鏈接文件中指定的地址
g_mm_region[0].len =
((uint8_t*)&_estack - (uint8_t*)&__bss_end__) - RHINO_CONFIG_SYSTEM_STACK_SIZE;
//RAM區的尾地址-bss段的結束地址-偏移地址
}
在main函數之中完成的工作是首先要使用aos_heap_set進行堆區域的設置,之後使用aos_init函數(其實是對於krhino_init函數的封裝)進行內核的初始化,在內核初始化之後,建立一個sys_init的系統任務,在該系統任務中會調用應用業務層代碼application_start()函數。前面已經介紹了aos_heap_set函數的主要目的,接下來分析aos_init(即krhino_init的源碼)
kstat_t krhino_init(void)
{
g_sys_stat = RHINO_STOPPED; //設置系統狀態爲STOPPED
#if (RHINO_CONFIG_USER_HOOK > 0) //鉤子函數,默認是開啓的
krhino_init_hook(); //鉤子函數初始化
#endif
#if (RHINO_CONFIG_CPU_NUM > 1) //如果是多核的處理器,因爲developer僅僅有一個CPU,所以處理不會執行
krhino_spin_lock_init(&g_sys_lock);
#endif
//運行隊列初始化,系統中如果有多個任務並行處理的話,建立過多的任務對於系統的壓力很大,使用工作隊列同樣可以
//實現任務的相關操作,減輕系統壓力
runqueue_init(&g_ready_queue);
tick_list_init();//初始化g_tick_head雙向鏈表
/*內核對象初始化,內核對象數據結構爲:kobj_list_t 內部包含:互斥量 信號量 消息隊列 內存池 事件等*/
#if (RHINO_CONFIG_SYSTEM_STATS > 0)
kobj_list_init();
#endif
//內存初始化,根據前面aos_heap_init函數的g_mm_region參數對內存進行初始化
#if (RHINO_CONFIG_MM_TLF > 0)
k_mm_init();
#endif
//動態內存管理初始化,
#if (RHINO_CONFIG_KOBJ_DYN_ALLOC > 0)
klist_init(&g_res_list);
krhino_sem_create(&g_res_sem, "res_sem", 0);
dyn_mem_proc_task_start();
#endif
#if (RHINO_CONFIG_CPU_NUM > 1)
for (uint8_t i = 0; i < RHINO_CONFIG_CPU_NUM; i++) {
krhino_task_cpu_create(&g_idle_task[i], "idle_task", NULL, RHINO_IDLE_PRI, 0,
&g_idle_task_stack[i][0], RHINO_CONFIG_IDLE_TASK_STACK_SIZE,
idle_task, i, 1u);
}
#else //單核處理器創建空閒任務,系統空閒時運行該任務
krhino_task_create(&g_idle_task[0], "idle_task", NULL, RHINO_IDLE_PRI, 0,
&g_idle_task_stack[0][0], RHINO_CONFIG_IDLE_TASK_STACK_SIZE,
idle_task, 1u);
#endif
//工作隊列初始化
#if (RHINO_CONFIG_WORKQUEUE > 0)
workqueue_init();
#endif
//定時器初始化,建立一個定時器任務,用於管理系統的定時器列。
#if (RHINO_CONFIG_TIMER > 0)
ktimer_init();
#endif
rhino_stack_check_init();
return RHINO_SUCCESS;
}
以上爲內核的初始化部分的相關介紹,主要是新建立了幾個任務,初始化了一些必要的內核對象等。內核初始化的細節部分沒有具體去介紹,因爲內核部分涉及到的數據結構較多,在此處進行介紹,篇幅較長,以後建立一個內核的章節來介紹其內部實現細節。
在完成了內核的初始化工作之後,會調用任務建立接口新建立一個sys_init的任務,該任務會進行板級的相關初始化,並調用業務層代碼入口函數application_start。
新建立了任務僅僅是在任務隊列中加入了該任務,任務並沒有開始調度執行,aos_start(即krhino_start)爲內核啓動進入調度的函數。此時sys_init任務才能夠運行起來。
aos_start內核調度函數如下所示:
kstat_t krhino_start(void)
{
ktask_t *preferred_task;
if (g_sys_stat == RHINO_STOPPED) { //當前系統爲STOPPED狀態
#if (RHINO_CONFIG_CPU_NUM > 1) //多核處理器的調度方法
for (uint8_t i = 0; i < RHINO_CONFIG_CPU_NUM; i++) {
preferred_task = preferred_cpu_ready_task_get(&g_ready_queue, i);
preferred_task->cpu_num = i;
preferred_task->cur_exc = 1;
g_preferred_ready_task[i] = preferred_task;
g_active_task[i] = g_preferred_ready_task[i];
g_active_task[i]->cur_exc = 1;
}
#else //單核處理器
//從RDY隊列中取出最高優先級的任務
preferred_task = preferred_cpu_ready_task_get(&g_ready_queue, 0);
g_preferred_ready_task[0] = preferred_task;//保存位於RDY隊列中的當前最高優先級的任務
g_active_task[0] = preferred_task; //當前運行的任務
#endif
#if (RHINO_CONFIG_USER_HOOK > 0)
krhino_start_hook(); //啓動鉤子函數,主要用於記錄當前任務的時間信息
#endif
g_sys_stat = RHINO_RUNNING; //修改系統狀態爲RUNNING
cpu_first_task_start(); //任務啓動,設置任務控制塊
/* should not be here */
return RHINO_SYS_FATAL_ERR;
}
return RHINO_RUNNING;
}
aos_start函數運行起來之後,相當於此時內核已經完成了調度。當前系統中除了默認建立的空閒任務和定時器任務之外,還有一個剛剛建立的sys_init的任務。接下里分析一下sys_init函數所完成的主要功能:
sys_init函數代碼如下所示,這是系統開始運行的第一個和硬件版以及應用程序有關的任務代碼,前面的初始化部分僅僅是內核相關的初始化代碼。在該函數中完成的功能是:
- SoC處理器與硬件開發板相關初始化
- hal
- 開發板的flash分區以及cli組件初始化
- 嚮應用程序傳遞參數初始化
- alios things相關的組件初始化
static void sys_init(void)
{
/*在該函數中主要完成flash 預存取 、數據以及指令緩存使能或者關閉,
//設置NVIC 組別,設置時鐘時基標準、設置。以及進行HAL_Mspinit硬件初始化*/
stm32_soc_init();
#ifdef BOOTLOADER
main();
#else
//進行OTA對象的初始化以及wifi的初始化
hw_start_hal();
//對flash區域進行邏輯分析,設置各個分區的起始和終止地址
//新建一個fota信號量
board_init();
//傳參初始化,設置傳參個數,數據地址,以及是否使能cli組件
var_init();
#ifdef AOS_CPLUSPLUS
cpp_init();
#endif
//函數完成的功能是各個組件的初始化以及進入應用程序
aos_kernel_init(&kinit);
#endif
}
接下來主要分析aos_kernel_init(&kinit);函數,以註釋的形式進行分析
int aos_kernel_init(kinit_t *kinit)
{
//VFS虛擬文件系統初始化,詳情可以參考https://www.yuque.com/beiqiang/linux_related/itm2vp 的介紹
#ifdef AOS_VFS
vfs_init(); //建立VFS互斥鎖,初始化inode數組
vfs_device_init(); //註冊/dev/event節點以及操作方法
#endif
#ifdef CONFIG_AOS_CLI
cli_service_init(kinit); //CLI組件的初始化,並將參數傳遞進來
#else
extern void log_no_cli_init(void);
log_no_cli_init();
#endif
#ifdef AOS_KV
aos_kv_init(); //KV組件存儲初始化
#endif
#ifdef WITH_SAL
sal_device_init(); //SAL網絡框架初始化
#endif
#ifdef AOS_LOOP
aos_loop_init(); //在主任務中系統默認創建了一個YLOOP實例
#endif
#ifdef OSAL_RHINO
trace_start();
#endif
#ifdef AOS_UOTA
ota_service_init(); //OTA固件升級初始化
#endif
#ifdef AOS_SENSOR
sensor_init(); //傳感器模塊初始化
#endif
#ifdef AOS_GPS
gps_init();
#endif
// auto_component generated by the compiler system, now gcc support
#if defined (__GNUC__) && !defined (__CC_ARM)
//aos_components_init();
#endif
#ifdef AOS_BINS
app_pre_init();
#ifdef AOS_FRAMEWORK_COMMON
aos_framework_init(); //MESH網絡的初始化工作,uData傳感器框架的初始化工作
#endif
if (app_info->app_entry) {
#if (RHINO_CONFIG_USER_SPACE > 0)
app_info->app_entry(0, NULL);
#else
app_info->app_entry((void *)kmbins_tbl, 0, NULL);
#endif
}
#else
#if (RHINO_CONFIG_CPU_USAGE_PERIOD > 0) //CPU 使用記錄
krhino_task_cpu_usage_init();
#endif
application_start(kinit->argc, kinit->argv); //調用業務層代碼入口函數
#endif
return 0;
}
總結
以上就是從系統開始進入main函數到調用業務層函數入口application_start的全部過程,其中有很多組件以及內核的初始化工作。爲了能夠更好的實現自己的代碼,對系統的整個流程有一個清晰的認識是很必要的工作。在本篇文章中,僅僅是簡單的分析了較爲頂層的一個初始化流程,具體的細節會在後續的文章中繼續分析。