嵌入式调试技巧-代码自动初始化

文章介绍了在嵌入式开发中如何实现初始化代码的自动调用,通过宏定义和链接器的section属性,将初始化函数放入特定段,并在运行时遍历这些段进行调用,以达到解耦和自动初始化的目的。示例代码展示了如何定义和使用这些宏,以及通过检查map文件验证初始化顺序的正确性。
摘要由CSDN通过智能技术生成

代码自动初始化

概述

在嵌入式开发过程中,可能会遇到初始化代码自动初始化,比如RTT中就运用到这项技术。那么初始化代码是如何做到自动化调用的呢?

在嵌入式实际开发过程中,往往需要对 bsp 部分进行外设配置,以及一些模块、参数进行初始化,常见的方法如下:

  • 将各个部分的初始化代码分别封装成一个单独的函数,然后再main函数刚开始的地方进行调用已实现初始化;
    该方法简单粗暴,但是此方法存在一些不足
  • 所有初始化均需要在main函数内调用,不能做到彻底解耦
  • 框架设计不友好,部分非业务部分初始化,如软件框架类的初始化,往往不希望还需要业务在main函数内调用初始化

那有没有一种方法能解决上述的问题呢?
答案是肯定的,本文将向大家介绍一种自动初始化的实现方式。注意,此方案在不同的平台,由于链接器使用的链接脚本可能不一致,容易出现问题,需要大家重点注意,细节在下文中将详细介绍。

案例讲解

为了更好的阐述代码自动初始化的技术,本文通过一个demo程序进行讲解如何实现该技术,以下是该demo程序:

typedef void (*init_func)(void);

#define INIT_EXPORT(fn, level)    const init_func init_##fn __attribute__((used, section(".init."level))) = fn
void start(void)
{
    return;
}
INIT_EXPORT(start, "1");
void end(void)
{
    return;
}
INIT_EXPORT(end, "4");

接下来分析上述代码:

关于宏定义:#define INIT_EXPORT(fn, level) const init_func init_##fn attribute((used, section(".init."level))) = fn的代码分析如下:

  • 首先定义一个函数指针typedef void (*init_func)(void);用于所有初始化函数的指针类型;
  • 定义宏INIT_EXPORT(fn, level) 作为一个函数接口,后续的初始化函数可通过此接口加入自动初始化列表内
    • const init_func init_##fn,其中##为连接符,假设参数fn为test,则init_##fn为init_test;init_func 为指令类型
    • const init_func init_##fn与const int *p是同一个概念,只不过数据的类型不一样
  • const init_func init_##fn定义了一个init_func类型的指针变量,此指针还没有赋值初始化,因此将参数fn赋值给此变量,宏定义可以简化为#define INIT_PORT(fn, level) const init_func init_##fn = fn(__attribute__先忽略)
  • attribute((used, section(".init."level))) 这是一个关键字进行修饰,可以做如下拆解:
    • attribute((used))用来告诉编译器,此函数如果没有被调用,也不要被优化
    • attribute((section(".init."level)))用来修饰的内容放入指定的段“.init.”level中(编译器在你编程的时候自动完成),注意level为宏的第二个传入参数,传入来的时候是一个字符串,所以不用加#加进行拼接,C语言中两个字符串会自动拼接。

综述分析:宏#define INIT_EXPORT(fn, level) const init_func init_##fn attribute((used, section(".init.“level))) = fn,其实定义了一个init_func类型的函数指针,并将此函数指针通过__attribute__((used, section(”.init."level)))指定存放到特定的段内。

demo程序中定义了两个函数:

  • void start(void)
  • void end(void)
    并通过宏INIT_EXPORT(fn,level)添加到自动初始化段内
  • INIT_EXPORT(start, “1”);
  • INIT_EXPORT(end, “6”);

注意事项:

  1. level参数是一个字符串,而不是数字
  2. 用户自定义初始化函数编译器会自动添加到start和end的level值中间,是由于编译器采用__attribute__((section(".init."level))) 指定放入到指定段的时候会针对其进行排序,keil默认采用根据名称的方式排序,因此需要放置在用户自定义的,放置在用 start 和 end 中间,方便后续遍历 start 和 end 中间部分进行完整初始化

