RT-Thread快速入门-了解内核启动流程

2023-05-16

首发,公众号【一起学嵌入式】,RTOS、Linux、C

内核是操作系统最基础也是最重要的部分。从本文开始进入 RT-Thread 内核相关知识的学习。

首先了解内核的基础知识,对 RT-Thread 内核的设计有个初步的认识。

然后了解一下 RT-Thread 系统启动流程。

内核介绍

下图为 RT-Thread 的内核架构图:

在这里插入图片描述

内核包括两部分:内核库、实时内核实现。

内核库

为了保证内核能够独立运行,RT-Thread 设计了一套小型的类似 C 库的函数实现子集,根据编译器的不同 C 库的情况会有些不同。

RT-Thread 内核服务库仅提供了内核用到的 C 库函数的实现,为了避免与标准 C 库重名,在这些函数前都会加上 “rt_” 前缀。文件 src/kservice.c 部分函数定义如下:

void *rt_memset(void *s, int c, rt_ubase_t count)

void *rt_memcpy(void *dst, const void *src, rt_ubase_t count)

rt_int32_t rt_memcmp(const void *cs, const void *ct, rt_ubase_t count)

char *rt_strstr(const char *s1, const char *s2)

rt_size_t rt_strlen(const char *s)

rt_int32_t rt_snprintf(char *buf, rt_size_t size, const char *fmt, ...)

rt_int32_t rt_sprintf(char *buf, const char *format, ...)

内核实现

实时内核的实现包括:对象管理、线程管理、调度器、线程间通信、时钟管理、内存管理等等。内核最小的资源占用情况是 3KB ROM,1.2KB RAM。

1. 线程

线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法。

RT-Thread 支持 256 个线程优先级,也可通过配置文件更改为最大支持 32 个或 8 个线程优先级。 0 优先级代表最高优先级,最低优先级留给空闲线程使用。

RT-Thread 支持创建多个具有相同优先级的线程。相同优先级的线程之间的调度,采用时间片轮转算法,使每个线程都运行设定的时间。

调度器在切换到最高就绪线程的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关 。

2.时钟管理

RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。

RT-Thread 提供了两类定时器机制:

  • 单次触发定时器。该定时器启动后只会触发一次定时器事件,然后自动停止。
  • 周期触发定时器。这类定时器会周期性地触发定时器事件,直到用户手动停止定时器。

3.线程间同步

RT-Thread 采用信号量、互斥量与事件集实现线程间同步。

线程通过对信号量、互斥量的获取与释放进行同步。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。

线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和 “与触发”,适合于线程等待多个事件的情况。

4.线程间通信

RT-Thread 支持邮箱和消息队列等通信机制。

邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。

5.内存管理

RT-Thread 支持静态内存池管理及动态内存堆管理。

动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB 内存管理算法。

还有一种动态内存堆管理叫做 memheap,适用于系统含有多个地址且不连续的内存堆。使用 memheap 可以将多个内存堆 “粘贴” 在一起,让用户操作起来像是在操作一个内存堆。

启动过程分析

RT-Thread 的启动流程与其他 RTOS 有所不同。许多 RTOS 启动入口函数为 main,而 RT-Thread 在 main 函数运行之前,系统已经完成了功能初始化。main 函数作为用户程序的入口。

系统先从启动文件开始运行,然后进入 RT-Thread 的启动入口 rtthread_startup() ,最后进入用户入口 main()

以 MDK-ARM 为例,RT-Thread 启动流程,如下图所示:

在这里插入图片描述

系统启动后,先从汇编代码 startup_xx.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()

1. 扩展 main()

RT-Thread 使用了 MDK 的扩展功能 $Sub$$$Super$$,使得 RT-Thread 可以在进入 main() 之前完成系统功能初始化。关于 $Sub$$$Super$$ 的扩展功能,可以查看 《Arm Compiler for Embedded Reference Guide Version 6.17》

