嵌入式动态库
嵌入式动态库
简介
在 Linux 系统中,动态库(.so)依靠 ELF 动态链接器完成符号解析与运行时绑定,实现了模块复用、升级与内存共享。
而在嵌入式系统中,由于缺乏动态链接器、内存受限或执行环境受控,无法使用完整的 ELF 动态加载机制。但嵌入式固件仍常常需要一种“可复用、可升级、可替换”的通用功能模块。
本文实现了一种适合嵌入式项目使用的动态库的实现,采用固定地址入口、跳转表(function table)等方式,构建一个“简化版动态库接口”。这种机制本质上是将共享库的概念提前到链接期或固件布局阶段:
- 公共模块放在 Flash 固定区域
- 导出函数入口地址作为 ABI
- 应用通过跳板或函数指针表调用
- 允许升级公共模块而不改动应用
分区
以 512k Flash 和 64k RAM 为例,包含 bootloader
如果不使用独立的动态库,将库的内容直接链接到程序中,布局会这样:
如果使用了动态库,bootloader 和 app 可以共享库:
为了让应用之间,以及库和应用之间能进行消息通信,可以划定一片共享内存区域。
链接脚本中的分区布局如下:
bootloader:
MEMORY
{
FLASH(rx) : ORIGIN = 0x00000000, LENGTH = 16k
SHARED_RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 1K
RAM(rwx) : ORIGIN = 0x10000400, LENGTH = 64k - 1k
}
动态库:
MEMORY
{
FLASH(rx) : ORIGIN = 0x00004000, LENGTH = 32k
SHARED_RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 1K
RAM(rwx) : ORIGIN = 0x10000400, LENGTH = 64k - 1k
}
主应用:
MEMORY
{
FLASH(rx) : ORIGIN = 0x0000C000, LENGTH = 512k - 16k - 32k
SHARED_RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 1K
RAM(rwx) : ORIGIN = 0x10000400, LENGTH = 64k - 1k
}
动态库的实现
动态库需要将其接口(API)放置在固定位置,以便其他的程序可以找到该接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// sharedlib.h
#pragma once
typedef struct {
void (*init)(void);
void (*entry)(void);
} ShareLibAPITable;
#define SharedLibAPIAddr 0x4000
#ifdef SharedLibAPIAddr // api table固定放在0x4000位置,并用宏封装
#define ShareLibAPI ((const ShareLibAPITable*)SharedLibAPIAddr)
#else // 也允许不通过动态库的方式直接调用,让其直接变为调用者程序的一部分
extern const ShareLibAPI shareLibAPITable;
#define ShareLibAPI ((const ShareLibAPITable*)&shareLibAPITable)
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// sharedlib.c
#include "sharedlib.h"
void shareLibInit()
{
// ...
}
void sharedLibEntry()
{
// ...
}
// 指定该 api table 的 section 为 shared_lib_api
__attribute__((section(.shared_lib_api),used))
const ShareLibAPITable shareLibAPITable = {
.init = shareLibInit,
.entry = sharedLibEntry,
}
不作为动态库时,
sharedlib.c可以直接链接到主程序中。
动态库链接脚本:
SECTION
{
.shared_lib_api 0x4000 :
{
/* 名字为 shared_lib_api 段放到0x4000位置 */
KEEP(*(.shared_lib_api))
} > FLASH
/* 其他代码段放到FLASH的后续位置 */
.text :
{
*(.text*)
*(.rodata*)
} > FLASH
/* 如果动态库 */
.data :
{
*(.data*)
} > FLASH
.bss :
{
*(.bss*)
} > FLASH
}
动态库的使用
动态库的使用很简单,只要引入相应的头文件,该头文件包含了 API table(function 跳转表) 的入口,并已经被封装成了结构体指针,直接使用即可:
1
2
3
4
5
6
7
// app.cpp
#include "sharedlib.h"
int main()
{
ShareLibAPI->init();
}
共享内存
由于动态库和其他程序是分开链接的,导致它们之间的全局变量是无法共享的,即使是同名的变量,其地址也是不同的,所有需要提供共享内存,让它们能访问到相同的变量。
// sharedram.hpp
#pragma once
__attribute__((section(".shared_ram"), used))
inline struct {
unsigned char sharedAttr1[4];
unsigned char sharedAttr2[8];
} sharedRam;
在所有链接脚本中加入该 .shared_ram 段
SECTION
{
/* ... */
.shared_ram {
. = ALIGN(4)
KEEP(*(.shared_ram))
} > SHARED_RAM
/* ... */
}
这样就能保证所有程序都能访问到这片位置固定的共享内存:
1
2
3
4
5
6
7
8
// app.cpp
#include "sharedram.hpp"
int main()
{
auto& attr1 = reinterpret_cast<long&>(sharedRam.sharedAttr1);
attr1 = 2;
}
1
2
3
4
5
6
7
8
// sharedlib.c
#include "sharedram.hpp"
void shareLibInit()
{
auto& attr1 = reinterpret_cast<long&>(sharedRam.sharedAttr1);
int x = attr1;
}
参考
本文由作者按照 CC BY-SA 4.0 进行授权


