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

2023-11-03

代码自动初始化

概述

在嵌入式开发过程中,可能会遇到初始化代码自动初始化,比如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)

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

嵌入式调试技巧-代码自动初始化 的相关文章

随机推荐

  • Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

    介绍new Thread的弊端及Java四种线程池的使用 对Android同样适用 1 new Thread的弊端 执行一个异步任务你还只是如下new Thread吗 new Thread new Runnable Override pub
  • ElasticSearch 排序

    1 相关性排序 ElasticSearch为了按照相关性来排序 需要将相关性表示为一个数值 在 Elasticsearch 中 相关性得分 由一个浮点数进行表示 并在搜索结果中通过 score 参数返回 默认排序是 score 降序 GET
  • 【Linux多线程编程-自学记录】03.主线程与子线程生命周期

    Linux多线程编程学习代码记录 代码已上传gitee 点个Star也不错哦 https gitee com chenshao777 linux thread git Linux多线程编程 自学记录 01 线程ID与进程ID 02 创建线程
  • Debian/Ubuntu升级B-B-R教程

    安装 系统要求 Debian Ubuntu系统 内核版本v4 9 3 v4 12 x 得安装gcc Makefile默认只支持gcc 6 你可以修改它 例如 gcc 4 9 1 更换内核并开启BBR 这里手动安装 方法从BBR刚出来时就在博
  • 項目管理

    刚开始拿到一个项目后工作的执行顺序 其实作为一个管理者 难道一个项目后有很多的工作要做 1 wbs 2 难点的调查 1 将项目分解 分解到最细 能分配到每个人的手中的细度为止 标注每个工作包的难易程度 依据每个人的能力和工作的难易程度进行分
  • 莫圣宏:3.30今天黄金操作建议及晚间盘面行情分析

    现在的你是不是还在迷茫中 无论行情暴涨 暴跌 单边还是震荡 你是不是总是没把握住 就是所谓的一买就跌 一跌就割 一割就涨 一涨就追 一追又套 一套再割 这就像一个死套 资金不断缩水 如此循环 如果你处在这样的循环中 请停下来好好想想 总结一
  • 在centos上使用openswan搭建IPSec***

    背景 因公司业务逐渐迁移到阿里云上 所以有需求搭建一条从公司内容到阿里云的 隧道 因环境限制并未有 设备可以使用 所以计划在linux上搭建ipsec 来实现该功能 拓扑图如下 目的 在阿里云服务器172 16 0 11和公司服务器192
  • 华为od机考真题-HJ64-MP3光标位置(中等)

    while 1 try count int input command input if count lt 4 base
  • c++字符串中的字符替换

    include
  • C#:MSB4086:在条件“condition”中,尝试对计算结果为“value”而不是数字的“expression”进行数值比较

    C MSB4086 在条件 condition 中 尝试对计算结果为 value 而不是数字的 expression 进行数值比较 如果条件表达式中需要数字 但该表达式的计算结果为非数值 则 MSBuild 项目文件中会出现此错误 如果项目
  • Shuffle'm Up【模拟】

    题目链接 POJ 3087 题意 给你两刀牌 第一刀是s1 第二刀是s2 然后有目标的理牌的最终形态S12 现在给出理牌的规则 理牌规则 假设s1 123 s2 456 则第一次理牌之后 S 415263 然后新的s1 415 新的s2 2
  • Oauth2授权模式访问之授权码模式(authorization_code)访问

    Oauth3授权模式访问之授权码模式 authorization code 访问 获取code redirect uri可以随便写 在浏览器输入 注意是get请求方式 http localhost 8080 oauth authorize
  • 【U8+】使用天联高级版客户端登录用友U8,指定U8服务器地址。

    问题描述 当使用U8客户端电脑作为天联高级版软件服务器的时候 即 U8应用服务器和远程服务器不是同一台电脑 每次新建天高用户后 新的天高用户 登录天高客户端后并打开U8登录界面 用友U8的登录窗口服务器地址即为天高服务器的计算机名称 而不是
  • chatgpt 优秀项目

    chagpt token 获取 点击即可 一 pandala 项目 仓库地址 pandora docker 部署方案 拉取镜像 docker pull pengzhile pandora 本地启动镜像 docker run d name c
  • python使用kafka收发消息

    简介 kafka是最近几年很流行的消息队列中间件 在大数据以及后端服务领域有很广泛的应用 废话不多说 接下来直接上代码介绍python如何向kafka发送数据以及订阅数据 消息发布 Kafka的消息是 发布 订阅 模式的 接下来先介绍向ka
  • android二级listview列表实现代码(高仿大众点评)

    今天来实现以下大众点评客户端的横向listview二级列表 先看一下样式 这种横向的listview二级列表在手机软件上还不太常见 但是使用过平板的都应该知道 在平板上市比较常见的 可能是因为平板屏幕比较大 而且也能展现更多的内容 下面来看
  • VC数据类型转换大全 (转)

    VC数据类型转换大全 int i 100 long l 2001 float f 300 2 double d 12345 119 char username 程佩君 char temp 200 char buf CString str v
  • 多任务工作流难管理?看dhtmlxGantt如何实现云管理平台高效管理!

    今天我们分享一个关于KeyWorks OS为意大利公共管理机构提供的工作流解决方案 这是将dhtmlxGantt集成到低代码平台的成功案例 关于KeyWorks OS dhtmlxGantt帮助知名低代码管理平台KeyWorks OS完善B
  • 【HDFS】XXXRpcServer和ClientNamenodeProtocolServerSideTranslatorPB小记

    初始化RouterRpcServer时候会new ClientNamenodeProtocolServerSideTranslatorPB 并把当前RouterRpcServer对象 this 传入构造函数 ClientNamenodePr
  • 嵌入式调试技巧-代码自动初始化

    代码自动初始化 概述 在嵌入式开发过程中 可能会遇到初始化代码自动初始化 比如RTT中就运用到这项技术 那么初始化代码是如何做到自动化调用的呢 在嵌入式实际开发过程中 往往需要对 bsp 部分进行外设配置 以及一些模块 参数进行初始化 常见