摘要:本文帶來基於LiteOS一站式開發工具LiteOS Studio,經過單步調試,來動態分析LiteOS的啓動流程。
編者按:在LiteOS大揭祕系列,咱們和讀者們分享了《LiteOS是怎麼在STM32上開始運行的》,從源碼上靜態分析了一遍LiteOS的啓動流程。本文提供一種新的方式,即基於LiteOS一站式開發工具LiteOS Studio,經過單步調試,來動態分析LiteOS的啓動流程,給開發者一個更直觀的展現。git
瞭解LiteOS系統,咱們能夠先從它的啓動流程開始。不一樣的芯片和編譯工具,其啓動流程可能會有一些差別,本文基於碼雲 LiteOS開源站點 master分支12月的代碼,以STM32F769IDISCOVERY(ARM Cortex M7)開發板和GCC編譯工具爲例,使用LiteOS Studio的單步調試,動態分析LiteOS的啓動流程。json
在開始前,須要準備好LiteOS Studio環境,包含LiteOS Studio安裝、新建工程、編譯、燒錄,掌握LiteOS Studio如何調測等等,能夠參考官網文檔站點https://liteos.gitee.io/liteo...。segmentfault
注意,若是開發板使用的是板載ST-LINK仿真器,須要刷爲JLINK。請參考 st-link仿真器單步調測。app
另外,執行單步調測,默認中止在main()函數。LiteOS操做系統的啓動是從main函數開始的。而ARM Cortex-M芯片從上電到執行main函數,中間通過了Reset_Handler等函數。LiteOS系統重啓、復位等都是從Reset_Handler函數開始執行的。在LiteOS Studio工程找到文件.vscodelaunch.json,把其中的postLaunchCommands屬性下面的"b main"改成"b Reset_Handler"。以下圖:dom
從新開始調測,系統會暫停在Reset_Handler函數處。以下圖:函數
當對STM32F769IDISCOVERY開發板進行上電操做或者復位操做時,該開發板會從異常向量表中獲取Reset_Handler函數的地址並執行該函數。彙編文件targetsSTM32F769IDISCOVERYlos_startup_gcc.S定義了該函數。工具
los_startup_gcc.S是啓動引導文件,從Reset_Handler開始到執行main函數,主要工做就是準備C代碼的運行環境,具體包括:oop
代碼以下:post
Reset_Handler: cpsid i ldr sp, =_estack /* set stack pointer */ /* Copy the vector_ram segment initializers from flash to SRAM */ movs r1, #0 b LoopCopyVectorInit CopyVectorInit: ldr r3, =_si_liteos_vector_data ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4 LoopCopyVectorInit: ldr r0, =_s_liteos_vector ldr r3, =_e_liteos_vector adds r2, r0, r1 cmp r2, r3 bcc CopyVectorInit /* Copy the data segment initializers from flash to SRAM */ movs r1, #0 b LoopCopyDataInit CopyDataInit: ldr r3, =_sidata ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4 LoopCopyDataInit: ldr r0, =_sdata ldr r3, =_edata adds r2, r0, r1 cmp r2, r3 bcc CopyDataInit ldr r2, =_sbss b LoopFillZerobss /* Zero fill the bss segment. */ FillZerobss: movs r3, #0 str r3, [r2], #4 LoopFillZerobss: ldr r3, = _ebss cmp r2, r3 bcc FillZerobss /* Call the clock system initialization function.*/ bl SystemInit /* Call static constructors */ /* bl __libc_init_array */ /* Call the application's entry point.*/ bl main bx lr
Data段存放的是已經初始化的全局變量,須要從Flash中獲取這些數據到RAM中。而bss段存放的是沒有初始化的全局變量,所以Flash中並無bss段的變量值,因此啓動引導文件只是對RAM中的.bss段進行清零操做。開發工具
los_startup_gcc.S啓動引導文件中使用的_estack 、_si_liteos_vector_data、_s_liteos_vector、_e_liteos_vector、_sidata、_sdata 、_edata、_sbss、_ebss,這些符號都定義在targetsSTM32F769IDISCOVERYliteos.ld連接腳本中。
連接腳本根據應用須要,設置堆棧大小和棧地址,並控制每一個段的存放位置。對於中斷向量和data段,既要放到Flash中,也須要放到RAM中,並經過連接腳本的AT關鍵字把Flash的地址設定爲load地址。
注:連接腳本中的相關代碼能夠訪問https://gitee.com/LiteOS/LiteOS/blob/master/targets/STM32F769IDISCOVERY/liteos.ld查看。
los_startup_gcc.S啓動引導文件中除了定義Reset_Handler函數,還定義了其餘中斷異常處理函數Default_Handler,併爲Default_Handler的每一個異常處理程序提供弱別名。所謂弱別名,即具備相同名稱的任何函數都將覆蓋此處的函數。這樣作能夠防止用戶使能了中斷卻沒有設置中斷處理程序時形成的崩潰。Default_Handler函數只是進入一個無限循環以保留系統狀態供調試器檢查。
如今咱們來單步調測運行los_startup_gcc.S,啓動調測後,系統會暫停在Reset_Handler函數的第一行代碼cpsid i,此語句用來關中斷,執行先後,觀察寄存器primask值的變化,會發現由0變爲1。繼續執行語句" ldr sp, =_estack",一樣觀察寄存器,寄存器sp的值變化了。以下圖:
繼續運行單步調測,觀察如何調用LoopCopyVectorInit和CopyVectorInit,實現把中斷向量從Flash複製到RAM的。在調測過程當中,寄存器的數值多是10進制進行展現的,若是想查看其餘進制展現的數值,能夠在調測界面的監視器窗口輸入$寄存器名稱+進制代碼來切換進制查看,如$r0,x來查看r0寄存器的16進制。詳細的進制代碼以下:
進制切換如圖所示:
因爲循環次數較多,若是想跨過中斷向量的複製,繼續下面的代碼,能夠設置斷點,而後F5繼續調測到斷點處。以下圖,咱們在118行設置了斷點,繼續執行會完成向量表的複製,去執行數據段data的初始化。
以此類推,經過Studio邊調測、邊分析啓動過程的後續代碼。當執行到語句「bl main」,再按F11跳入繼續執行時,就會跳轉到C代碼的main函數。下文繼續分析main函數。
LiteOS的main函數定義在targetsSTM32F769IDISCOVERYSrcmain.c。main函數主要負責LiteOS的初始化工做。代碼以下:
INT32 main(VOID) { HardwareInit(); PRINT_RELEASE("n********Hello Huawei LiteOS********n" "nLiteOS Kernel Version : %sn" "build data : %s %snn" "**********************************n", HW_LITEOS_KERNEL_VERSION_STRING, __DATE__, __TIME__); UINT32 ret = OsMain(); if (ret != LOS_OK) { return LOS_NOK; } OsStart(); return 0; }
硬件初始化函數HardwareInit()和主要芯片相關,這裏不作詳細介紹。下面介紹LiteOS內核的初始化,代碼以下:
LITE_OS_SEC_TEXT_INIT UINT32 OsMain(VOID) { UINT32 ret; #ifdef LOSCFG_EXC_INTERACTION ret = OsMemExcInteractionInit((UINTPTR)&__bss_end); if (ret != LOS_OK) { return ret; } #endif /* 初始化動態內存池 */ ret = OsMemSystemInit((UINTPTR)&__bss_end + g_excInteractMemSize); if (ret != LOS_OK) { return ret; } /* * 配置最大支持的任務個數、信號量個數、互斥鎖個數、 * 隊列個數以及軟件定時器個數,設置g_sysClock和 * g_tickPerSecond全局變量 */ OsRegister(); #ifdef LOSCFG_SHELL_LK OsLkLoggerInit(NULL); #endif #ifdef LOSCFG_SHELL_DMESG ret = OsDmesgInit(); if (ret != LOS_OK) { return ret; } #endif /* * 初始化硬中斷,此後LiteOS就會接管系統的中斷, * 使用中斷前須要先註冊中斷並使能 */ OsHwiInit(); /* * 設置中斷向量的中斷處理函數,包括 * Hard Fault硬件故障中斷、 * Non Maskable Interrupt不可屏蔽中斷(NMI)、 * Memory Management內存管理中斷、 * Bus Fault 總線故障中斷、 * Usage Fault使用故障中斷、 * SVCall利用SVC指令調用系統服務的中斷 */ ArchExcInit(); ret = OsTickInit(GET_SYS_CLOCK(), LOSCFG_BASE_CORE_TICK_PER_SECOND); if (ret != LOS_OK) { return ret; } #ifdef LOSCFG_PLATFORM_UART_WITHOUT_VFS uart_init(); #ifdef LOSCFG_SHELL extern int uart_hwiCreate(void); /* HuaWeiChange */ uart_hwiCreate(); #endif /* LOSCFG_SHELL */ #endif /* LOSCFG_PLATFORM_UART_WITHOUT_VFS */ /* * 初始化任務鏈表包括任務的排序鏈表, * 初始化優先級消息隊列鏈表(用於管理不一樣優先級任務) */ ret = OsTaskInit(); if (ret != LOS_OK) { PRINT_ERR("OsTaskInit errorn"); return ret; } #ifdef LOSCFG_KERNEL_TRACE ret = LOS_TraceInit(NULL, LOS_TRACE_BUFFER_SIZE); if (ret != LOS_OK) { PRINT_ERR("LOS_TraceInit errorn"); return ret; } #endif /* * 初始化任務監視器 */ #ifdef LOSCFG_BASE_CORE_TSK_MONITOR OsTaskMonInit(); #endif /* * OsIpcInit包括初始化消息隊列鏈表、互斥鎖鏈表和信號量鏈表 */ ret = OsIpcInit(); if (ret != LOS_OK) { return ret; } /* * CPUP should be inited before first task creation which depends on the semaphore * when LOSCFG_KERNEL_SMP_TASK_SYNC is enabled. So don't change this init sequence * if not necessary. The sequence should be like this: * 1. OsIpcInit * 2. OsCpupInit -> has first task creation * 3. other inits have task creation */ #ifdef LOSCFG_KERNEL_CPUP ret = OsCpupInit(); if (ret != LOS_OK) { PRINT_ERR("OsCpupInit errorn"); return ret; } #endif /* * OsSwtmrInit對軟件定時器和其在percpu上的排序鏈表進行初始化, * 並初始化按期器處理函數的內存池,同時還會建立軟件定時器 * 的消息隊列和定時器任務 */ #ifdef LOSCFG_BASE_CORE_SWTMR ret = OsSwtmrInit(); if (ret != LOS_OK) { return ret; } #endif #ifdef LOSCFG_KERNEL_SMP (VOID)OsMpInit(); #endif #ifdef LOSCFG_KERNEL_DYNLOAD ret = OsDynloadInit(); if (ret != LOS_OK) { return ret; } #endif #if defined(LOSCFG_HW_RANDOM_ENABLE) || defined (LOSCFG_DRIVERS_RANDOM) random_alg_context.ra_init_alg(NULL); run_harvester_iterate(NULL); #endif /* 建立空閒任務 */ ret = OsIdleTaskCreate(); if (ret != LOS_OK) { return ret; } #ifdef LOSCFG_KERNEL_RUNSTOP ret = OsWowWriteFlashTaskCreate(); if (ret != LOS_OK) { return ret; } #endif #ifdef LOSCFG_DRIVERS_BASE ret = OsDriverBaseInit(); if (ret != LOS_OK) { return ret; } #ifdef LOSCFG_COMPAT_LINUX (VOID)do_initCalls(LEVEL_ARCH); #endif #endif #ifdef LOSCFG_KERNEL_PERF ret = LOS_PerfInit(NULL, LOS_PERF_BUFFER_SIZE); if (ret != LOS_OK) { return ret; } #endif /* * LOSCFG_PLATFORM_OSAPPINIT宏默認已經在.config、menuconfig.h中定義。 * OsAppInit建立了一個名爲「app_Task」的任務,該任務處理函數爲 * app_init,任務優先級爲10; * OsTestInit建立了一個名爲「IT_TST_IN」的任務,該任務處理函數爲 * TestTaskEntry,任務優先級爲25。該函數暫時沒有開源。 */ #ifdef LOSCFG_PLATFORM_OSAPPINIT ret = osAppInit(); #else /* LOSCFG_TEST */ ret = OsTestInit(); #endif if (ret != LOS_OK) { return ret; } return LOS_OK; }
完成內核的初始化後,調用OsStart()開始任務調度,自此LiteOS開始正常工做。OsStart函數的代碼以下:
LITE_OS_SEC_TEXT_INIT VOID OsStart(VOID) { LosTaskCB *taskCB = NULL; /* 獲取當前執行任務的CPU ID,STM32F769是單核芯片,cpuid爲0 */ UINT32 cpuid = ArchCurrCpuid(); /* * 配置Tick中斷向量,其中斷處理函數爲OsTickHandler。 * 初始化System Tick Timer及其中斷,並啓動此Timer。 * 計數器會產生週期性中斷 */ OsTickStart(); LOS_SpinLock(&g_taskSpin); /* 獲取最高優先級任務隊列中的第一個任務,賦給taskCB */ taskCB = OsGetTopTask(); #ifdef LOSCFG_KERNEL_SMP /* * attention: current cpu needs to be set, in case first task deletion * may fail because this flag mismatch with the real current cpu. */ taskCB->currCpu = (UINT16)cpuid; #endif /* 設置32位的調度flag,第CPU ID位設置爲1 */ OS_SCHEDULER_SET(cpuid); PRINTK("cpu %u entering schedulern", cpuid); /* * 調度g_runTask即taskCB任務,OsStartToRun函數 * 定義在los_dispatch.S彙編文件中 */ OsStartToRun(taskCB); }
如今咱們來單步調測運行main.c源代碼,LiteOS Studio在調測時,能夠同步展現當前運行的源代碼行,及對應的反彙編文件行,以下圖:
在調測過程當中,變量的數值多是10進制進行展現的,若是想查看其餘進制展現的數值,能夠在調測界面的監視器窗口輸入變量名稱名稱+進制代碼來切換進制查看,如memStart,x來查看變量memStart的16進制。如圖:
本期分享使用LiteOS Studio查看LiteOS啓動過程,同時展現了使用LiteOS Studio調測的技巧,你們能夠繼續邊調測、邊分析後續的代碼,會看到LiteOS整個啓動流程:從板子復位上電開始,調用匯編代碼Reset_Handler進入啓動引導文件,完成C代碼運行環境的準備工做、最後跳轉到main函數。在main函數中完成硬件初始化和LiteOS內核的初始化,並經過彙編跳轉到執行第一個最高優先級的任務命令的地址上,從而開始LiteOS的運行。
歡迎你們分享使用LiteOS Studio調測LiteOS的心得,有任何問題、建議,均可以在開源LiteOS社區(https://gitee.com/liteos)留言,謝謝!
本文分享自華爲雲社區《使用LiteOS Studio圖形化查看LiteOS在STM32上運行的奧祕》,原文做者:zhushy 。