热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RT-thread组件初始化代码分析

RT-thread提供了组件化功能,具体实现是在componentsinit文件夹下components.c文件中实现的。应用组件化功能首先在rtconfig.h中添加宏定义#def

        RT-thread提供了组件化功能,具体实现是在components/init文件夹下components.c文件中实现的。应用组件化功能首先在rtconfig.h中添加宏定义#define RT_USING_COMPONENTS_INIT;若需要启用调试模式,则还要添加#define RT_DEBUG_INIT 1

void rt_components_board_init(void)
{
#ifndef _MSC_VER
#if RT_DEBUG_INIT //启用初始化调试模式,主要目的为将各个组件初始化的状态通过串口打印到PC端(在rtconfig.h中宏定义为1)
int result;
const struct rt_init_desc *desc; //rt_init_desc是在rtdef.h中定义的结构体类型
for (desc = &__rt_init_desc_rti_start; desc <&__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_start; fn_ptr <&__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}

#endif

#endif
}
void rt_components_init(void)
{
#ifndef _MSC_VER
#if RT_DEBUG_INIT //启用初始化调试模式
int result;
const struct rt_init_desc *desc; //rt_init_desc为在rtdef.h中定义的结构体类型,这里定义了指向该结构体类型的指针变量

rt_kprintf("do components intialization.\n");
for (desc = &__rt_init_desc_rti_board_end; desc <&__rt_init_desc_rti_end; desc ++) //注意这里的 & 符号
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
const init_fn_t *fn_ptr; //定义指向该函数类型的指针,函数指针变量fn_ptr指向的是初始化函数首地址,fn_ptr本身并不代表初始化函数首地址
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr <&__rt_init_rti_end; fn_ptr ++) //注意这里的 & 符号
{
(*fn_ptr)(); //从函数指针中取出初始化函数首地址(函数名)并进行初始化
}

#endif

#else
#ifdef
RT_USING_MODULE
rt_system_module_init();
#endif

#ifdef
RT_USING_FINSH
/* initialize finsh */
finsh_system_init();
finsh_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

#ifdef
RT_USING_LWIP
/* initialize lwip stack */
/* register ethernetif device */
eth_system_device_init();

/* initialize lwip system */
lwip_system_init();
rt_kprintf(
"TCP/IP initialized!\n");
#endif

#ifdef
RT_USING_DFS
/* initialize the device file system */
dfs_init();

#ifdef RT_USING_DFS_ELMFAT
/* initialize the elm chan FatFS file system*/
elm_init();
#endif

#if defined(RT_USING_DFS_NFS) && defined(RT_USING_LWIP)
/* initialize NFSv3 client file system */
nfs_init();
#endif

#ifdef
RT_USING_DFS_YAFFS2
dfs_yaffs2_init();
#endif

#ifdef
RT_USING_DFS_UFFS
dfs_uffs_init();
#endif

#ifdef
RT_USING_DFS_JFFS2
dfs_jffs2_init();
#endif

#ifdef
RT_USING_DFS_ROMFS
dfs_romfs_init();
#endif

#ifdef
RT_USING_DFS_RAMFS
dfs_ramfs_init();
#endif

#ifdef
RT_USING_DFS_DEVFS
devfs_init();
#endif
#endif /* end of RT_USING_DFS */

#ifdef
RT_USING_NEWLIB
libc_system_init(RT_CONSOLE_DEVICE_NAME);
#else
/* the pthread system initialization will be initiallized in libc */
#ifdef
RT_USING_PTHREADS
pthread_system_init();
#endif
#endif

#ifdef
RT_USING_RTGUI
rtgui_system_server_init();
#endif

#ifdef
RT_USING_USB_HOST
rt_usb_host_init();
#endif
#endif
}

 上面代码红色粗体是组件初始化的入口,是一个函数指针。init_fn_t 的定义在rtdef.h中,如下所示:

