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通過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可配置。