https://developer.arm.com/documentation/101754/0617/armlink-Reference/Accessing-and-Managing-Symbols-with-armlink/Use-of--Super---and--Sub---to-patch-symbol-definitions?lang=en

指导文档内容原文如下:

在这里插入图片描述

对于 RT-Thread 中的 main() 函数,给其添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main ,在这个函数中添加 RT-Thread 的系统启动,进行一系列的初始化操作。

接着在调用 $Super$$main 转到 main()函数执行 。这样可以减少用户的工作量,不用去管 main() 之前的系统初始化操作。将主要精力用于完成用户需求的功能模块。

src/components.c 文件中可以看到相关的源码:

extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
  rtthread_startup();
  return 0;
}

$Sub$$main 函数调用了 rtthread_startup() 函数,rtthread_startup() 完成 RT-Thread 的启动。

int rtthread_startup(void)
{
		/* 关闭全局中断 */
    rt_hw_interrupt_disable();

    /* 硬件配置初始化 */
    rt_hw_board_init();

    /* 打印 RT-Thread 版本信息 */
    rt_show_version();

    /* 定时器系统初始化 */
    rt_system_timer_init();

    /* 线程调度器初始化 */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* 信号初始化 */
    rt_system_signal_init();
#endif

    /* 创建用户主线程 main */
    rt_application_init();

    /* 定时器线程初始化 */
    rt_system_timer_thread_init();

    /* 空闲线程初始化 */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

    /* 启动调度器 */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

这部分启动代码,大致可以分为四个部分:

  • 初始化与系统相关的硬件;
  • 初始化系统内核对象,例如定时器、调度器、信号;
  • 创建 main 线程,在 main 线程中对各类模块依次进行初始化;
  • 初始化定时器线程、空闲线程,并启动调度器。

2. 进入 main()

rtthread_startup() 函数中调用 rt_application_init() 函数,该函数会创建一个初始化线程,也就是用户线程。

该线程的入口函数为 main_thread_entry(),在这个函数中会调用 $Super$$main(), 进入 main()

/* 系统 main 线程入口函数 */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);
    
#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread 组件初始化 */
    rt_components_init();
#endif    
#ifdef RT_USING_SMP
    rt_hw_secondary_cpu_up();
#endif
    /* 调用系统主函数 main() */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif
}

自动初始化

RT-Thread 具备自动初始化机制:初始化函数不需要被显示调用,只需要在函数定义处,通过宏定义的方式进行声明,即可在系统启动过程中执行。

RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,宏定义将需要在启动时进行初始化的函数指针放到该段中,形成一张初始化函数表。在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。

示例代码:

