STM32 实现类似手机运行APP的方法(篇五:动态链接)

一、简介

动态链接 是一种在运行时链接函数地址的技术,程序运行时在内存中建立函数表,其他程序可以通过这个函数表来调用函数。据我了解,windows、linux使用的动态库就是使用这个方式实现的。
由前面几篇文章了解到,单片机环境中app调用系统函数api可以使用 --symdefs=syscall.sym 命令来输出函数符号表,这种方式的缺点如下:
1、因为每次编译系统程序时函数地址都不尽相同,导致app必须在系统程序编译之后编译,否则会因为函数地址不同而导致函数调用失败。
2、可能存在部分函数api在系统程序中并没有使用到,而app程序中需要使用,用这种方式导出的符号表则会被编译器优化掉,app程序就无法使用对应函数,即使取消勾选 One ELF Section per Function 选项依然无法彻底解决,反而会因为链接了很多没用过的函数,导致系统程序过大。
本文旨在解决单片机环境中运行app正常调用系统函数的问题。
由于作者水平问题,可能存在许多错误和疏漏,如有发现,还请批评指正。

二、系统程序修改

1、添加 sys_api.h 文件

此文件使用了编译器的段加载功能,有兴趣的读者可参阅:

https://blog.csdn.net/Ranchaun/article/details/106268032

#ifndef sys_api_h__
#define sys_api_h__

#include "stdint.h"
#include "stdlib.h"
#include "stddef.h"


typedef struct
{
    void *fun;
    int index;
}api_item_struct;

// 把需要用到的函数api 添加到段中
#define api_item(fun_,index_)    \
    __attribute__((used)) static const api_item_struct api_##index_ __attribute__((section("sys_api")))=\
    {.fun=fun_,.index=index_}
    
#endif

2、建立系统API函数表

新建 sys_api.c 每个api对应一个序号,app程序中使用该序号来调用api。


// 强制定义API函数表指针到地址 0x20000008
#define API_TABLE_PTR   (*((void ***)0x20000008))

typedef struct{
    int api_num;
    void **fun_table;
}api_table_struct;

// api函数表,这个表在初始化向量中引用
api_table_struct g_api_table;

static int api_table_init(void)
{
    extern const unsigned int Load$$sys_api$$Base;
    extern const unsigned int Load$$sys_api$$Limit;
    
    api_item_struct *start=(api_item_struct *)&Load$$sys_api$$Base;
    api_item_struct *end=(api_item_struct *)&Load$$sys_api$$Limit;
    
    if(g_api_table.fun_table==0)
    {
        g_api_table.api_num=end-start;
        g_api_table.api_num+=10;
        g_api_table.fun_table=malloc(sizeof(void *)*g_api_table.api_num);
        
        for(;start<end;start++)
        {
            if(start->index<g_api_table.api_num-1)
            {
                g_api_table.fun_table[start->index]=start->fun;
            }
            else
            {
                printf("%s:fun init err item->index=%d\r\n",__func__,start->index);
            }
        }
    }
    
    API_TABLE_PTR=g_api_table.fun_table;
    
    return 0;
}

// 系统启动时初始化api函数表
extern_init(sys_api,api_table_init);

// 按如下方式添加系统API
// 操作系统内核相关api
api_item(rt_hw_interrupt_disable,1);
api_item(rt_hw_interrupt_enable,2);
api_item(rt_enter_critical,3);
api_item(rt_exit_critical,4);
api_item(rt_thread_mdelay,5);
api_item(rt_thread_create,6);
api_item(rt_thread_startup,7);
api_item(rt_thread_delete,8);
//...省略起他函数api...

三、APP程序修改

1、api_table.s

新建 api_table.s 文件,添加如下代码:


    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8

; api函数表在地址 0x20000008
	MACRO		
	api_item $fun_,$index_
	EXPORT	$fun_
