一、简介
动态链接 是一种在运行时链接函数地址的技术,程序运行时在内存中建立函数表,其他程序可以通过这个函数表来调用函数。据我了解,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.youkuaiyun.com/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程序不会因为系统程序的重新编译而无法使用。