高可靠嵌入式实时操作系统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可配置。


分享到:


相關文章: