当前位置: 首页 > news >正文

RT_Thread 内核启动分析

无论是硬件系统还是软件系统,了解它的第一步需要知道它和他的low level如何接轨,并如何实现在其上面启动跑起来。

RT_thread 一样  RT_Thread 支持多种平台和多种编译器,rtthread_startup()函数是RT_Thread规定的统一入口。

执行顺序是:系统先从启动文件开始运行,然后进入RT_Thread的启动函数,最后进入用户入口函数。 基本的启动流程可以参考

RT-Thread 文档中心https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-%e7%a8%8b%e5%ba%8f%e5%86%85%e5%ad%98%e5%88%86%e5%b8%83这里主要列些一些关键逻辑

对于不同平台来讲 可以通过不同的方式将rtthread_startup()加载在main函数前面 文档中心以MDK_ARM为例写了如何加载在main函数前面

我现在以RTThread Studio GCC编译器为例

#elif defined(__GNUC__)
/* Add -eentry to arm-none-eabi-gcc argument */
int entry(void)
{
    rtthread_startup();
    return 0;
}

程序内部 告诉我们在使用gcc编译时加入参数 --eentry 即可

工程 右键 属性 即可进入下图所示选项 可以在里面配置编译选项,目前我还没找到 --entry入口在哪里加进去了,留坑。

按照文档中心说的 startup函数内

这里主要说两个初始化 rt_components_board_init() 和 rt_components_init() 函数名字很相似,但是调用顺序和功能大不相同

rt_components_board_init():

通过调用drv_common 内的函数 完成板子驱动层对接HAL层的硬件初始化

主要堆初始化 时钟初始化 控制台初始化  组件板级初始化 这是板子上其他功能正常运行的前提。所以系统启动的第一步便是执行这个。

我们经常就可以看到 RTM_EXPORT()

如果在RT操作系统下 使用 MODULE  会通过下述代码 根据不同的编译器,采用不同的方式到处模块符号,这些符号信息会被记录在特定的数据结构和段中,方便模块加载和管理时使用,目前我没用到  宏定义为空, 就是不需要单独导出模块。

#ifdef RT_USING_MODULE
struct rt_module_symtab
{
    void       *addr;
    const char *name;
};

#if defined(_MSC_VER)
#pragma section("RTMSymTab$f",read)
#define RTM_EXPORT(symbol)                                            \
__declspec(allocate("RTMSymTab$f"))const char __rtmsym_##symbol##_name[] = "__vs_rtm_"#symbol;
#pragma comment(linker, "/merge:RTMSymTab=mytext")

#elif defined(__MINGW32__)
#define RTM_EXPORT(symbol)

#else
#define RTM_EXPORT(symbol)                                            \
const char __rtmsym_##symbol##_name[] SECTION(".rodata.name") = #symbol;     \
const struct rt_module_symtab __rtmsym_##symbol SECTION("RTMSymTab")= \
{                                                                     \
    (void *)&symbol,                                                  \
    __rtmsym_##symbol##_name                                          \
};
#endif

rt_components_init():

首先rt_components_init()是main_thread_entry()内实现调用的。

这里值得一提的是:

/*
 * Components Initialization will initialize some driver and components as following
 * order:
 * rti_start         --> 0
 * BOARD_EXPORT      --> 1
 * rti_board_end     --> 1.end
 *
 * DEVICE_EXPORT     --> 2
 * COMPONENT_EXPORT  --> 3
 * FS_EXPORT         --> 4
 * ENV_EXPORT        --> 5
 * APP_EXPORT        --> 6
 *
 * rti_end           --> 6.end
 *
 * These automatically initialization, the driver or component initial function must
 * be defined with:
 * INIT_BOARD_EXPORT(fn);
 * INIT_DEVICE_EXPORT(fn);
 * ...
 * INIT_APP_EXPORT(fn);
 * etc.
 */

这是rt_thread比较有特点的初始化,隐藏显示调用,“自动”完成初始化的核心 ,下面分析一下怎么完成这个功能的。

1.如果使用了组件初始化的功能

就会定义一个宏

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

这个宏分开解释意思为:

RT_USED     根据编译器类型不同 告诉编译器此段代码不管有没有显示调用 都不能被优化

然后定义一个函数指针  将指针赋值为函数名,函数名即为函数地址 ,通过这种方式统一了代码架构 格式  

