编译和运行
DM-APP 可以被编译成两种类型:可执行文件 (.mo) 和库文件 (.so)。本质上两者都是 ET_DYN 类型的 ELF 文件,唯一的不同是可执行文件指定了 main 函数作为执行入口,而库文件指定了 0 作为执行入口。
编译和运行可执行文件.mo
-
在 Windows 系统上:双击 win_env.bat 打开命令行运行环境。
-
在 Linux 系统上:直接使用 Shell 命令行即可。
- 进入 dm-app
根目录:
cd luban-lite/packages/artinchip/aic-dm-apps
- 执行下列命令编译 dm-app
为可执行文件:
scons --app=hello
- 查看目标文件:
ls hello/hello.mo
- 如有需要,执行下列命令可清理目标文件,否则可跳过此步:
scons --app=hello -c
- 将生成的 hello.mo 文件拷贝到单板存储介质的文件系统中:
- 在 Shell
下直接运行:
/sdcard/hello.mo
系统输出示例如下:[AIC-DM-APP] init! // DM 初始化函数 module_init() [AIC-DM-APP] Hello, world! // DM 主函数 main() index => 0 // my_thread_init() 调用 rt-thread API 创建的线程 index => 1 index => 2 index => 3
编译和运行库文件 .so
- 进入 dm-app
根目录:
cd luban-lite/packages/artinchip/aic-dm-apps
- 执行下列命令编译 dm-app
为可执行文件:
scons --app=hello
- 查看目标文件:
ls hello/hello.so
- 如有需要,执行下列命令可清理目标文件,否则可跳过此步:
scons --app=hello -c
- 在 Kernel 中使能 test_dm_lib
测试命令:
Drivers options ---> Drivers examples ---> [*] Enable DM Lib test command
-
将生成的 hello.so 拷贝到单板存储介质的文件系统中。
-
使用 test_dm_lib 命令来动态加载:
test_dm_lib // dlopen() 动态加载 /sdcard/hello.so
[AIC-DM-APP] init! // DM 初始化函数 module_init() index => 0 // my_thread_init() 调用 rt-thread API 创建的线程 index => 1 index => 2
实例代码注意事项
本节根据实例代码分析,详细书面了使用可执行文件 hello.mo 和库文件 hello.so 的源码时需要注意的特殊事项:
-
可执行文件 .mo 注意事项
-
RT-Thread API 的调用在 hello.mo 中,可以调用如 rt_thread_create、 rt_thread_startup 等 RT-Thread API。这些 API 默认已经使用 RTM_EXPORT() 声明,可以直接调用 luban-lite/packages/artinchip/aic-dm-apps/hello/rtt_api_test.c。例如:
#include <rtthread.h> void my_thread_entry(void* parameter) { int index = 0; while (1) { rt_kprintf("index => %d\n", index ++); rt_thread_delay(RT_TICK_PER_SECOND); } } int my_thread_init(void) { rt_thread_t tid; tid = rt_thread_create("tMyTask", my_thread_entry, RT_NULL, 2048, 20, 20); if (tid != RT_NULL) rt_thread_startup(tid); return 0; }
-
模块初始化和退出函数如果 dm-app 定义了 module_init() 和 module_cleanup() 函数,它们会在模块初始化和退出时被自动调用 luban-lite/packages/artinchip/aic-dm-apps/hello/main.c。例如:
void module_init(struct rt_dlmodule *module) { printf("[AIC-DM-APP] init!\n"); } void module_cleanup(struct rt_dlmodule *module) { printf("[AIC-DM-APP] exit!\n"); }
用户可以利用该机制来做一些初始化和清理的工作。如果不需要,则无需实现这两个函数。
-
查看 hello.mo 创建的子线程
从代码可知,运行 hello.mo 后会创建
tMyTask
线程,但使用 ps 或者 list_thread 命令无法看到该线程。这是因为该方式下启动的线程会被链接到模块本身的进程链表module->object_list
,而上述命令只能查看全局链表information->object_list
中的线程。目前模块本身的进程链表
module->object_list
不支持命令查看,在模块退出时会停止掉module->object_list
中模块启动的所有子进程。 -
后台进程保活当 hello.mo 的 main() 函数返回后,系统马上会执行模块退出动作,main() 函数创建的所有子进程也会被全部清理。为了让模块的子进程作为后台进程继续运行,可以为 hello.mo 的 main() 函数定义一个特殊返回值 RT_DLMODULE_DEAMON。如果返回该值,则 main() 函数返回后系统不会执行模块退出动作。例如 luban-lite/packages/artinchip/aic-dm-apps/hello/main.c:
#define RT_API_TEST int main(int argc, char *argv[]) { printf("[AIC-DM-APP] Hello, world!\n"); #ifdef RT_API_TEST my_thread_init(); return RT_DLMODULE_DEAMON; #endif return 0; }
-
-
库文件 .so
-
dlopen()、dlsym() 实例test_dm_lib 命令的基本原理是使用 dlopen() 函数动态加载 hello.so 到系统内存,再使用 dlsym() 函数查找到 hello.so 中的 my_thread_init() 函数并调用 luban-lite/bsp/examples/test-dm-lib/test_dm_lib.c。例如:
#define DM_LIB_PATH "/sdcard/hello.so" #define DM_LIB_FUNC "my_thread_init" #define DEAMON_THREAD static void cmd_test_dm_lib(int argc, char **argv) { struct rt_dlmodule *module = NULL; int (*func)(void) = NULL; module = dlopen(DM_LIB_PATH, 0); if (!module) { printf("dlopen %s fail!\n", DM_LIB_PATH); return; } func = dlsym(module, DM_LIB_FUNC); if (!func) { printf("dlsym %s fail!\n", DM_LIB_FUNC); return; } func(); #ifndef DEAMON_THREAD dlclose(module); #endif }
-
查看 hello.so 创建的子线程
通过 test_dm_lib 命令动态加载 hello.so 并调用 my_thread_init() 函数,同样会会创建
tMyTask
线程。上述示例中,使用 ps 或者 list_thread 命令可以看到该线程,是因为系统通过 dlmodule_self() 判断当前进程非模块执行进程,对应的进程链表就加入到了全局链表information->object_list
中。 -
后台进程保活
当调用 my_thread_init() 函数返回后,常规情况是执行 dlclose(module) 来清理动态加载的模块。如果 my_thread_init() 函数创建的所有子进程希望作为后台进程运行,则不能调用 dlclose(module) 来卸载动态库。因为一旦调用 dlclose(module),动态库就会被卸载,而此时后台进程可能仍在运行并需要访问动态库中的数据。一旦有新的内存分配就会覆盖原动态模块的数据,触发 CPU 异常。
为了避免这种情况,可以在不调用 dlclose(module) 的情况下让程序持续运行,或者在程序结束时手动清理动态库占用的资源。
-