Edit online

编译和运行

29 May 2024
Read time: 4 minute(s)

DM-APP 可以被编译成两种类型:可执行文件 (.mo) 和库文件 (.so)。本质上两者都是 ET_DYN 类型的 ELF 文件,唯一的不同是可执行文件指定了 main 函数作为执行入口,而库文件指定了 0 作为执行入口。

编译和运行可执行文件.mo

执行本节步骤前,确认打开正确的命令行运行环境:
  • 在 Windows 系统上:双击 win_env.bat 打开命令行运行环境。

  • 在 Linux 系统上:直接使用 Shell 命令行即可。

  1. 进入 dm-app 根目录:
    cd luban-lite/packages/artinchip/aic-dm-apps
  2. 执行下列命令编译 dm-app 为可执行文件:
    scons --app=hello
  3. 查看目标文件:
    ls hello/hello.mo
  4. 如有需要,执行下列命令可清理目标文件,否则可跳过此步:
    scons --app=hello -c
  5. 将生成的 hello.mo 文件拷贝到单板存储介质的文件系统中:
  6. 在 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

  1. 进入 dm-app 根目录:
    cd luban-lite/packages/artinchip/aic-dm-apps
  2. 执行下列命令编译 dm-app 为可执行文件:
    scons --app=hello
  3. 查看目标文件:
    ls hello/hello.so
  4. 如有需要,执行下列命令可清理目标文件,否则可跳过此步:
    scons --app=hello -c
  5. 在 Kernel 中使能 test_dm_lib 测试命令:
    Drivers options  --->
        Drivers examples  --->
            [*] Enable DM Lib test command
  6. 将生成的 hello.so 拷贝到单板存储介质的文件系统中。

  7. 使用 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 注意事项
    1. RT-Thread API 的调用
      hello.mo 中,可以调用如 rt_thread_creatert_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;
      }
    2. 模块初始化和退出函数
      如果 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");
      }

      用户可以利用该机制来做一些初始化和清理的工作。如果不需要,则无需实现这两个函数。

    3. 查看 hello.mo 创建的子线程

      从代码可知,运行 hello.mo 后会创建 tMyTask 线程,但使用 ps 或者 list_thread 命令无法看到该线程。这是因为该方式下启动的线程会被链接到模块本身的进程链表 module->object_list ,而上述命令只能查看全局链表 information->object_list 中的线程。

      目前模块本身的进程链表 module->object_list 不支持命令查看,在模块退出时会停止掉 module->object_list 中模块启动的所有子进程。

    4. 后台进程保活
      hello.momain() 函数返回后,系统马上会执行模块退出动作,main() 函数创建的所有子进程也会被全部清理。为了让模块的子进程作为后台进程继续运行,可以为 hello.momain() 函数定义一个特殊返回值 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
    1. 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
      }
    2. 查看 hello.so 创建的子线程

      通过 test_dm_lib 命令动态加载 hello.so 并调用 my_thread_init() 函数,同样会会创建 tMyTask 线程。上述示例中,使用 ps 或者 list_thread 命令可以看到该线程,是因为系统通过 dlmodule_self() 判断当前进程非模块执行进程,对应的进程链表就加入到了全局链表 information->object_list 中。

    3. 后台进程保活

      当调用 my_thread_init() 函数返回后,常规情况是执行 dlclose(module) 来清理动态加载的模块。如果 my_thread_init() 函数创建的所有子进程希望作为后台进程运行,则不能调用 dlclose(module) 来卸载动态库。因为一旦调用 dlclose(module),动态库就会被卸载,而此时后台进程可能仍在运行并需要访问动态库中的数据。一旦有新的内存分配就会覆盖原动态模块的数据,触发 CPU 异常。

      为了避免这种情况,可以在不调用 dlclose(module) 的情况下让程序持续运行,或者在程序结束时手动清理动态库占用的资源。