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 可以直接使用:

列表 10.1 luban-lite/kernel/rt-thread/components/libc/posix/libdl/dlsyms.c
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.1. shell 调用

hello.mo 拷贝到单板存储介质的文件系统中,在 shell 下直接运行:

aic/> /sdcard/hello.mo
      Hello, world

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) 机制来支持应用程序独立开发的需求。基本原理如下:

../../../_images/dm_design.png

图 10.82 Dynamic Module 实现原理

核心就是实现了 ELF 的链接和加载。具体步骤分解如下:

  1. 编译链接:使用 gcc 工具链将应用源文件 main.c 使用 -fPIC -shared 选项编译链接成 ET_DYN 格式的 ELF 文件 hello.mohello.mo 是一个标准的 ET_DYN 格式ELF 文件,位置无关且可动态链接。注意这里需要使用 riscv-none-embed-gcc 工具链,否则会编译不成功。

  2. 文件加载:在运行的时候,首先会把 hello.mo 文件的数据段代码段拷贝到内存当中。因为我们编译的是位置无关代码 PIC,代码可以被加载到任意位置,所以可以从 Heap 中动态分配内存再进行代码拷贝。此时代码还是不能运行,因为代码中还存在很多对系统函数的调用,需要重新定位重新链接。

  3. 动态链接:遍历 hello.mo 中的可重定位段,对需要重定位的符号,在内核的导出符号表 rtmsymtab 中查询,将查询到的绝对地址回填到可重定位符号的位置。至此完成动态链接,可以跳转到程序入口处执行了。