最后的vhdl_board_init初始化函数:

void vhdl_board_init(void)
{

    const init_func *fn_ptr = NULL;

    for (fn_ptr = &init_start; fn_ptr < &init_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
}

此函数在main函数中调用,通过for循环遍历start和end中间的段,并调用进行初始化。

通过上述一系列的操作,用户初始化函数时只需要调用宏INIT_EXPORT(fn,level)即可完成自动初始化。

为了针对不同类型的初始化,可以将start和end之间距离拉大点,用作区别不同类型的初始化,如下所示

INIT_EXPORT(start, "1");
INIT_EXPORT(end, "6");

并且对外开放相应的接口宏

#define INIT_BSP_PORT(func)		INIT_EXPORT(func, "2");
#define INIT_DATA_PORT(func)	INIT_EXPORT(func, "3");

用户通过调用 BSP_INIT_PORT 和 DATA_INIT_PORT 进行注册

检测初始是否成功??

检测MAP文件

在上述方法中,我们使用 attribute((section(".init."level))) 将数据存入指定的段内,那么是否成功了呢,以及排序方式是否符合我们的预期呢?接下来我们需要通过检查map文件进行确认。

keil / MDK 环境检查

keil/mdk编译完程序之后,双击工程栏处的工程,可以查看map文件,还不知道的可以网上搜下

以下是上述demo的map文件中关键字段,我们可以看到我们需要的数据存放在正确的段内,并且排序也是正常的,注意一定要检查排序和字段是否完整!
在这里插入图片描述

gcc 环境
  1. 在链接器脚本ld文件中建立sections区域块.text中定义新的section information for inital ,比如rtt如下:
	.ALIGN(4)
	__rt_init_start=.;
	KEEP(*(SORT(.rti_fn*)))
	__rt_init_end=.;
	.ALIGN(4)
  1. 编写auto_init文件
typedef void (*init_fn_t)(void);
#define INIT_EXPORT(fn,level)	__attribute__((used)) const init_fn_t __rt_init_##fn __attribute__((section(".rti_fn." level))) = fn

static int rti_start(void)
{
	return 0;
}
INIT_EXPORT(rti_start,"0");
static int rti_board_start(void)
{
	return 0;
}
// 注册rti_board_start到内存中
INIT_EXPORT(rti_board_start,"0.end");
static int rti_board_end(void)
{
	return 0;
}
// 注册rti_board_end到内存中
INIT_EXPORT(rti_board_end,"1.end");
static int rti_end(void)
{
	return 0;
}
INIT_EXPORT(rti_end,"6.end");

void rt_components_board_init(void)
{
    volatile 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)();
    }
}
// 遍历剩下的地址内存空间,防止函数指针访问出错
void rt_components_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;

    rt_kprintf("do components initialization.\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
    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)();
    }
#endif /* RT_DEBUG_INIT */
}
#endif /* RT_USING_COMPONENTS_INIT */

//最后生成的map文件如下:

 *(SORT(.rti_fn*))
 .rti_fn.0      0x0800cf30        0x4 ./rt-thread/src/components.o
                0x0800cf30                __rt_init_rti_start
 .rti_fn.0.end  0x0800cf34        0x4 ./rt-thread/src/components.o
                0x0800cf34                __rt_init_rti_board_start
 .rti_fn.1      0x0800cf38        0x4 ./drivers/drv_clk.o
                0x0800cf38                __rt_init_clock_information
 .rti_fn.1.end  0x0800cf3c        0x4 ./rt-thread/src/components.o
                0x0800cf3c                __rt_init_rti_board_end
 .rti_fn.6      0x0800cf40        0x4 ./rt-thread/components/finsh/shell.o
                0x0800cf40                __rt_init_finsh_system_init
 .rti_fn.6.end  0x0800cf44        0x4 ./rt-thread/src/components.o
                0x0800cf44                __rt_init_rti_end
                0x0800cf48                __rt_init_end = .
                0x0800cf48                . = ALIGN (0x4)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值