int rt_hw_usart_init(void) /* 串 口 初 始 化 函 数 */
{
  ... ...
  /* 注 册 串 口 1 设 备 */
  rt_hw_serial_register(&serial1, "uart1",
  RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
  uart);
  return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使 用 组 件 自 动 初 始 化 机 制 */

上述代码中 rt_hw_usart_init() 会被系统自动调用。

在系统启动框图中,有两个函数 rt_components_board_init()rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数:

初始化序号函数函数声明的宏描述
1board init functionsINIT_BOARD_EXPORT(fn)非常早期的初始化,此时调度器还未启动
2pre-initialization functionsINIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多依赖的函数
3device init functionsINIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4components init functionsINIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统或者 LWIP
5enviroment init functionsINIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6application init functionsINIT_APP_EXPORT(fn)应用初始化,比如 GUI 应用

rt_components_board_init() 函数会遍历通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数,主要初始化硬件环境,其函数代码如下:

void rt_components_board_init(void)
{
  const init_fn_t *fn_ptr;
  for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
  {
  	(*fn_ptr)();
  }
}

rt_components_init() 函数会系统运行起来之后,在 main 线程中被调用。此时硬件环境和操作系统都已经初始化完成,可以执行应用代码。rt_components_init() 会遍历剩下的几个宏声明的初始化函数表。

void rt_components_init(void)
{
  const init_fn_t *fn_ptr;

  for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
  {
    (*fn_ptr)();
  }
}

上述两端代码中 __rt_init_rti_board_start__rt_init_rti_board_end、和 __rt_init_rti_end 是通过RT-Thread 内部宏定义实现的,各个宏定义汇总如下:

#define INIT_EXPORT(fn, level) \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

static int rti_start(void)
{
    return 0;
}
INIT_EXPORT(rti_start, "0");

/* 实现 __rt_init_rti_board_start */
static int rti_board_start(void)
{
    return 0;
}
INIT_EXPORT(rti_board_start, "0.end");

/* 实现 __rt_init_rti_board_end */
static int rti_board_end(void)
{
    return 0;
}
INIT_EXPORT(rti_board_end, "1.end");

/* 实现 __rt_init_rti_end */
static int rti_end(void)
{
    return 0;
}
INIT_EXPORT(rti_end, "6.end");

自动初始化机制中宏定义的核心部分为 INIT_EXPORT,这个宏定义的涉及到的一些宏定义如下:

#define SECTION(x)                  __attribute__((section(x)))
/* 该宏的作用是向编译器说明这段代码有用 */
#define RT_USED                     __attribute__((used))

宏定义中,两个符号 ## 用于将两个字符串进行拼接。宏定义 INIT_EXPORT将 字符串__rt_init_ 和 函数名字符串 fn 进行拼接。

section 关键字可以将变量定义到指定的输入段中。宏定义会将__rt_init_fn 放到指定的段中。

OK,今天先到这,下次继续。加油~### RT-Thread快速入门-了解内核启动流程

内核是操作系统最基础也是最重要的部分。从本文开始进入 RT-Thread 内核相关知识的学习。

首先了解内核的基础知识,对 RT-Thread 内核的设计有个初步的认识。

然后了解一下 RT-Thread 系统启动流程。

内核介绍

下图为 RT-Thread 的内核架构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5J9Ni6bv-1642862822807)(M:\document\01.RTOS\02.RT-Thread\01.快速入门\04.RT-Thread快速入门-了解内核启动流程\image-20220110201443245.png)]

内核包括两部分:内核库、实时内核实现。

内核库

为了保证内核能够独立运行,RT-Thread 设计了一套小型的类似 C 库的函数实现子集,根据编译器的不同 C 库的情况会有些不同。

RT-Thread 内核服务库仅提供了内核用到的 C 库函数的实现,为了避免与标准 C 库重名,在这些函数前都会加上 “rt_” 前缀。文件 src/kservice.c 部分函数定义如下:

void *rt_memset(void *s, int c, rt_ubase_t count)

void *rt_memcpy(void *dst, const void *src, rt_ubase_t count)

rt_int32_t rt_memcmp(const void *cs, const void *ct, rt_ubase_t count)

char *rt_strstr(const char *s1, const char *s2)

rt_size_t rt_strlen(const char *s)

rt_int32_t rt_snprintf(char *buf, rt_size_t size, const char *fmt, ...)

rt_int32_t rt_sprintf(char *buf, const char *format, ...)

内核实现

实时内核的实现包括:对象管理、线程管理、调度器、线程间通信、时钟管理、内存管理等等。内核最小的资源占用情况是 3KB ROM,1.2KB RAM。

1. 线程

线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法。

RT-Thread 支持 256 个线程优先级,也可通过配置文件更改为最大支持 32 个或 8 个线程优先级。 0 优先级代表最高优先级,最低优先级留给空闲线程使用。

RT-Thread 支持创建多个具有相同优先级的线程。相同优先级的线程之间的调度,采用时间片轮转算法,使每个线程都运行设定的时间。

调度器在切换到最高就绪线程的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关 。

2.时钟管理

RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。

RT-Thread 提供了两类定时器机制:

  • 单次触发定时器。该定时器启动后只会触发一次定时器事件,然后自动停止。
  • 周期触发定时器。这类定时器会周期性地触发定时器事件,直到用户手动停止定时器。

3.线程间同步

RT-Thread 采用信号量、互斥量与事件集实现线程间同步。

