Stm32F103 + NuttX(三):系统启动流程分析
发布于 2025-12-30
|
更新于
2025-12-30
|
字数:5222
在编写自己的板级配置之前,我觉得还是要先了解整个 NuttX 系统的初始化流程,看看每次复位后都运行了哪些函数,这样可以确定我们要编写的最小代码。
本文还是以 stm32f103-minimum 为例。
从链接脚本入手
这里可以看到程序入口是 _stext,这只是一个标记,位于 .text 的开始,而 .text 段以向量表 *(.vectors) 开始。*(.vectors) 是链接脚本中的段选择器,用于收集所有放置在 .vectors 段的代码。
OUTPUT_ARCH(arm)
EXTERN(_vectors)
ENTRY(_stext)
SECTIONS
{
.text : {
_stext = ABSOLUTE(.);
*(.vectors)
*(.text .text.*)
*(.fixup)
*(.gnu.warning)
*(.rodata .rodata.*)
*(.gnu.linkonce.t.*)
*(.glue_7)
*(.glue_7t)
*(.got)
*(.gcc_except_table)
*(.gnu.linkonce.r.*)
_etext = ABSOLUTE(.);
} > flash
...
}
全局查找 .vectors 可以发现这是架构通用代码,像 STM32 这种 ARMv7-M 架构的,其内容如下。这个文件定义了 NuttX 在 ARMv7-M 架构下的中断向量表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// nuttx/arch/arm/src/armv7-m/arm_vectors.c
#include <nuttx/config.h>
#include "chip.h"
#include "arm_internal.h"
#include "ram_vectors.h"
#include "nvic.h"
// 初始堆栈指针,指向 SRAM 的可用区域(BSS 段末尾加上空闲线程的堆栈大小)
#define IDLE_STACK (_ebss + CONFIG_IDLETHREAD_STACKSIZE)
extern void __start(void);
static void start(void)
{
// 设置 lr 寄存器为 0,表示这是调用链的起点。然后跳转到 __start 外部函数
asm volatile ("mov lr, #0\n\t"
"b __start\n\t");
}
extern void exception_common(void);
extern void exception_direct(void);
// 使用 locate_data(".vectors") 强制放置在 .vectors 段,确保向量表位于 FLASH 的开头
const void * const _vectors[] locate_data(".vectors")
aligned_data(VECTAB_ALIGN) =
{
// 初始堆栈指针
IDLE_STACK,
// 重置向量
start,
// [2 ... NVIC_IRQ_PENDSV] 指向通用异常处理
// 其余指向直接中断处理(用于外设中断)
[2 ... NVIC_IRQ_PENDSV] = &exception_common,
[(NVIC_IRQ_PENDSV + 1) ... (15 + ARMV7M_PERIPHERAL_INTERRUPTS)]
= &exception_direct
};
|
根据 ,《Armv7-M Architecture Reference Manual》 描述,中断向量表的第一项(偏移量 0)为 SP_main,之后的中断序号中,第一个是 Reset,因此在上述代码的设置下,我们复位后会进入 start 函数,并马上跳转到 __start。
复位入口
__start 函数位于 nuttx/arch/arm/src/stm32/stm32_start.c,它负责系统启动前的所有初始化工作,确保硬件和软件环境准备好后启动 NuttX 内核。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
#include <nuttx/config.h>
#include <stdint.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/init.h>
#include "arch/board/board.h"
#include "arm_internal.h"
#include "itm_syslog.h"
#include "nvic.h"
#include "mpu.h"
#include "stm32.h"
#include "stm32_gpio.h"
#include "stm32_userspace.h"
#include "stm32_start.h"
// 计算堆的起始地址,从 BSS 结束 + 空闲堆栈大小 开始。
#define HEAP_BASE ((uintptr_t)_ebss + CONFIG_IDLETHREAD_STACKSIZE)
// 只读变量,提供空闲线程堆栈的顶部地址,即堆的起始地址,内核使用此地址初始化空闲线程的堆栈上下文,确保堆栈不与堆冲突。
const uintptr_t g_idle_topstack = HEAP_BASE;
// 如果启用了 CONFIG_DEBUG_FEATURES,则开启输出
#ifdef CONFIG_DEBUG_FEATURES
# define showprogress(c) arm_lowputc(c)
#else
# define showprogress(c)
#endif
// 避免在早期阶段干扰堆栈检查
#ifdef CONFIG_ARMV7M_STACKCHECK
void __start(void) noinstrument_function;
#endif
void __start(void)
{
const uint32_t *src;
uint32_t *dest;
// 如果进行堆栈检查,则设置 r10 为堆栈限制(CONFIG_IDLETHREAD_STACKSIZE - 64),防止堆栈溢出
#ifdef CONFIG_ARMV7M_STACKCHECK
/* Set the stack limit before we attempt to call any functions */
__asm__ volatile("sub r10, sp, %0" : :
"r"(CONFIG_IDLETHREAD_STACKSIZE - 64) :);
#endif
// MPU 早期重置
mpu_early_reset();
// 时钟配置
stm32_clockconfig();
arm_fpuconfig();
// 初始化用于串口通信的 USART,确保调试输出可用
stm32_lowsetup();
stm32_gpioinit();
// DEBUG 阶段性输出,后续同理
showprogress('A');
// 清零 BSS 段
for (dest = (uint32_t *)_START_BSS; dest < (uint32_t *)_END_BSS; )
{
*dest++ = 0;
}
showprogress('B');
// 将已初始化的数据段从其在 FLASH 中的临时存放位置移动到 SRAM 中的正确位置
for (src = (const uint32_t *)_DATA_INIT,
dest = (uint32_t *)_START_DATA; dest < (uint32_t *)_END_DATA;
)
{
*dest++ = *src++;
}
showprogress('C');
// 可选初始化:堆栈检查初始化
#ifdef CONFIG_ARMV7M_STACKCHECK
arm_stack_check_init();
#endif
// 可选初始化:性能事件初始化
#ifdef CONFIG_ARCH_PERF_EVENTS
up_perf_init((void *)STM32_SYSCLK_FREQUENCY);
#endif
// 可选初始化:ITM SYSLOG 初始化
#ifdef CONFIG_ARMV7M_ITMSYSLOG
itm_syslog_initialize();
#endif
// 可选初始化:早期串口初始化
#ifdef USE_EARLYSERIALINIT
arm_earlyserialinit();
#endif
showprogress('D');
// 对于用户空间/内核空间分离的构建,进行平台特定的用户内存初始化
#ifdef CONFIG_BUILD_PROTECTED
stm32_userspace();
showprogress('E');
#endif
// 板级资源初始化
stm32_boardinitialize();
showprogress('F');
showprogress('\r');
showprogress('\n');
// 启动 NuttX 内核
nx_start();
// 防御性代码:不应该到达此处,如果 nx_start() 返回了,则进入无限循环
#ifndef CONFIG_DISABLE_IDLE_LOOP
for (; ; );
#endif
}
|
板级资源初始化(一)
既然是板级资源,那肯定和板级配置有关,这里就会跳转到 stm32f103-minimum/src/stm32_boot.c。这里也是根据可选的配置进行分别进行自动 LED、SPI、USB 的初始化。
这里还有一个很重要的函数是 board_late_initialize,它是在内核初始化之后的板级资源初始化,用于部分复杂的设备驱动可能依赖内核服务的情况。比如字符设备的注册就需要内核先进行文件系统初始化。CONFIG_BOARD_LATE_INITIALIZE 宏在当前配置下是开启的,后续会再跳到这个函数再次进行板级初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include <nuttx/config.h>
#include <nuttx/spi/spi.h>
#include <debug.h>
#include <nuttx/board.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "stm32f103_minimum.h"
void stm32_boardinitialize(void)
{
#ifdef CONFIG_ARCH_LEDS
board_autoled_initialize();
#endif
#if defined(CONFIG_STM32_SPI1) || defined(CONFIG_STM32_SPI2)
stm32_spidev_initialize();
#endif
#if defined(CONFIG_USBDEV) && defined(CONFIG_STM32_USB)
stm32_usbinitialize();
#endif
}
#ifdef CONFIG_BOARD_LATE_INITIALIZE
void board_late_initialize(void)
{
#ifndef CONFIG_BOARDCTL
stm32_bringup();
#endif
}
#endif
|
内核初始化
nx_start 位于 nuttx/sched/init/nx_start.c,负责进行 NuttX 内核的初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
void nx_start(void)
{
int i;
// DEBUG 输出
sinfo("Entry\n");
// 初始化状态:已完成 BOOT
g_nx_initstate = OSINIT_BOOT;
// 初始化任务列表
tasklist_initialize();
// 初始化 IDLE 任务
idle_task_initialize();
// 初始化状态:任务列表已就绪
g_nx_initstate = OSINIT_TASKLISTS;
// 开始初始化 RTOS 数据
// 驱动早期初始化,允许一些驱动在内核初始化时使用
drivers_early_initialize();
// 开启调度追踪(需要配置 CONFIG_TRACE_SCHED)
sched_trace_begin();
// 开始初始化 RTOS 功能
// 初始化信号量,这进行的很早因为后续有很多子系统依赖信号量。
nxsem_initialize();
// 初始化内存管理器
#if defined(MM_KERNEL_USRHEAP_INIT) || defined(CONFIG_MM_KERNEL_HEAP) || \
defined(CONFIG_MM_PGALLOC)
{
FAR void *heap_start;
size_t heap_size;
// 可选初始化:用户内存分配器
#ifdef MM_KERNEL_USRHEAP_INIT
up_allocate_heap(&heap_start, &heap_size);
kumm_initialize(heap_start, heap_size);
#endif
// 可选初始化:内核内存分配器
#ifdef CONFIG_MM_KERNEL_HEAP
up_allocate_kheap(&heap_start, &heap_size);
kmm_initialize(heap_start, heap_size);
#endif
// 可选初始化:页分配器
#ifdef CONFIG_MM_PGALLOC
up_allocate_pgheap(&heap_start, &heap_size);
mm_pginitialize(heap_start, heap_size);
#endif
}
#endif
// 可选初始化:内核动态映射模块
#ifdef CONFIG_MM_KMAP
kmm_map_initialize();
#endif
// 可选初始化:额外的堆
#ifdef CONFIG_ARCH_HAVE_EXTRA_HEAPS
up_extraheaps_init();
#endif
// 可选初始化:IO 缓存
#ifdef CONFIG_MM_IOB
iob_initialize();
#endif
// 初始化 PID 的哈希表逻辑
// - 计算初始哈希表大小
i = 1 << LOG2_CEIL(CONFIG_PID_INITIAL_COUNT);
while (i <= CONFIG_SMP_NCPUS)
{
i <<= 1;
}
// - 分配内存空间
g_pidhash = kmm_zalloc(sizeof(*g_pidhash) * i);
DEBUGASSERT(g_pidhash);
// - 保存哈希表大小到全局变量
g_npidhash = i;
// 初始化 IDLE 任务组
idle_group_initialize();
// 记录最后一个分配的 PID。确保后续新任务的 PID 从 CONFIG_SMP_NCPUS 开始递增,避免与 IDLE PID 冲突。如果有 2 个 CPU,则下一个任务的 PID 为 2
g_lastpid = CONFIG_SMP_NCPUS - 1;
// 初始化状态:内存管理已就绪
g_nx_initstate = OSINIT_MEMORY;
// 初始化与任务相关的数据结构
task_initialize();
// 初始化 instrument 功能,看起来用于调试和性能分析
instrument_initialize();
// 初始化文件系统
fs_initialize();
// 初始化中断处理系统
irq_initialize();
// 初始化 POSIX 标准的时钟功能
clock_initialize();
// 可选初始化:定时器
#ifndef CONFIG_DISABLE_POSIX_TIMERS
timer_initialize();
#endif
// 初始化信号功能
nxsig_initialize();
// 可选初始化:消息队列
#if !defined(CONFIG_DISABLE_MQUEUE) || !defined(CONFIG_DISABLE_MQUEUE_SYSV)
nxmq_initialize();
#endif
// 可选初始化:网络
#ifdef CONFIG_NET
net_initialize();
#endif
// 可选初始化:二进制格式
#ifndef CONFIG_BINFMT_DISABLE
binfmt_initialize();
#endif
// 开始初始化硬件功能
// 处理器架构特定的初始化函数,比如中断服务例程和时钟启动
up_initialize();
// 初始化通用驱动
drivers_initialize();
// 可选初始化:板级早期初始化
#ifdef CONFIG_BOARD_EARLY_INITIALIZE
board_early_initialize();
#endif
// 初始化状态:硬件已可用
g_nx_initstate = OSINIT_HARDWARE;
// 开始设置多任务
// 记录 CPU0 IDLE 任务启动事件,用于调度跟踪和调试
sched_note_start(&g_idletcb[0]);
// 为每个 CPU 的 IDLE 任务初始化 stdio
for (i = 0; i < CONFIG_SMP_NCPUS; i++)
{
if (i > 0)
{
// 对于非 CPU0 的 IDLE 任务,从 CPU0 克隆文件描述符
DEBUGVERIFY(group_setuptaskfiles(&g_idletcb[i], NULL, true));
}
else
{
// 对于 CPU0,创建初始 stdio 文件。这些文件将被后续任务继承。
DEBUGVERIFY(group_setupidlefiles());
}
}
// SMP CPU 启动
#ifdef CONFIG_SMP
// 断言当前是 CPU0
DEBUGASSERT(this_cpu() == 0);
// 启动其他 CPU,初始化多核环境
DEBUGVERIFY(nx_smp_start());
#endif
// 启动系统
// 初始化状态:OS 已就绪,开始多任务
g_nx_initstate = OSINIT_OSREADY;
// 启动初始任务
DEBUGVERIFY(nx_bringup());
// 进入 IDLE 任务循环
// 初始化状态:空闲循环
g_nx_initstate = OSINIT_IDLELOOP;
// 结束调度跟踪
sched_trace_end();
// 解锁调度器,允许其他线程访问内存管理器
sched_unlock();
// IDLE 循环
sinfo("CPU0: Beginning Idle Loop\n");
#ifndef CONFIG_DISABLE_IDLE_LOOP
for (; ; )
{
// 调用处理器特定的空闲操作(如低功耗模式),节省能源
up_idle();
}
#endif
}
|
这里进行了一系列的内核子系统初始化,创建了 IDLE 任务,最后调用 nx_bringup() 启动了初始任务。
初始任务
nx_bringup 的主要作用是在操作系统基本初始化完成后,启动系统级的初始任务和服务,包括页面管理、工作队列、文件系统挂载以及应用程序的启动。该文件确保系统从内核模式过渡到用户模式,并为后续的用户应用程序提供运行环境。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
int nx_bringup(void)
{
// 开始调度跟踪
sched_trace_begin();
// 为 IDLE 任务设置初始环境变量,如 PWD、PATH 和 LD_LIBRARY_PATH
#ifndef CONFIG_DISABLE_ENVIRON
#ifdef CONFIG_LIBC_HOMEDIR
setenv("PWD", CONFIG_LIBC_HOMEDIR, 1);
#endif
#ifdef CONFIG_PATH_INITIAL
setenv("PATH", CONFIG_PATH_INITIAL, 1);
#endif
#ifdef CONFIG_LDPATH_INITIAL
setenv("LD_LIBRARY_PATH", CONFIG_LDPATH_INITIAL, 1);
#endif
#endif
// 启动页面填充内核线程(处理缺页异常)
nx_pgworker();
// 启动工作队列线程
nx_workqueues();
// 执行板级初始化(late)并启动应用初始化线程
nx_create_initthread();
// 调度跟踪结束
sched_trace_end();
return OK;
}
|
接下来就是创建名为“AppBringUp”的内核线程,在其中进行后续启动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static inline void nx_create_initthread(void)
{
#ifdef CONFIG_BOARD_LATE_INITIALIZE
int pid;
/* Do the board/application initialization on a separate thread of
* execution.
*/
pid = nxthread_create("AppBringUp", TCB_FLAG_TTYPE_KERNEL,
CONFIG_BOARD_INITTHREAD_PRIORITY,
NULL, CONFIG_BOARD_INITTHREAD_STACKSIZE,
nx_start_task, NULL, environ);
DEBUGASSERT(pid > 0);
UNUSED(pid);
#else
/* Do the board/application initialization on this thread of execution. */
nx_start_application();
#endif
}
|
创建入口点
nx_start_task 继续调用 nx_start_application,这个函数会完成最后的板级设置,然后根据配置(通过入口点或文件路径)启动用户应用程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
#ifdef CONFIG_BOARD_LATE_INITIALIZE
static int nx_start_task(int argc, FAR char **argv)
{
/* Do the board/application initialization and exit */
nx_start_application();
return OK;
}
#endif
static inline void nx_start_application(void)
{
// 可选:准备参数数组
#ifndef CONFIG_INIT_NONE
FAR char * const argv[] =
{
# ifdef CONFIG_INIT_ARGS
CONFIG_INIT_ARGS,
# endif
NULL,
};
posix_spawnattr_t attr;
#endif
int ret;
// 可选:挂载 ROMFS 到 /etc
#ifdef CONFIG_ETC_ROMFS
nx_romfsetc();
#endif
// 可选:进行板级晚期初始化。非常重要,这里我们是启用了的。
#ifdef CONFIG_BOARD_LATE_INITIALIZE
board_late_initialize();
#endif
// 可选:核心转储初始化
#ifdef CONFIG_COREDUMP
coredump_initialize();
#endif
// 如果定义 CONFIG_INIT_NONE,记录日志并跳过(适用于flat build)
#ifdef CONFIG_INIT_NONE
UNUSED(ret);
sinfo("No init thread\n");
#else
// 否则,初始化 posix_spawnattr_t 属性(优先级和栈大小)
posix_spawnattr_init(&attr);
attr.priority = CONFIG_INIT_PRIORITY;
attr.stacksize = CONFIG_INIT_STACKSIZE;
// 启动配置 1:入口点
#if defined(CONFIG_INIT_ENTRY)
sinfo("Starting init thread\n");
# ifdef CONFIG_BUILD_PROTECTED
DEBUGASSERT(USERSPACE->us_entrypoint != NULL);
// - 1.1 Protected 构建从 USERSPACE->us_entrypoint 获取入口点
ret = task_spawn(CONFIG_INIT_ENTRYNAME,
USERSPACE->us_entrypoint,
NULL, &attr, argv, NULL);
# else
// - 1.2 Flat 构建从 CONFIG_INIT_ENTRYPOINT 获取入口点
ret = task_spawn(CONFIG_INIT_ENTRYNAME,
CONFIG_INIT_ENTRYPOINT,
NULL, &attr, argv, NULL);
# endif
#elif defined(CONFIG_INIT_FILE)
// 启动配置 2:文件
# ifdef CONFIG_INIT_MOUNT
// 挂载文件系统
ret = nx_mount(CONFIG_INIT_MOUNT_SOURCE, CONFIG_INIT_MOUNT_TARGET,
CONFIG_INIT_MOUNT_FSTYPE, CONFIG_INIT_MOUNT_FLAGS,
CONFIG_INIT_MOUNT_DATA);
DEBUGASSERT(ret >= 0);
# endif
sinfo("Starting init task: %s\n", CONFIG_INIT_FILEPATH);
posix_spawnattr_init(&attr);
attr.priority = CONFIG_INIT_PRIORITY;
attr.stacksize = CONFIG_INIT_STACKSIZE;
// 从文件执行程序。
ret = exec_spawn(CONFIG_INIT_FILEPATH, argv, NULL,
CONFIG_INIT_SYMTAB, CONFIG_INIT_NEXPORTS, NULL, &attr);
#endif
posix_spawnattr_destroy(&attr);
DEBUGASSERT(ret > 0);
#endif /* CONFIG_INIT_NONE */
}
|
这里会涉及到 Flat 构建和 Protected 构建:
- Flat 构建:内核和用户应用程序共享同一个地址空间,没有内存保护分离
- 所有代码(内核和用户)运行在同一权限级别。
- 内存访问无限制,用户代码可直接访问内核内存。
- 启动简单,性能开销低,适用于资源受限的嵌入式系统。
- 安全性低:用户代码错误可能导致整个系统崩溃。
- Protected 构建:内核和用户空间分离,提供基本的内存保护。
- 内核运行在特权模式,用户应用程序在非特权模式。
- 用户空间无法直接访问内核内存,系统调用通过接口交互。
- 安全性较高:用户代码错误不会直接影响内核。
- 启动更复杂,需要用户空间 blob(二进制块)。
一般嵌入式设备的话选用 Flat 构建就可以了,我们这里看 Flat 构建的分支就是通过 CONFIG_INIT_ENTRYPOINT 创建任务。在 stm32f103-minimum:nsh 的配置下,
CONFIG_INIT_ENTRYPOINT="nsh_main"
板级资源初始化(二)
总结一下板级资源初始化的入口,这些初始化函数都定义在 stm32f103-minimum/src/stm32_boot.c 中(stm32 没有 board_early_initialize):
- BOOT 阶段(
__start):调用 stm32_boardinitialize() 进行最早的初始化
- 内核初始化阶段(
nx_start):调用 board_early_initialize 进行板级早期初始化
- 系统初始任务启动阶段(
nx_start_task):调用 board_late_initialize 进行板级晚期初始化。
目前 stm32f103-minimum:nsh 的配置中,主要的初始化工作位于 board_late_initialize。
1
2
3
4
5
6
7
8
9
10
11
12
|
#ifdef CONFIG_BOARD_LATE_INITIALIZE
void board_late_initialize(void)
{
#ifndef CONFIG_BOARDCTL
/* Perform board initialization here instead of from the
* board_app_initialize().
*/
stm32_bringup();
#endif
}
#endif
|
board_late_initialize 直接跳转到 stm32_bringup(),在这里面我们可以看到具体的板级资源的初始化,代码比较长,因此做了一些省略,主要就是根据配置是否打开来进行对应的初始化,在 nsh 的默认配置下,大部分外设资源都没有启用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
// stm32f103-minimum/src/stm32_bringup.c
int stm32_bringup(void)
{
#ifdef CONFIG_ONESHOT
struct oneshot_lowerhalf_s *os = NULL;
#endif
int ret = OK;
#ifdef CONFIG_DEV_GPIO
ret = stm32_gpio_initialize();
if (ret < 0)
{
syslog(LOG_ERR, "Failed to initialize GPIO Driver: %d\n", ret);
return ret;
}
#endif
#ifdef CONFIG_VIDEO_FB
/* Initialize and register the framebuffer driver */
ret = fb_register(0, 0);
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: fb_register() failed: %d\n", ret);
}
#endif
...
#ifdef CONFIG_USERLED
/* Register the LED driver */
ret = userled_lower_initialize("/dev/userleds");
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: userled_lower_initialize() failed: %d\n", ret);
}
#endif
...
return ret;
}
|
启动 nsh
目前所有的初始化工作都已经完成了,马上要启动 nsh,从上面代码可以看到是通过 CONFIG_INIT_ENTRYPOINT 来控制入口点,当前配置下这个值是 nsh_main,那 nuttx 内核要怎么找到这个 app 函数呢?
这就需要看 cmake 的相关文件,有一个非常重要的宏定义 nuttx_add_application,位于 nuttx/cmake/nuttx_add_application.cmake,有 200+ 行代码,这里挑出最关键的部分,其余的可以自行查看与学习。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// nuttx/cmake/nuttx_add_application.cmake:153
# 将应用程序作为静态库 libapps.a 链接到内核
set(TARGET "apps_${NAME}")
add_library(${TARGET} ${SRCS})
...
if(NOT NO_MAIN_ALIAS)
# 从 SRCS(源文件列表)中获取第一个文件,并赋值给 MAIN_SRC。
list(GET SRCS 0 MAIN_SRC)
# 入口点映射
# 为指定的源文件(${MAIN_SRC})添加编译定义(compile definition):main=${NAME}_main。
# 这会在编译时通过 -Dmain=nsh_main(对于NAME=nsh)传递给编译器。
# 结果:nsh_main.c 中的 int main(...) 被宏替换为 int nsh_main(...),创建别名。
set_property(
SOURCE ${MAIN_SRC}
APPEND
PROPERTY COMPILE_DEFINITIONS main=${NAME}_main)
endif()
|
通过映射之后,内核就开始运行 apps/system/nsh/nsh_main.c 中的 main 函数了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
int main(int argc, FAR char *argv[])
{
struct sched_param param;
int ret = 0;
/* Check the task priority that we were started with */
sched_getparam(0, ¶m);
if (param.sched_priority != CONFIG_SYSTEM_NSH_PRIORITY)
{
/* If not then set the priority to the configured priority */
param.sched_priority = CONFIG_SYSTEM_NSH_PRIORITY;
sched_setparam(0, ¶m);
}
/* Initialize the NSH library */
nsh_initialize();
#ifdef CONFIG_NSH_CONSOLE
/* If the serial console front end is selected, run it on this thread */
ret = nsh_consolemain(argc, argv);
/* nsh_consolemain() should not return. So if we get here, something
* is wrong.
*/
dprintf(STDERR_FILENO, "ERROR: nsh_consolemain() returned: %d\n", ret);
ret = 1;
#endif
return ret;
}
|