STM32上实现驱动注册initcall机制(类linux)

提示:本文实现自动初始化驱动


前言

如果我们把要初始化函数的地址按照顺序排列在一起,我们只要拿到第一个函数的地址,那么我们就可以依次拿到其它函数的地址,然后去执行相应的函数:
1.首先需要定义一个函数指针,我们的初始化函数都必须按照这个格式写
2.定义一个 section 区域用来存放我们的函数指针
3.按照顺序来存放函数指针


具体实现如下:

一、定义一个函数指针

typedef int (*aini_init_fn_t)(void);

此函数形参为void,返回值类型int
后面我们自己的初始化函数也必须是这种格式

二、定义指针,指向函数地址

#define AINI_USED        		__attribute__((used))
#define AINI_SECTION(x)  		__attribute__((section(x)))

#define AINI_INIT_EXPORT(fn,level) \
    AINI_USED const aini_init_fn_t __aini_call_##fn AINI_SECTION(".aini_call." level) = fn

##操作是连接左右两边的字符串,这样就可以定义前缀是"_aini_call"+函数名的变量,把这个变量放入.aini_call.区域,并使用level来进行标记排序

解释:
attribute()是gnu C中的扩展语法,它可以用来实现很多灵活的定义行为

#define AINI_USED        		__attribute__((used))

使用前提是在编译器编译过程中,如果定义的符号没有被引用,编译器就会对其进行优化,不保留这个符号,而__attribute__((used))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。

#define AINI_SECTION(x)  		__attribute__((section(x)))

