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