$fun_		PROC
	MOV		r12, #0x20000000
	LDR		r12, [r12,#0x8]
	LDR		r12, [r12,#4*$index_]
	BX		r12
    ENDP
	
	MEND


; 定义系统api函数
; 操作系统内核相关api
	api_item rt_hw_interrupt_disable,1 ;
	api_item rt_hw_interrupt_enable,2 ;
	api_item rt_enter_critical,3 ;
	api_item rt_exit_critical,4 ;
	api_item rt_thread_mdelay,5 ;
	api_item rt_thread_create,6 ;
	api_item rt_thread_startup,7 ;
	api_item rt_thread_delete,8 ;
	; ...省略其它api定义...

    ALIGN   4
    END

2、sys_api.h

新建 sys_api.h 文件,这个文件主要是声明系统api函数的类型,把api原型复制到这个文件并稍加修改就可以使用了,也可以直接把声明api函数的头文件复制到app工程下。以下是部分api函数以及数据类型声明示例:

#ifndef sys_api_h__
#define sys_api_h__

#include "stdint.h"
#include "stdlib.h"
#include "stddef.h"


typedef signed   char                   rt_int8_t;      /**<  8bit integer type */
typedef signed   short                  rt_int16_t;     /**< 16bit integer type */
typedef signed   long                   rt_int32_t;     /**< 32bit integer type */
typedef unsigned char                   rt_uint8_t;     /**<  8bit unsigned integer type */
typedef unsigned short                  rt_uint16_t;    /**< 16bit unsigned integer type */
typedef unsigned long                   rt_uint32_t;    /**< 32bit unsigned integer type */
typedef int                             rt_bool_t;      /**< boolean type */

typedef long                            rt_base_t;      /**< Nbit CPU related date type */
typedef unsigned long                   rt_ubase_t;     /**< Nbit unsigned CPU related data type */
typedef rt_base_t                       rt_err_t;       /**< Type for error number */
typedef rt_uint32_t                     rt_tick_t;      /**< Type for tick count */
typedef rt_ubase_t                      rt_size_t;      /**< Type for size number */


/* 以下定义为指针定义,我修改过,但保证能正常使用 */

typedef struct{void *p;}*               rt_thread_t;
typedef struct{void *p;}*               rt_timer_t;
typedef struct{void *p;}*               rt_sem_t;
typedef struct{void *p;}*               rt_mutex_t;
typedef struct{void *p;}*               rt_event_t;
typedef struct{void *p;}*               rt_mailbox_t;
typedef struct{void *p;}*               rt_mq_t;




rt_base_t rt_hw_interrupt_disable(void);
void rt_hw_interrupt_enable(rt_base_t level);
void rt_enter_critical(void);
void rt_exit_critical(void);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
rt_thread_t rt_thread_create(const char *name,
                             void (*entry)(void *parameter),
                             void       *parameter,
                             rt_uint32_t stack_size,
                             rt_uint8_t  priority,
                             rt_uint32_t tick);
rt_err_t rt_thread_startup(rt_thread_t thread);
rt_err_t rt_thread_delete(rt_thread_t thread);

    
rt_timer_t rt_timer_create(const char *name,
                           void (*timeout)(void *parameter),
                           void       *parameter,
                           rt_tick_t   time,
                           rt_uint8_t  flag);
rt_err_t rt_timer_delete(rt_timer_t timer);
rt_err_t rt_timer_start(rt_timer_t timer);
rt_err_t rt_timer_stop(rt_timer_t timer);
                             

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);
rt_err_t rt_sem_delete(rt_sem_t sem);
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);
rt_err_t rt_sem_release(rt_sem_t sem);
  

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time);
rt_err_t rt_mutex_release(rt_mutex_t mutex);


rt_event_t rt_event_create(const char *name, rt_uint8_t flag);
rt_err_t rt_event_delete(rt_event_t event);
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
rt_err_t rt_event_recv(rt_event_t   event,
                       rt_uint32_t  set,
                       rt_uint8_t   opt,
                       rt_int32_t   timeout,
                       rt_uint32_t *recved);


rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag);
rt_err_t rt_mb_delete(rt_mailbox_t mb);
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_uint32_t  value,
                         rt_int32_t   timeout);
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout);


rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag);
rt_err_t rt_mq_delete(rt_mq_t mq);
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size);
rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size);
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout);


//使用从系统程序继承的printf函数代替c语言库中的printf函数
int printf_(const char *,...);
int sprintf_(char * __restrict /*s*/, const char * __restrict /*format*/, ...);
                    
#define printf printf_ 
#define sprintf sprintf_ 

                    
#endif


3、api调用

经过以上的修改,app程序就可以调用系统程序提供的api函数了,与调用本地函数没有任何区别,在此不做演示。
需要注意的是必须在系统初始化api函数表之后再运行app程序,使用函数表编译的app程序不会因为系统程序的重新编译而无法使用。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值