section属性是__attribute__支持的一个属性,它的作用是指定变量或函数应该被放置在哪个段中。

常见的段有.text(存放代码) .data(存放已初始化的全局变量和静态变量) .bss(存放未初始化的全局变量和静态变量)等

通过section属性,可以创建自定义的段,或者将变量、函数放置到特定的已有段中。

上述即为把对应的函数和变量放到名为.rti_fn_level 段中 level为字符串

将上述两者结合起来,可以知道rt_thread 将段分为

.rti_fn.0 

.rti_fn.0.end

.rti_fn.1

.rti_fn.1.end

.rti.2

.rti.3

.rti.4

.rti.5

.rti.6

按照既定规则,将函数保存到这些段地址。至于如何自动到这些段初始化呢? 以rt_components_init为例

 volatile 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前缀的变量,并按照初始化级别区分了

通过取内存里上述定义的函数指针的地址,遍历完成初始化。这里完成的是 2 3 4 5 6 的初始化也是按照先后初始化 但是统一在主线程完成初始化。

对于链接编译完成后 可以验证性的结果可以在生成的map文件内查看,可以看到

 *(SORT(.rti_fn*))
 .rti_fn.0      0x0800e0d0        0x4 ./rt-thread/src/components.o
                0x0800e0d0                __rt_init_rti_start
 .rti_fn.0.end  0x0800e0d4        0x4 ./rt-thread/src/components.o
                0x0800e0d4                __rt_init_rti_board_start
 .rti_fn.1      0x0800e0d8        0x4 ./drivers/drv_clk.o
                0x0800e0d8                __rt_init_clock_information
 .rti_fn.1.end  0x0800e0dc        0x4 ./rt-thread/src/components.o
                0x0800e0dc                __rt_init_rti_board_end
 .rti_fn.6      0x0800e0e0        0x4 ./rt-thread/components/finsh/shell.o
                0x0800e0e0                __rt_init_finsh_system_init
 .rti_fn.6      0x0800e0e4        0x4 ./applications/HC_SRO4.o
                0x0800e0e4                __rt_init_hcsr04_init
 .rti_fn.6.end  0x0800e0e8        0x4 ./rt-thread/src/components.o
                0x0800e0e8                __rt_init_rti_end
                0x0800e0ec                __rt_init_end = .
                0x0800e0ec                . = ALIGN (0x4)
                [!provide]                PROVIDE (__ctors_start__, .)

总结完上述结果后,我们就可以通过自动初始化机制,隐式完成对应的初始化设备,什么函数应该在什么顺序调用,描述里也有例子,多用就会越来越熟悉。

相关文章:

  • 家用路由器的WAN口和LAN口有什么区别
  • 【WebGL】attribute方式实例化绘制
  • 线代[8]|北大丘维声教授《怎样学习线性代数?》(红色字体为博主注释)
  • 计算机考研复试上机07
  • ES6箭头函数:基础与进阶指南
  • 红队内网攻防渗透:内网渗透之内网对抗:实战项目VPC1打靶PHP-RCE三层代理路由防火墙上线密码喷射域控提权
  • 请求go构建缓存,go clean -cache
  • 使用西门子 PLC(以 S7 - 1200 为例)编写梯形图程序来根据转速计算瞬时流量和累计流量的详细步骤
  • 0基础学Linux系统(准备1)
  • 沉浸式翻译插件深度评测:打破语言壁垒的黑科技利器
  • w223信息技术知识赛系统设计与实现
  • aws(学习笔记第二十九课) aws cloudfront hands on
  • Qwen-VL传统方法微调
  • 萝卜头笔作文赏析
  • CMMI评估的五个等级
  • ROS 2机器人开发--话题通信:订阅与发布
  • 52类110个主流Java组件和框架
  • SpringBoot实现异步调用的方法
  • 互联网医院系统源码解析:如何开发智能化的电子处方小程序?
  • 什么是方法
  • 巴基斯坦外长:印巴停火
  • 祝贺!苏翊鸣成功解锁“2160”
  • 印称一名高级官员在巴基斯坦发动的袭击中死亡
  • 巴西总统卢拉将访华
  • 屈晓华履新四川省社科联党组书记,此前担任省国动办主任
  • 全国人大常委会启动食品安全法执法检查