线程通过对信号量、互斥量的获取与释放进行同步。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。

线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和 “与触发”,适合于线程等待多个事件的情况。

4.线程间通信

RT-Thread 支持邮箱和消息队列等通信机制。

邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。

5.内存管理

RT-Thread 支持静态内存池管理及动态内存堆管理。

动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB 内存管理算法。

还有一种动态内存堆管理叫做 memheap,适用于系统含有多个地址且不连续的内存堆。使用 memheap 可以将多个内存堆 “粘贴” 在一起,让用户操作起来像是在操作一个内存堆。

启动过程分析

RT-Thread 的启动流程与其他 RTOS 有所不同。许多 RTOS 启动入口函数为 main,而 RT-Thread 在 main 函数运行之前,系统已经完成了功能初始化。main 函数作为用户程序的入口。

系统先从启动文件开始运行,然后进入 RT-Thread 的启动入口 rtthread_startup() ,最后进入用户入口 main()

以 MDK-ARM 为例,RT-Thread 启动流程,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0A7P6j3F-1642862822807)(M:\document\01.RTOS\02.RT-Thread\01.快速入门\04.RT-Thread快速入门-了解内核启动流程\image-20220110220424242.png)]

系统启动后,先从汇编代码 startup_xx.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()

1. 扩展 main()

RT-Thread 使用了 MDK 的扩展功能 $Sub$$$Super$$,使得 RT-Thread 可以在进入 main() 之前完成系统功能初始化。关于 $Sub$$$Super$$ 的扩展功能,可以查看 《Arm Compiler for Embedded Reference Guide Version 6.17》

https://developer.arm.com/documentation/101754/0617/armlink-Reference/Accessing-and-Managing-Symbols-with-armlink/Use-of--Super---and--Sub---to-patch-symbol-definitions?lang=en

指导文档内容原文如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qQjOzWd-1642862822808)(M:\document\01.RTOS\02.RT-Thread\01.快速入门\04.RT-Thread快速入门-了解内核启动流程\image-20220110231409139.png)]

对于 RT-Thread 中的 main() 函数,给其添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main ,在这个函数中添加 RT-Thread 的系统启动,进行一系列的初始化操作。

接着在调用 $Super$$main 转到 main()函数执行 。这样可以减少用户的工作量,不用去管 main() 之前的系统初始化操作。将主要精力用于完成用户需求的功能模块。

src/components.c 文件中可以看到相关的源码:

extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
  rtthread_startup();
  return 0;
}

$Sub$$main 函数调用了 rtthread_startup() 函数,rtthread_startup() 完成 RT-Thread 的启动。

int rtthread_startup(void)
{
		/* 关闭全局中断 */
    rt_hw_interrupt_disable();

    /* 硬件配置初始化 */
    rt_hw_board_init();

    /* 打印 RT-Thread 版本信息 */
    rt_show_version();

    /* 定时器系统初始化 */
    rt_system_timer_init();

    /* 线程调度器初始化 */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* 信号初始化 */
    rt_system_signal_init();
#endif

    /* 创建用户主线程 main */
    rt_application_init();

    /* 定时器线程初始化 */
    rt_system_timer_thread_init();

    /* 空闲线程初始化 */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

    /* 启动调度器 */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

这部分启动代码,大致可以分为四个部分:

  • 初始化与系统相关的硬件;
  • 初始化系统内核对象,例如定时器、调度器、信号;
  • 创建 main 线程,在 main 线程中对各类模块依次进行初始化;
  • 初始化定时器线程、空闲线程,并启动调度器。

2. 进入 main()

rtthread_startup() 函数中调用 rt_application_init() 函数,该函数会创建一个初始化线程,也就是用户线程。

该线程的入口函数为 main_thread_entry(),在这个函数中会调用 $Super$$main(), 进入 main()

/* 系统 main 线程入口函数 */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);
    
#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread 组件初始化 */
    rt_components_init();
#endif    
#ifdef RT_USING_SMP
    rt_hw_secondary_cpu_up();