/* initialization export */
#ifdef
RT_USING_COMPONENTS_INIT //在rtconfig.h中进行宏定义,则启用RT-thread的组件初始化功能
typedef
int (*init_fn_t)(void); //对指向int ()(void)函数类型的指针类型取别名init_fn_t。 利用这个别名可定义指向该函数类型的指针,也可用于直接定义该类型的函数名
#ifdef _MSC_VER
/* we do not support MS VC++ compiler *///这里没有采用microsoft VC++ complier
#define INIT_EXPORT(fn, level)
#else
#if RT_DEBUG_INIT //启用初始化调试模式
struct rt_init_desc
{
const char* fn_name;
const init_fn_t fn; //这里定义的fn并不是函数指针,而是直接定义函数名
};
#define INIT_EXPORT(fn, level) \
const char __rti_##fn##_name[] = #fn; \ //C语言中#连接符是把传递过来的参数当成字符串进行替代
const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \ //定义结构体变量__rt_init_desc_##fn
{ __rti_##fn##_name, fn};
#else
#define INIT_EXPORT(fn, level) \
const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn //这里定义的__rt_init_##fn并不是函数指针,而是直接定义函数名

#endif

#endif
#else
#define INIT_EXPORT(fn, level)
#endif

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* device/component/fs/app init routines will be called in init_thread */
/* device initialization */
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "2")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "3")
/* file system initialization (dfs-elm, dfs-rom, ...) */
#define INIT_FS_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")

其中typdef int (*init_fn_t)(void)的意思是定义init_fn_t为指向函数的指针类型,该函数返回int类型值。这样一来,我们对(*init_fn_t)()的意思就清楚了。

INIT_EXPOT(fn,level) 的表达式是const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn,其中##连词符。

      ## 连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#连接符是把传递过来的参数当成字符串进行替代。

SECTION的定义为:

      #define SECTION(x)   __attribute__((section(x)))

RealView 编译工具 编译器参考指南中给出了下面的解释:

   __attribute__((section("name")))

      通常,ARM 编译器将它生成的对象放在节中,如 data 和 bss。但是,您可能需要使用其他数据节,或者希望变量出现在特殊节中,例如,便于映射到特殊硬件。section 属性指定变量必须放在特定数据节中。如果使用 section 属性,则将只读变量放在 RO 数据节中,而将读写变量放在 RW 数据节中,除非您使用 zero_init 属性。在这种情况下,变量被放在 ZI 节中。到此,意思已经很明了了,编译器可以根据对section("name")中的name指定,可以将它生成的数据放到特定的数据节中。

类似的这样的方式,Linux也提供了一些借鉴,把一个函数的地址(注意是函数地址,而不是函数本身)输出到一个独立的section中,同时按照一定顺序进行排列,例如:
.rti_fn.0
.rti_fn.1
.rti_fn.2
...
.rti_fn.7
这样几个section(这样几个不同的section也给出了排列的顺序)。同时把.rti_fn.0和.rti_fn.7保留给系统使用,分别定义出两个桩放置在这两个点上。

也可以按照RT-Thread的形式定义简化的宏:
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn, level)        \
        const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

#define INIT_BOARD_EXPORT(fn)                INIT_EXPORT(fn, "1")
#define INIT_CPU_EXPORT(fn)                     INIT_EXPORT(fn, "2")
#define INIT_DEVICE_EXPORT(fn)                INIT_EXPORT(fn, "3")
#define INIT_COMPONENT_EXPORT(fn)         INIT_EXPORT(fn, "4")
#define INIT_FS_EXPORT(fn)                       INIT_EXPORT(fn, "5")
#define INIT_APP_EXPORT(fn)                     INIT_EXPORT(fn, "6")
INIT_EXPORT宏用于输出一个函数到初始化序列中,相应的可以定义一些更简化的宏。
这样两个桩可以定义成:
static int rti_start(void)
{
        return 0;
}
INIT_EXPORT(rti_start, "0");
static int rti_end(void)
{
        return 0;
}
INIT_EXPORT(rti_end,"7");
根据这两个桩的位置,简化的rt_components_init()函数就可以变成:
void rt_components_init(void)
{
        const init_fn_t* fn_ptr;
        for (fn_ptr = &__rt_init_rti_start; fn_ptr <&__rt_init_rti_end; )
        {
                (*fn_ptr)();
                fn_ptr ++;
        }
}

