高可靠嵌入式實時操作系統Nuttx啟動流程

NuttX實時操作系統概述

NuttX是一個嵌入式實時操作系統(Embedded RTOS),它很小巧,在微控制器環境中使用。NuttX主要遵循POSIX和ANSI標準開發,可從8位微控制器環境擴展到32位微控制器環境,對於在上述標準下不可用的功能,或者對於不適合嵌入式環境(如fork())的功能,採用了Unix和其他常見RTOS的其他標準API(例如VxWorks)。NuttX支持ARM、Atmel AVR、x86、MIPS、OpenRISC、Renesas、RISC-V、Zilog、Misoc等多種CPU體系結構。本文章選取使用最廣泛的x86體系結構介紹Nuttx的啟動流程。

高可靠嵌入式實時操作系統Nuttx啟動流程

Nuttx實時操作系統啟動流程

Nuttx通過GCC編譯器鏈接生成一個可執行的ELF文件。每一個鏈接過程都由鏈接腳本控制。鏈接腳本主要用於規定如何把輸入文件內的section放入輸出文件內, 並控制輸出文件內各部分在程序地址空間內的佈局。連接器有個默認的內置連接腳本, 可用ld –verbose查看。-T選項用以指定自己的鏈接腳本, 它將代替默認的連接腳本。x86平臺的Nuttx鏈接腳本位於nuttx-8.1\boards\x86\qemu\qemu-i486\scripts\qemu.ld目錄。鏈接腳本內容如下,其ENTRY指定__start()是第一個被調用執行的函數,其代碼段起始地址為0x100000。

OUTPUT_ARCH(i386)

ENTRY(__start)

SECTIONS

{

. = 0x00100000;

.text :

{

_stext = ABSOLUTE(.);

*(.text .text.*)

*(.gnu.linkonce.t.*)

_etext = ABSOLUTE(.);

}

.text ALIGN (0x1000) :

{

_srodata = ABSOLUTE(.);

*(.rodata .rodata.*)

*(.fixup)

*(.gnu.warning)

*(.glue_7)

*(.glue_7t)

*(.got)

*(.gcc_except_table)

*(.gnu.linkonce.r.*)

_erodata = ABSOLUTE(.);

}

.data ALIGN (0x1000) :

{

_sdata = ABSOLUTE(.);

*(.data .data.*)

*(.gnu.linkonce.d.*)

CONSTRUCTORS

_edata = ABSOLUTE(.);

}

.bss :

{

_sbss = ABSOLUTE(.);

*(.bss .bss.*)

*(.gnu.linkonce.b.*)

*(COMMON)

_ebss = ABSOLUTE(.);

}

/* Stabs debugging sections */

.stab 0 : { *(.stab) }

.stabstr 0 : { *(.stabstr) }

.stab.excl 0 : { *(.stab.excl) }

.stab.exclstr 0 : { *(.stab.exclstr) }

.stab.index 0 : { *(.stab.index) }

.stab.indexstr 0 : { *(.stab.indexstr) }

.comment 0 : { *(.comment) }

.debug_abbrev 0 : { *(.debug_abbrev) }

.debug_info 0 : { *(.debug_info) }

.debug_line 0 : { *(.debug_line) }

.debug_pubnames 0 : { *(.debug_pubnames) }

.debug_aranges 0 : { *(.debug_aranges) }

}

__start函數位於nuttx-8.1\arch\x86\src\qemu\qemu_head.S文件中,其代碼如下:

/****************************************************************************

* Name: Start

****************************************************************************/

.type__start, @function

__start:

/* Set up the stack */

mov$(_ebss + CONFIG_IDLETHREAD_STACKSIZE), %esp

/* Multiboot setup */

push%eax/* Multiboot magic number */

push%ebx/* Multiboot data structure */

/* Initialize and start NuttX */

callup_lowsetup/* Low-level, pre-OS initialization */

callnx_start/* Start NuttX */

/* NuttX will not return */

cli

hang:

hlt/* Halt machine should NuttX return */

jmphang

.size__start, . - __start

對基於棧的計算機系統而言,棧是存儲局部變量和進行函數調用所必不可少的連續內存區域。編譯器、操作系統和CPU按照規範各司其職,保證正確合理的使用棧,因此在調用第一個C函數up_lowsetup之前必須設置棧寄存器esp。其中_ebss由上面的鏈接腳本定義,由於在x86系統中棧是朝低地址方向生長的,壓棧操作(push指令)會導致esp寄存器(棧指針)的值減少,彈出操作(pop指令)會導致esp的值變大,mov $(_ebss + CONFIG_IDLETHREAD_STACKSIZE), %esp指令確定了棧的大小為CONFIG_IDLETHREAD_STACKSIZE。

如果寫過彙編的都知道GCC下彙編採用AT&T彙編語法格式,其與INTEL彙編語法有一定不同。其基本點如下:

  • 引用寄存器要在寄存器號前加%,如mov %eax,%ebx
  • 操作數排列是從源(左)到目的(右),如mov %eax(源),%ebx(目的) (eax寄存器的值存入ebx寄存器)
  • 使用立即數,要在數前面加$,如mov $4,%eax (立即數4存入eax寄存器)
  • 符號常數直接引用如 mov value,%ebx (變量value的值存入ebx寄存器)
  • 引用符號地址在符號前加$,如mov $value, %ebx (變量value的地址存入ebx寄存器)

掌握上述語法規則後,閱讀上面的彙編代碼就不覺得彆扭了。言歸正傳,設置esp棧寄存器後,執行"callup_lowsetup"指令調用第一個C函數up_lowsetup。該函數代碼如下:

void up_lowsetup(void)

{

/* Initialize the Global descriptor table */

up_gdtinit();

/* Early serial driver initialization */

#ifdef USE_EARLYSERIALINIT

up_earlyserialinit();

#endif

/* Now perform board-specific initializations */

x86_boardinitialize();

}

該函數主要是設置全局描述符表gtd、初始化串口和板級相關初始化。up_lowsetup函數執行完成後緊接著執行"call nx_start"指令調用nx_start函數。nx_start函數是為了初始化操作系統並生成執行的用戶初始化線程,進入Nuttx的初始入口。

nx_start主要包含初始化所有任務鏈表、初始化IDEL任務控制塊、信號量初始化、內核堆初始化、用戶堆初始化、任務管理初始化、中斷初始化、看門狗初始化、時鐘初始化、定時器初始化、信號初始化、消息隊列初始化、文件系統初始化、網絡協議棧初始化、共享內存初始化、C庫初始化、SMP多核初始化以及調用nx_bringup->nx_create_initthread創建初始化線程。

static inline void nx_start_application(void)

{

int pid;

sinfo("Starting init thread\n");

pid = nxtask_create("init", CONFIG_USERMAIN_PRIORITY,

CONFIG_USERMAIN_STACKSIZE,

(main_t)CONFIG_USER_ENTRYPOINT,

(FAR char * const *)NULL);

}

static int nx_start_task(int argc, FAR char **argv)

{

/* Do the board/application initialization and exit */

nx_start_application();

return OK;

}

static inline void nx_create_initthread(void)

{

int pid;

pid = kthread_create("AppBringUp", CONFIG_BOARD_INITTHREAD_PRIORITY,

CONFIG_BOARD_INITTHREAD_STACKSIZE,

(main_t)nx_start_task, (FAR char * const *)NULL);

}

其中用戶入口點CONFIG_USER_ENTRYPOINT可配置。


分享到:


相關文章: