10.2. Dynamic Module 使用指南¶
10.2.1. 配置和编译¶
我们简称 Luban-Lite 原生开发的映像为 kernel
,使用 Dynamic Module
机制开发的应用为 dm-app
。
10.2.1.1. kernel¶
10.2.1.1.1. 配置¶
要使用 Dynamic Module
功能,内核需要打开以下两项配置:
Rt-Thread options --->
RT-Thread Components --->
C/C++ and POSIX layer --->
POSIX (Portable Operating System Interface) layer --->
[*] Enable POSIX file system and I/O
[*] Enable dynamic module APIs, dlopen()/dlsym()/dlclose() etc
还可以选择 dm-app 动态加载时使用的内存 heap。具体的可选 heap 会随不同平台的配置有所不同:
Rt-Thread options --->
RT-Thread Components --->
C/C++ and POSIX layer --->
POSIX (Portable Operating System Interface) layer --->
[*] Enable dynamic module APIs, dlopen()/dlsym()/dlclose() etc
Select dynamic module use mem (Prsam CMA heap) --->
(X) Sys Heap
( ) Prsam CMA heap
10.2.1.1.2. 符号导出¶
kernel 中被 dm-app 访问到的符号需要使用 RTM_EXPORT()
宏来进行声明,没有声明的 kernel 函数是不能在 dm-app 中使用的。类似如 linux 中的 EXPORT_SYMBOL()
宏。
例如对于一些标准的 c 库函数,kernel 已经定义好了 RTM_EXPORT()
声明,dm-app 可以直接使用:
RTM_EXPORT(strcpy);
RTM_EXPORT(strncpy);
RTM_EXPORT(strlen);
RTM_EXPORT(strcat);
RTM_EXPORT(strstr);
RTM_EXPORT(strchr);
RTM_EXPORT(strcmp);
RTM_EXPORT(strtol);
RTM_EXPORT(strtoul);
RTM_EXPORT(strncmp);
RTM_EXPORT(memcpy);
RTM_EXPORT(memcmp);
RTM_EXPORT(memmove);
RTM_EXPORT(memset);
RTM_EXPORT(memchr);
RTM_EXPORT(putchar);
RTM_EXPORT(puts);
RTM_EXPORT(printf);
RTM_EXPORT(sprintf);
10.2.1.2. dm-app¶
动态模块 (Dynamic Module) 应用的开发的步骤如下。
10.2.1.2.1. 生成 sdk¶
dm-app 的开发目录在 luban-lite/packages/artinchip/aic-dm-apps
,首先确保 luban-lite/
根目录下的 kernel 工程被正确配置且编译通过后,然后生成对应的 dm-app sdk:
$ cd luban-lite/packages/artinchip/aic-dm-apps
$ scons --target=sdk
scons: Reading SConscript files ...
Copy rtconfig.h...
Copy rtconfig.py...
Copy rtua.py...
Copy rt-thread/tools/...
Copy tools/env/...
Copy tools/scripts/...
Copy onestep.sh...
Copy win_env.bat...
Copy win_cmd.bat...
Build local sdk succeed!
dm-app sdk 创建完成以后, aic-dm-apps
就可以脱离 luban-lite sdk
进行开发了。 aic-dm-apps
文件夹可以被拷贝到任意 Linux/Windows 路径进行开发和编译。
10.2.1.2.2. 目录结构¶
aic-dm-apps
的目录结构如下所示:
aic-dm-apps/
├── hello // hello 实例
│ ├── hello.mo // 目标文件
│ ├── main.c
│ ├── main.o
│ └── SConscript
├── SConstruct // scons 入口
├── toolchain // 解压后的工具链
├── tools // 编译需要的支持
│ ├── env
│ ├── host
│ ├── __init__.py
│ ├── onestep.sh
│ ├── scripts
│ ├── sdk
│ ├── toolchain
│ ├── ua.def
│ ├── ua.py
│ └── ua.pyc
├── win_cmd.bat
└── win_env.bat // windows 下的命令行环境
10.2.1.2.3. 编译 app¶
创建完 dm-app sdk 就可以编译 dm-app 了。如果是 windows 双击 win_env.bat
打开命令行运行环境,linux 直接使用 shell 命令行即可。具体步骤如下:
$ cd luban-lite/packages/artinchip/aic-dm-apps
$ scons --app=hello // 编译
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
CC hello/main.o
LINK hello/hello.mo
riscv-none-embed-strip -R .hash hello/hello.mo
riscv-none-embed-size hello/hello.mo
text data bss dec hex filename
295 160 4 459 1cb hello/hello.mo
scons: done building targets.
$ ls hello/hello.mo // 查看目标文件
$ scons --app=hello -c // 清理
10.2.2. 运行和使用¶
对于 dm-app 实例 hello 生成的目标文件 hello.mo
,有几种方式可以运行。
10.2.2.2. code 调用¶
在 kernel 代码中对 hello.mo
的调用可以通过以下 api 来进行:
dlmodule_exec()
dlopen()/dlclose()/dlsym()
10.2.3. 用户 dm-app¶
用户开发自己的 dm-app 可以基于 hello
实例来开发,把 hello
文件夹复制并改名成自己的应用:
$ cd luban-lite/packages/artinchip/aic-dm-apps
$ cp -r hello xxxapp
$ scons --app=xxxapp // 编译
$ ls xxxapp/xxxapp.mo // 目标文件
$ scons --app=xxxapp -c // 清理
把用户源文件拷贝到 xxxapp
文件夹,编辑 xxxapp/SConscript
文件让所有源文件能被 scons 编译。 SConscript
的语法和修改方法请参考 Luban-Lite 的相关文档。
10.2.4. 原理说明¶
在 Linux/Windows 等大型系统中应用和驱动是可以独立开发,应用独立编译成 elf/exe 文件,然后在目标系统上执行。毫无疑问这种动态加载的方式是需要开销的,一般嵌入式系统都精简了该功能。但是在实际产品开发的过程中,特别是需要二次开发的场景,独立开发和编译应用程序有强烈需求。
Luban-Lite 使用动态模块 (Dynamic Module) 机制来支持应用程序独立开发的需求。基本原理如下:

图 10.82 Dynamic Module 实现原理¶
核心就是实现了 ELF 的链接和加载。具体步骤分解如下:
编译链接:使用
gcc
工具链将应用源文件main.c
使用-fPIC -shared
选项编译链接成ET_DYN
格式的 ELF 文件hello.mo
。hello.mo
是一个标准的ET_DYN
格式ELF 文件,位置无关且可动态链接。注意这里需要使用riscv-none-embed-gcc
工具链,否则会编译不成功。文件加载:在运行的时候,首先会把
hello.mo
文件的数据段代码段拷贝到内存当中。因为我们编译的是位置无关代码 PIC,代码可以被加载到任意位置,所以可以从 Heap 中动态分配内存再进行代码拷贝。此时代码还是不能运行,因为代码中还存在很多对系统函数的调用,需要重新定位重新链接。动态链接:遍历
hello.mo
中的可重定位段,对需要重定位的符号,在内核的导出符号表rtmsymtab
中查询,将查询到的绝对地址回填到可重定位符号的位置。至此完成动态链接,可以跳转到程序入口处执行了。