事实上,aozima做了工程测试得到了验证。工程编译后,从map文件找到相关部分内容:

InitFuncSym$$Base                   0x00000e18   Number    0init_1.o(InitFuncSym)

__rt_init_init_1                         0x00000e18   Data         4init_1.o(InitFuncSym)

__rt_init_init_2                         0x00000e1c   Data         4init_2.o(InitFuncSym)

__rt_init_init_3                         0x00000e20   Data         4init_3.o(InitFuncSym)

__rt_init_init_4                         0x00000e24   Data         4init_4.o(InitFuncSym)

__rt_init_init_5                         0x00000e28   Data         4init_5.o(InitFuncSym)

__rt_init_init_6                         0x00000e2c   Data         4init_6.o(InitFuncSym)

InitFuncSym$$Limit                  0x00000e30   Number    0init_6.o(InitFuncSym)

尽管数据存放在不同的文件,但从这里这可以看到是空间连续分配的

总之,通过定义

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

可以系统各部分的组件通过INIT_EXPORT(fn,level)放到一个特定代码段当中,简言之,当我们要初始化某个组件时,定义完这个初始化函数后,根据上面宏定义的注释,在其下面接着放一条INIT_XXX_EXPORT(fn)就可以了。相当于一个指定到特定代码段的隐形调用,而且要清楚这个段中是不同组件初始化函数的入口地址,例如:

int my_init_fun(void) {... ...}

INIT_XXX_EXPORT(my_init_fun)

例如在finsh组件shell.c中:

int finsh_system_init(void) {......}

INIT_COMPONENT_EXPORT(finsh_system_init);

注意:定义的初始化函数必须满足输入参数类型为void,返回类型为int,即为typedef int (*init_fn_t)(void)中定义的函数类型。