attribute((section(“.initcall” #id “.init”))) 表示编译时将目标符号放置在括号指定的段中。
# 在宏定义中的作用是将目标字符串化
## 在宏定义中的作用是符号连接,将多个符号连接成一个符号,并不将其字符串化。

假设有 AINI_INIT_EXPORT(test_init,“0”) 宏展开如下:

const aini_init_fn_t __aini_call_test_init __attribute__((section(".aini_call." level))) = test_init

这就是一个简单的变量定义(定义了一个函数指针,并且指向 test_init 函数)。
同时声明__aini_call_test_init 这个变量即使没被引用也保留符号,且将其放置在内核镜像的 .aini_call.0 段处。

三、定义不同level的section

//板级初始化 顺序1
#define AINI_BOARD_INIT(fn)      	AINI_INIT_EXPORT(fn,"1")
    
//设备初始化 顺序2
#define AINI_DEVICE_INIT(fn)     	AINI_INIT_EXPORT(fn,"2")
    
//组件初始化 顺序3
#define AINI_COMPONENT_INIT(fn)  	AINI_INIT_EXPORT(fn,"3")
    
//环境初始化 顺序4
#define AINI_ENV_INIT(fn)        	AINI_INIT_EXPORT(fn,"4")
    
//APP初始化 顺序5
#define AINI_APP_INIT(fn)        	AINI_INIT_EXPORT(fn,"5")

1.同一个 level的section 定义函数地址顺序

说明: 同一个 level的section 可以定义好多个不同的函数指针,并且初始化。其函数地址是按照定义先后顺序排列的

int test1(void)
{
    printf("test1 ...\r\n");
}
int test2(void)
{
    printf("test2 ...\r\n");
}
int test3(void)
{
    printf("test3 ...\r\n");
}
AINI_INIT_EXPORT(test1,"2"); 
AINI_INIT_EXPORT(test3,"2");  
AINI_INIT_EXPORT(test2,"2");    		

串口打印:
在这里插入图片描述
map图查看:
在这里插入图片描述
可以看到 __aini_call_test1、__aini_call_test3、__aini_call_test2 依次放置在内核镜像的 .aini_call.2 段处。

2.不同 level的section 定义函数地址顺序

说明: 不同 level的section 定义的函数指针,其函数地址是存放顺序与section相关, 与定义先后顺序无关

int test1(void)
{
    printf("test1 ...\r\n");
}
int test2(void)
{
    printf("test2 ...\r\n");
}
int test3(void)
{
    printf("test3 ...\r\n");
}
AINI_INIT_EXPORT(test1,"1"); 
AINI_INIT_EXPORT(test3,"3");  
AINI_INIT_EXPORT(test2,"2");  

在这里插入图片描述
如图,函数定义在不同的段,所以存放顺序与段的名字相关,段名字先后顺序:
.aini_call.0 > .aini_call.0.end > .aini_call.1 > .aini_call.1.end > .aini_call.2 > .aini_call.3 > .aini_call.5.end

注意:段名字可以为任意字符,先后顺序与字符ascii码大小相关

四、程序结构

1.aini.h 文件

#ifndef __AINI_H
#define __AINI_H

/********************************************************************
*@beief 自动化初始化模块,依赖编译器,目前仅支持 CC_ARM 环境
*灵感来自于 RT-Thread 的自动初始化思路,对此表示感谢
*使用此模块可以让代码变的简洁
**********************************************************************/

#if defined(__CC_ARM) || defined(__CLANG_ARM)

#define AINI_USED        __attribute__((used))
#define AINI_SECTION(x)  __attribute__((section(x)))

/**
 * @brief 申明函数指针  os_init_fn_t 
 * 形参:void
 * 返回:int
 * 扩展:函数指针是指向函数的指针变量,它本身是指针变量,指向函数地址
 */
typedef int (*aini_init_fn_t)(void);

#define AINI_INIT_EXPORT(fn,level) \
    AINI_USED const aini_init_fn_t __aini_call_##fn AINI_SECTION(".aini_call." level) = fn


/**
*这个函数需要重写
*主要执行系统启动后最早期的必须按照顺序执行的函数
*比如 先执行 复位外设,配置系统时钟,初始化IO,初始化打印串口等
*/    
extern int ani_seq_init(void);

#else
#error 'not support complier!'
#endif

#endif

2.aini.c 文件

#include "aini.h"
#include "bsp.h"

static int aini_start(void)
{
    printf("os_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_start,"0");

static int aini_board_start(void)
{
    printf("os_board_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_start,"0.end");


static int aini_board_end(void)
{
    printf("os_board_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_end,"1.end");

static int aini_end(void)
{
    printf("os_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_end,"5.end");


/**
 * @brief 自动调用 OS_BOARD_INIT(fn) 自定的函数
 * .init_call.1
 */
void aini_components_board_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_start; pfn < &__aini_call_aini_board_end;pfn++)
    {
        (*pfn)();
    }
}

/**
 * @brief 自动初始化 section .init_call.2 ~ .init_call.5
 * 
 */
void aini_components_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_board_end; pfn < &__aini_call_aini_end; pfn++)
    {
        (*pfn)();
    }
}

__weak int ani_seq_init(void)
{
   /* 初始化系统时钟,片内资源 */
}

3.main.c 文件

#include "bsp.h"
#include "aini.h"

int ani_seq_init(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  bsp_Init();
}

int board_init(void)
{
    printf("init BOARD ...\r\n");
}

int device_init(void)
{
    printf("init DEVICE ...\r\n");
}

int component_init(void)
{
    printf("init COMPONENT ...\r\n");
}

int env_init(void)
{
    printf("init ENV ...\r\n");
}

int app_init(void)
{
    printf("init APP ...\r\n");
}

AINI_INIT_EXPORT(board_init,"1");   		//板级初始化 顺序1
AINI_INIT_EXPORT(device_init,"2");   		//设备初始化 顺序2
AINI_INIT_EXPORT(component_init,"3");   	//组件初始化 顺序3
AINI_INIT_EXPORT(env_init,"4"); 			//环境初始化 顺序4
AINI_INIT_EXPORT(app_init,"5"); 			//APP初始化 顺序5

int main(void)
{
	ani_seq_init();
	aini_components_board_init();
	aini_components_init();	
	
	while (1)
	{		
	}
}

4.输出结果

在这里插入图片描述

5.map文件

在这里插入图片描述

五、程序简化

以上程序main函数还需要手动调用初始化,不够简洁,这里我们自动化调用
aini.h文件不用变
aini.c文件改为如下:

#include "aini.h"
#include "bsp.h"

static int aini_start(void)
{
    printf("os_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_start,"0");

static int aini_board_start(void)
{
    printf("os_board_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_start,"0.end");

static int aini_board_end(void)
{
    printf("os_board_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_end,"1.end");

static int aini_end(void)
{
    printf("os_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_end,"5.end");


/**
 * @brief 自动调用 OS_BOARD_INIT(fn) 自定的函数
 * .init_call.1
 */
static void aini_components_board_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_start; pfn < &__aini_call_aini_board_end;pfn++)
    {
        (*pfn)();
    }
}

/**
 * @brief 自动初始化 section .init_call.2 ~ .init_call.5
 * 
 */
static void aini_components_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_board_end; pfn < &__aini_call_aini_end; pfn++)
    {
        (*pfn)();
    }
}


#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int $Super$$main(void);
/** 
*$Sub$$main 重定义main函数,在mian函数之前执行
*/
int $Sub$$main(void)
{
    /* 1.关中断 */
    //do nothing
    
    /* 2.初始化系统时钟,片内资源 */
    ani_seq_init();
    
    /* 3.初始化板级设备 */
    aini_components_board_init();
    
    /* 4.初始化一些软件级别的初始化,如库,APP等 */
    aini_components_init();
    
    /* 5. 开中断 */
    //do nothing
    
    #if defined(__CC_ARM) || defined(__CLANG_ARM)
    extern int main(void);
    extern int $Super$$main(void);
    /* 6.跳转到main函数 */
    $Super$$main(); /* for ARMCC. */
    #else
    #error 'not support complier!'
    #endif
    
    return 0;
}
#else
#error 'not support complier!'
#endif

__weak int ani_seq_init(void)
{
   /* 初始化系统时钟,片内资源 */
}

main函数可以简化为如下:

int main(void)
{
	while (1)
	{		
	}
}

参考

https://blog.csdn.net/bai_yechuang2012/article/details/129876064
https://blog.csdn.net/ziqi5543/article/details/102478126?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_36310253/article/details/125233779
https://gitee.com/MacRsh/mr-library/blob/master/document/user/auto_init.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值