#endif
    /* 调用系统主函数 main() */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif
}

自动初始化

RT-Thread 具备自动初始化机制:初始化函数不需要被显示调用,只需要在函数定义处,通过宏定义的方式进行声明,即可在系统启动过程中执行。

RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,宏定义将需要在启动时进行初始化的函数指针放到该段中,形成一张初始化函数表。在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。

示例代码:

int rt_hw_usart_init(void) /* 串 口 初 始 化 函 数 */
{
  ... ...
  /* 注 册 串 口 1 设 备 */
  rt_hw_serial_register(&serial1, "uart1",
  RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
  uart);
  return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使 用 组 件 自 动 初 始 化 机 制 */

上述代码中 rt_hw_usart_init() 会被系统自动调用。

在系统启动框图中,有两个函数 rt_components_board_init()rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数:

初始化序号函数函数声明的宏描述
1board init functionsINIT_BOARD_EXPORT(fn)非常早期的初始化,此时调度器还未启动
2pre-initialization functionsINIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多依赖的函数
3device init functionsINIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4components init functionsINIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统或者 LWIP
5enviroment init functionsINIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6application init functionsINIT_APP_EXPORT(fn)应用初始化,比如 GUI 应用

rt_components_board_init() 函数会遍历通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数,主要初始化硬件环境,其函数代码如下:

void rt_components_board_init(void)
{
  const init_fn_t *fn_ptr;
  for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
  {
  	(*fn_ptr)();
  }
}

rt_components_init() 函数会系统运行起来之后,在 main 线程中被调用。此时硬件环境和操作系统都已经初始化完成,可以执行应用代码。rt_components_init() 会遍历剩下的几个宏声明的初始化函数表。

void rt_components_init(void)
{
  const init_fn_t *fn_ptr;

  for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
  {
    (*fn_ptr)();
  }
}

上述两端代码中 __rt_init_rti_board_start__rt_init_rti_board_end、和 __rt_init_rti_end 是通过RT-Thread 内部宏定义实现的,各个宏定义汇总如下:

#define INIT_EXPORT(fn, level) \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

static int rti_start(void)
{
    return 0;
}
INIT_EXPORT(rti_start, "0");

/* 实现 __rt_init_rti_board_start */
static int rti_board_start(void)
{
    return 0;
}
INIT_EXPORT(rti_board_start, "0.end");

/* 实现 __rt_init_rti_board_end */
static int rti_board_end(void)
{
    return 0;
}
INIT_EXPORT(rti_board_end, "1.end");

/* 实现 __rt_init_rti_end */
static int rti_end(void)
{
    return 0;
}
INIT_EXPORT(rti_end, "6.end");

自动初始化机制中宏定义的核心部分为 INIT_EXPORT,这个宏定义的涉及到的一些宏定义如下:

#define SECTION(x)                  __attribute__((section(x)))
/* 该宏的作用是向编译器说明这段代码有用 */
#define RT_USED                     __attribute__((used))

宏定义中,两个符号 ## 用于将两个字符串进行拼接。宏定义 INIT_EXPORT将 字符串__rt_init_ 和 函数名字符串 fn 进行拼接。

section 关键字可以将变量定义到指定的输入段中。宏定义会将__rt_init_fn 放到指定的段中。


公众号【一起学嵌入式】,精彩第一时间送达

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RT-Thread快速入门-了解内核启动流程 的相关文章

  • shell脚本自学笔记

    一 什么是Shell脚本 shell脚本并不能作为正式的编程语言 xff0c 因为它是在linux的shell中运行的 xff0c 所以称为shell脚本 事实上 xff0c shell脚本就是一些命令的集合 假如完成某个需求需要一口气输入
  • matlab2020安装

    前言 xff1a 这里之所以要安装最新的2020版本 xff0c 是因为matlab中的硬件支持工具是随着版本变化而变化的 xff0c 所以要升级matlab版本 MATLAB R2020a v9 8 0 最新中文版 64位 百度网盘链接后
  • px4的电调校准

    我们之前校准电调的步骤为 xff1a 第一 xff0c 先打开遥控器 xff0c 油门推到最大 第二 xff0c 给飞控供电 xff0c 此时电调会捕捉到油门最大量程 第三 xff0c 保持遥控不变 xff0c 飞控断电 xff0c 然后再
  • px4源码备份

    这几天在用1 8 0版本的源码时发现了玄学的事情 xff0c 在终端下完美运行和仿真 xff0c 但是拿到simulink下运行仿真就会出现飞机一进去就乱飞 xff0c 并且你关了simulink之后再回终端 xff0c 发现原本在终端下可
  • 数据结构有关树的知识总结(一)

    前段时间准备考研 xff0c 对数据结构做了一个简略的知识点总结 xff0c 知识针对考研所用到的数据结构 xff0c 下面是树的章节由于篇幅太多 xff0c 将有关树的知识点分开发表 xff0c 对B树和B 43 树没有深入了解 xff0
  • simulink中的state place模块的使用

    我们知道 xff0c state place模块输入的为u xff0c 输出的y xff0c 并且我们需要在模块参数中设置ABCD以及初始状态x的值 xff08 初始状态x的值一般为0 xff09 xff1a 但我们经常使用状态空间不太在意
  • 反馈线性化

    这里转载一篇介绍反馈线性化很好的文章 xff1a https max book118 com html 2018 1014 8135037141001126 shtm
  • simulink中使用memory模块实现变量的累加和

    在离散系统中 xff0c 我们经常会遇到需要存储上一时刻的变量 比如y xff08 k 1 xff09 xff0c 然后用在当前时刻的运算里 xff0c 这个时候我们就需要一个模块能够存储上一时刻的y xff08 k 1 xff09 xff
  • 关于使用gazebo启动很慢、很卡、很热的解决办法

    1 参照之前的博客进行离线下载模型 2 不要将系统装在移动硬盘上 3 断网 xff0c 因为联网的话他会一直搜索网上资源 xff0c 虽然此是可能会提示 xff1a Couldn span class token string 39 t f
  • sci中的插图操作和说明(超详细)

    http www 360doc com content 17 1219 23 50538487 714649321 shtml
  • latex中某一页出现图片与上下文间距过大,利用\vspace等强制修改间距的命令依然无法解决的问题

    问题描述 xff1a 当我利用下面的命令进行插入图片时 xff1a begin span class token punctuation span figure span class token punctuation span span
  • 魅族18解bl锁+刷boot+刷面具+刷lsp+刷hmspush教程

    一 解锁教程 这里就不多说了 xff0c 直接看教程 xff1a https www coolapk com feed 38216557 shareKey 61 MjUxODI2OGMzNGRlNjM1ZTQxYWY amp shareUi
  • 关于px4中uorb以及px4_simple_app的终极理解

    先允许我卖个萌 xff1a 看了一下午关于px4中的uorb的分析 xff0c 终于有所感悟了 信息量有点大 xff0c 先让我缓缓 xff0c 理理思绪 先说说我之前的疑惑吧 xff1a 疑惑1 在一开始学习px4的时候 xff0c 就在
  • 关于无人机升力的产生

    前言 xff1a 之前学过飞控 xff0c 但是现在回顾了一下发现最基本分析升力的产生的原因都忘了 xff0c 于是记录下来 先问一个问题 xff1a 到底是上表面的路程长还是下表面的路程长 xff1f 之前一直记错了以为是上表面的路径长
  • stm32之定时器运用———呼吸灯

    呼吸灯原理 1 在模拟电路中 xff0c 呼吸灯的实现可以通过一个呈现正弦的电压控制 xff0c 这个电压是连续变化的 xff0c 所以肉眼看上去就是逐渐变暗 xff0c 逐渐变亮 2 而在数字电路中如何实现这种效果呢 xff1f 就需要通
  • 数据结构有关树的知识总结(二)

    这一篇文章主要介绍三个知识点 xff1a 哈夫曼树 堆排序以及最佳归并树和败者树 xff08 四 xff09 哈夫曼树 1 构造哈夫曼树 xff1a 哈夫曼树的特点 xff1a 权值越大 xff0c 离根节点越近 xff1b 树中没有度为1
  • 关于磁力计的新的理解

    前言 xff1a 我发现真的是验证了一句话 xff0c 每次重看一遍书 xff0c 都能发现新的东西 xff0c 我发现看程序也是这样 xff0c 没重看一边都会发现新的东西 不想打字了 xff0c 就直接贴我的ppt
  • px4源码学习(local position estimator)

    前言 xff1a 之前学习的是px4源码中的attitude estimator q xff0c 可以说是学习的相当仔细和深入 于是借着这股劲继续学习位置估计中的local position estimator 另外需要说明的是 xff0c
  • px4在环仿真实践操作

    前言 xff1a 在这之前先确保你已经配置好了jMAVSim 一 在环仿真 有两种 xff0c 一种是软件在环仿真SITL xff0c 还有一种是硬件在环仿真HITL 先介绍HITL 二 软件在环仿真 步骤 xff1a 1 打开终端 xff
  • 关于px4源码中固定翼姿态控制

    在看完px4固定翼的姿态控制代码后 xff0c 我有几点思考 1 我们知道程序中姿态控制的方法是将控制角度转化为控制角速度 那pitch来说 xff0c 先求出设定的pitch和当前的pitch的差值 xff08 delta pitch x

随机推荐

  • 关于垂直起降固定翼VTOL

    参考网址 xff1a https www ncnynl com archives 201709 2068 html 截图 xff1a 尾座式演示视频 xff1a http 7xw24i com1 z0 glb clouddn com PX4
  • 相关的飞控官网网址

    3DR官网 xff1a http 3drobotics com 这个网站简单来说就是3dr卖飞控的官方旗舰店 xff0c 我还以为开发者的一些东西也在上面 xff0c xff0c 好浪费感情 apm飞控的官网 xff1a http copt
  • c++中数组作为参数传入函数

    前言 xff1a 最近再看一个用c 43 43 写数组求逆矩阵的程序 xff0c 那是相当的繁琐 xff0c 但是很有趣 其中涉及了大量的数组运算 xff0c 所以数组作为参数传入函数很重要 xff0c 这里记录一下 一维数组作为参数 1
  • px4如何读取串口信息

    前言 xff1a 网上有一大堆 xff0c 比如minicom xff0c mavproxy等等 xff0c 其实qgc就自带nutshell工具 在下面输入help就可以看到所有命令 xff01 xff01 xff01
  • 浅析人脸检测之Haar分类器方法:Haar特征、积分图、 AdaBoost 、级联

    浅析人脸检测之Haar 分类器方法 一 Haar 分类器的前世今生 人脸检测属于计算机视觉的范畴 xff0c 早期人们的主要研究方向是人脸识别 xff0c 即根据人脸来识别人物的身份 xff0c 后来在复杂背景下的人脸检测需求越来越大 xf
  • Windows 10 64bit系统下,使用pip安装Pygame、Matplotlib的过程和一些问题的解决

    最近在学习python xff0c 开始做第一个项目 xff1a 数据可视化 xff0c 在做项目之前 xff0c 需要安装一些软件 xff0c 本文记录安装项目所需的软件过程以及遇到的一些问题 一 安装Pygame 使用pip安装pyth
  • 目标检测的图像特征提取之(三)Haar特征

    目标检测的图像特征提取之 xff08 三 xff09 Haar特征 zouxy09 64 qq com http blog csdn net zouxy09 1 Haar like 特征 Haar like 特征最早是由Papageorgi
  • Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现

    Deep Learning论文笔记之 xff08 四 xff09 CNN卷积神经网络推导和实现 zouxy09 64 qq com http blog csdn net zouxy09 自己平时看了一些论文 xff0c 但老感觉看完过后就会
  • 曲线拟合 公式已与excel2007直线趋势图对比过。

    来自 xff1a http www fjptsz com xxjs xjw rj 117 04 htm 第4章 曲线拟合 许剑伟 于莆田十中 2008年4月23日 在很多情况下 xff0c 天文观测得到的数据是一组包含很大数量的序列点图象
  • MAVLink—最强大的微型飞行器通信协议

    c 光明工作室 2017 2037 COPYRIGHT 光明工作室团队成员大部分来自全国著名985 211工程院校 具有丰富的工程实践经验 xff0c 本工作室热忱欢迎大家的光临 工作室长期承接嵌入式开发 PCB设计 算法仿真等软硬件设计
  • 摄像机高精度标定的一些方法

    摄像机标定从标定板类型可以分为 xff1a 一维标定物 xff0c 二维标定物和三维标定物 xff08 哈哈哈 xff09 1 一维标定物 一维标定物标定算法是利用摄像机投影过程中的交比不变性的原理 xff0c 常用来标定摄像机阵列 xff
  • C#连接到SQL Server数据库

    理论知识 使用ADO NET ActiveX Data Objects 可以减少不同数据库系统带来的琐碎事情 ADO NET是一组公开数据访问服务的类 主要组成 xff1a NET 数据提供程序 xff1a 可以实现数据操作和对数据的快速
  • c++ 后台服务器开发面试题目总结

    文章目录 1 C 43 43 中include头文件时尖括号与双引号的区别1 1 区别1 2 总结 2 c 43 43 的封装 继承 多态3 计算机网络的OSI七层模型 xff0c 每一层的作用是啥4 红黑树的基本问题5 set怎么保证插入
  • Ubuntu18.04 PX4(XTdrone) gazebo联合仿真报错

    ubuntu18 04 默认安装gazebo9 0 启动PX4的indoor1 launch会报错 gzserver span class token operator span symbol lookup error span class
  • 云原生周刊:一文读懂 Pod 网络 | 2023.4.10

    文章推荐 一文读懂 Pod 网络 这篇文章旨在帮助读者理解 Pod 网络的概念和原理 Pod 网络是 Kubernetes 中的一个重要概念 xff0c 它描述了如何在一个集群中部署和运行应用程序 Pod 网络是指使用容器网络插件 如 Ca
  • 前辈大公司的面试,重点是他推荐我们应该看得那些书

    应届生上泡了两年 xff0c 一直都是下资料 xff0c 下笔试题 xff0c 面试题 一直都在感谢那些默默付出的人 写这个帖子花了我两个夜晚的时间 xff0c 不是为了炫耀 xff0c 只是为了能给那些 迷惘 的学弟学妹 xff0c 一点
  • 数据库for update 之后未提交事务导致锁表

    在工作的时候 xff0c 操作数据库 xff0c select for update xff0c 忘记提交事务 xff0c 数据库为了防止其他人对该表进行操作 xff0c 对该表进行锁表 xff0c 导致我再次for update 的时候一
  • 芯片驱动程序编写

    实质 利用程序控制单片机与芯片通信 xff0c 目的是读写芯片 xff0c 一般来说 xff0c 驱动程序就是对芯片的读写操作 看数据手册 寄存器表 芯片的所有功能都 映射 在寄存器表上 xff0c 阅读寄存器表就可以了解芯片的功能 这部分
  • 如何快速入门RTOS

    摘要 本文结合自己学习RTOS的经历 xff0c 来谈谈如何快速入门一款RTOS xff0c 希望能够给初学者以启发 xff0c 找到适合自己的学习思路和方法 我的学习经历 ucos学习 我是在上学期间接触到了RTOS xff0c 当时学习
  • RT-Thread快速入门-了解内核启动流程

    首发 xff0c 公众号 一起学嵌入式 xff0c RTOS Linux C 内核是操作系统最基础也是最重要的部分 从本文开始进入 RT Thread 内核相关知识的学习 首先了解内核的基础知识 xff0c 对 RT Thread 内核的设