推荐阅读
  • 本文探讨了将PEBuilder转换为DIBooter.sh的方法,重点介绍了如何将DI工具集成到启动层,实现离线镜像引导安装。通过使用DD命令替代传统的grub-install工具,实现了GRUB的离线安装。此外,还详细解析了bootice工具的工作原理及其在该过程中的应用,确保系统在无网络环境下也能顺利引导和安装。 ... [详细]
  • 本文详细介绍了使用响应文件在静默模式下安装和配置Oracle 11g的方法。硬件要求包括:内存至少1GB,具体可通过命令`grep -i memtotal /proc/meminfo`进行检查。此外,还提供了详细的步骤和注意事项,确保安装过程顺利进行。 ... [详细]
  • 优化Oracle数据库日志功能的关闭方法与实践
    在优化Oracle数据库日志功能的过程中,关闭不必要的日志记录是一项重要任务。本文探讨了Oracle 11g中日志路径的配置和管理,特别是针对常用的警报日志(alert log)。通过合理配置 `alert_$ORACLE_SID.log` 文件,可以有效减少日志文件的大小和提高系统性能。此外,文章还介绍了如何通过调整参数和使用脚本自动化日志管理,进一步提升数据库的稳定性和维护效率。 ... [详细]
  • 掌握DSP必备的56个核心问题,我已经将其收藏以备不时之需! ... [详细]
  • 对于以压缩包形式发布的软件,其目录中通常包含一个配置脚本 `configure`。该脚本的主要功能是确定编译所需的各项参数,如头文件的位置和链接库的路径,并生成相应的 `Makefile` 以供编译使用。通过运行此脚本,开发者可以确保软件在不同环境下的正确编译与安装。此外,该脚本还能够检测系统依赖项,进一步提高编译过程的可靠性和兼容性。 ... [详细]
  • PJSIP 编译与开发指南:深入解析 PJSIP 库的应用与优化
    PJSIP 编译与开发指南:深入解析 PJSIP 库的应用与优化 ... [详细]
  • 在Linux环境中,通过编写Shell脚本来实现自定义命令的创建与激活,能够极大地简化服务器上多个子系统的管理操作。例如,通过简单的命令如“tt”,即可快速查看各个应用程序的名称及其运行状态,从而提高系统维护的效率和便捷性。 ... [详细]
  • 在上篇文章的基础上,本文将继续探讨 Linux 设备驱动中的设备模型与 `devicedriverbus` 机制。在将设备注册到总线之前,需要先创建 `device` 对象。可以通过静态定义 `device` 结构体变量,并调用 `device_register` 函数来完成这一过程。此外,文章还将详细解析设备模型的内部工作机制,以及 `devicedriverbus` 机制如何实现设备与驱动的自动匹配和管理。 ... [详细]
  • 本文简要介绍了 MacOS 系统的分区与引导机制。通过详细解析系统分区结构和引导加载过程,帮助用户更好地理解 MacOS 的启动流程。文章还涵盖了不同版本 MacOS 的分区特点,以及如何在遇到引导问题时进行故障排除。对于希望深入了解 MacOS 内部运作机制的用户来说,本文提供了丰富的技术细节和实用建议。 ... [详细]
  • NanoPi2 使用体验深入解析(续篇)
    随着Raspberry Pi的问世,开源硬件领域迎来了前所未有的发展,激发了全球范围内的创新热潮。在中国,这一趋势同样催生了一系列类似的开发板,例如NanoPi 2。本文作为前篇的延续,将深入探讨NanoPi 2的实际使用体验,从性能、兼容性到应用场景,进行全面分析。 ... [详细]
  • 本文详细解析了 `ulimit` 命令的使用方法及其在实际场景中的应用。`ulimit` 是一个 Shell 内置命令,用于控制 Shell 启动的进程所能使用的系统资源。文章介绍了 `ulimit` 的基本语法格式,包括 `-a`、`-c`、`-d`、`-f`、`-H`、`-l`、`-m`、`-n`、`-p`、`-s`、`-S`、`-t`、`-v` 和 `-w` 等参数的含义和用法。通过具体示例,读者可以更好地理解和应用这些参数,以优化系统性能和资源管理。 ... [详细]
  • 不要急着丢弃旧手机:轻松改装成高效PC游戏性能监控工具
    对于许多PC玩家来说,实时监控游戏过程中的硬件状态是一项常见需求,例如关注游戏帧率、CPU和GPU频率以及温度等关键指标。通常情况下,玩家会借助第三方软件将这些数据展示在屏幕的一角。然而,一种更为创新的方法是利用旧手机改装成高效的性能监控工具,不仅节省成本,还能提供更加便捷和直观的监控体验。通过简单的设置和应用程序安装,旧手机可以变成一个专门的硬件监控设备,实时显示各种重要信息,帮助玩家更好地优化游戏性能。 ... [详细]
  • 深入解析Wget CVE-2016-4971漏洞的利用方法与安全防范措施
    ### 摘要Wget 是一个广泛使用的命令行工具,用于从 Web 服务器下载文件。CVE-2016-4971 漏洞涉及 Wget 在处理特定 HTTP 响应头时的缺陷,可能导致远程代码执行。本文详细分析了该漏洞的成因、利用方法以及相应的安全防范措施,包括更新 Wget 版本、配置防火墙规则和使用安全的 HTTP 头。通过这些措施,可以有效防止潜在的安全威胁。 ... [详细]
  • 深入RTOS实践,面对原子操作提问竟感困惑
    在实时操作系统(RTOS)的实践中,尽管已经积累了丰富的经验,但在面对原子操作的具体问题时,仍感到困惑。本文将深入探讨RTOS中的原子操作机制,分析其在多任务环境下的重要性和实现方式,并结合实际案例解析常见的问题及解决方案,帮助读者更好地理解和应用这一关键技术。 ... [详细]
  • 将Windows Server 2003的DHCP服务迁移到Windows Server 2008 R2的最佳实践与详细步骤
    近期,我们在为某单位进行网络系统升级,从Windows Server 2003迁移至Windows Server 2008 R2的过程中,整体进展较为顺利。然而,在迁移DHCP服务时遇到了一些挑战。本文详细介绍了此次迁移的最佳实践和具体步骤,包括前期准备、环境评估、数据迁移以及后期验证等环节,旨在为同类项目提供参考和指导。 ... [详细]
author-avatar
爱情de眷恋_558
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有