自动初始化机制原理详解

描述

自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。这篇文章就来探索一下其中的奥秘, 简单理解其原理!

| 知识点补充

__attribute__((section(x)))是GNU C的一个特色之一,它可以用于将变量或函数放置在指定的段中。例如,你可以使用__attribute__((section(".my_section")))将变量或函数放置在名为my_section的段中。这对于嵌入式系统编程和操作系统内核编程非常有用。

__attribute__((used))是GCC编译器提供的一个特性,用于告诉编译器在目标文件中保留一个静态变量或函数,即使它没有被引用。这样可以避免链接器删除未使用的节,或者确保某些特定的变量或函数被输出。

__attribute__((unused))是GCC编译器提供的一个特性,用于告诉编译器某个变量或函数可能未被使用,从而避免编译器产生未使用变量或函数的警告。在变量或函数前加上__attribute__((unused))即可使用该特性。

__attribute__((aligned(n)))是GCC编译器提供的一个特性,用于设置变量、类型、函数的对齐方式。它的作用是告诉编译器在分配内存空间时,要求以n个字节为边界。

__attribute__((weak))是GCC编译器提供的一个特性,用于声明或定义一个弱符号(weak symbol)。弱符号是指在链接时,如果存在同名的强符号(strong symbol),则会被强符号覆盖。

GCC

| 原理研究

深入研究了一下, 发现这样使用宏真的很奇妙, 这里就简单介绍一下原理:

export.h文件

 

#ifndef __EXPORT_H
#define __EXPORT_H

#define EXPORT_USED        __attribute__((used))
#define EXPORT_SECTION(x)  __attribute__((section(x)))

typedef int (*export_init_fn_t)(void);

#define EXPORT_INIT_EXPORT(fn,level) 
    EXPORT_USED const export_init_fn_t __export_call_##fn EXPORT_SECTION(".export_call." level) = fn

//板级初始化 顺序1
#define EXPORT_BOARD_INIT(fn)      EXPORT_INIT_EXPORT(fn,"1")

//设备初始化 顺序3
#define EXPORT_DEVICE_INIT(fn)     EXPORT_INIT_EXPORT(fn,"2")

//组件初始化 顺序4
#define EXPORT_COMPONENT_INIT(fn)  EXPORT_INIT_EXPORT(fn,"3")

//环境初始化 顺序5
#define EXPORT_ENV_INIT(fn)        EXPORT_INIT_EXPORT(fn,"4")

//APP初始化 顺序6
#define EXPORT_APP_INIT(fn)        EXPORT_INIT_EXPORT(fn,"5")

void export_components_init(void);

#endif

 

export.c文件

 

#include "export.h"
#include "stdio.h"

static int test_0_start(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_0_start,"0");

static int test_0_0(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_0_0,"0");

static int test_0_1(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_0_1,"0");

static int test_0_end(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_0_end,"0.end");

static int test_1_start(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_1_start,"1");

static int test_1_0(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_1_0,"1");

static int test_1_1(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_1_1,"1");

static int test_1_end(void)
{
    return 0;
}
EXPORT_INIT_EXPORT(test_1_end,"1.end");

// 自动初始化(在main函数调用)
void export_components_init(void)
{
    printf("pfn1:%p
", &__export_call_test_0_start);
    printf("pfn2:%p
", &__export_call_test_0_0);
    printf("pfn3:%p
", &__export_call_test_0_1);
    printf("pfn4:%p
", &__export_call_test_0_end);

    printf("pfn5:%p
", &__export_call_test_1_start);
    printf("pfn6:%p
", &__export_call_test_1_0);
    printf("pfn7:%p
", &__export_call_test_1_1);
    printf("pfn8:%p
", &__export_call_test_1_end);

    volatile const export_init_fn_t *pfn;
    for(pfn = &__export_call_test_0_start; pfn < &__export_call_test_1_end; pfn++)
    {
        printf("%p
", pfn);
        // (*pfn)();
    }
}

 

结果输出:

 

pfn1:08000c50
pfn2:08000c54
pfn3:08000c58
pfn4:08000c5c
pfn5:08000c60
pfn6:08000c64
pfn7:08000c68
pfn8:08000c6c
08000c50
08000c54
08000c58
08000c5c
08000c60
08000c64
08000c68

 

过程分析:

这个测试代码片段主要定义和使用了两个段, 每个段定义了开始和结束, 并且在开始和结束间插入了若干个函数, 通过观察地址的变化会发现, 它们是按规律递增的, 就可以使用遍历来调用指针指向的函数, 从而实现自动初始化外设的目的.

细节分析:

 

// 定义一个函数指针
typedef int (*export_init_fn_t)(void);

// 宏定义
#define EXPORT_INIT_EXPORT(fn,level) 
    EXPORT_USED const export_init_fn_t __export_call_##fn EXPORT_SECTION(".export_call." level) = fn

// 假设调用
EXPORT_INIT_EXPORT(test_1_0,"1");

// 一顿操作后, 内存就存在了一个export_init_fn_t __export_call_test_1_0存放在".export_call."的输入段中,并指定其属于第一级初始化段
// 就可以通过指针调用指针指向的函数来调用指定的函数,实现自动化初始化

 

| EventOS的EXPORT

这个先待定, 后续有时间再移植, export需要参考elab, 涉及到assertcommonexportlog, 感兴趣的读者可以参考:

GCC

 

链接//gitee.com/event-os/eventos/tree/dev_df/examples/stm32g070

 

使用了export机制可以让代码变得更加简洁, 感兴趣的读者可以在理解原理后进行完善和优化.

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分