TencentOS-tiny/doc/22.ElfLoader_Quick_Start.md

8.9 KiB
Raw Permalink Blame History

TencentOS tiny elfLoader 组件

1. 什么是 elfLoader

elfLoader 是 TencentOS tiny 提供的对 elf 格式文件进行加载并执行的组件TencentOS tiny 目前的 elfLoader 组件提供目标文件object及共享目标文件shared object的动态加载支持。

2. elfLoader 组件的基本使用范式

对于 elfLoader 来说,使用的基本范式流程为:

  • 将源代码编译成 object 或 shared-object 文件。
  • 将 object 或 shared-object 文件拷贝到开发板可以访问的文件系统中。
  • 调用 elfLoader 组件提供的 tos_elfloader_load 接口加载文件系统中的 object 或 shared-object 文件。
  • 调用 elfLoader 组件提供的 tos_elfloader_find_symbol 查找已加载的模块中某个符号的加载地址(一般来说是一个函数),执行之。
  • 如果后续业务不再需要这个模块,执行 tos_elfloader_unload 将模块卸载。

3. elfLoader 组件的具体使用实例object

3.1 编写一个模块的源码

 extern int d_e_a;

 int d_g_a = 3;

 static int d_s_a = 5;

 extern int f_e_a(int);

 static int f_s_a(void) {
     d_s_a += 7; // d_s_a = 12
 }

 int f_g_a(void) {
     f_s_a();

     d_g_a += d_s_a; // d_g_a = 15

     d_e_a += d_g_a; // + 15

     f_e_a(d_e_a);
 }

源码 1.c 如上,改模块中:

  • 引用了一个外部的符号 d_e_a变量这是一个应该在模块外定义的符号
  • 定义了一个全局符号 d_g_a变量
  • 定义了一个模块内的静态符号 d_s_a变量
  • 引用了一个外部的符号 f_e_a函数这是一个应该在模块外定义的符号
  • 定义了一个模块内的静态符号 f_s_a函数
  • 定义了一个全局符号 f_g_a函数

3.2 将模块编译成 object 文件

arm-linux-gnueabihf-gcc -fno-builtin -nostdlib -mthumb -mthumb-interwork -mcpu=cortex-m4 -c 1.c -o 1.o

我这里假设读者已经在本地安装好 arm 交叉编译器,且本实例是基于 TencentOS tiny 官方开发板mcu 是 arm cortex-m4 核心),按照以上编译选项将 1.c 编译为 1.o

3.3 将模块拷贝至文件系统

将模块1.o拷贝至已进行 FAT32 格式化后的 sd 卡,将 sd 卡插入 TencentOS tiny 官方开发板上的 sd 卡槽。

3.4 运行示例工程

TencentOS-tiny\board\TencentOS_tiny_EVB_MX_Plus\KEIL\elfloader_relocatable_object

用 keil 打开以上路径中的示例工程:

  • 添加 d_e_a 及 f_e_a 实现

    因为 1.o 依赖此两个外部符号,因而需要在内核中定义这两个符号

int d_e_a = 9;

int f_e_a(int a)
{
    /* a = d_e_a + d_g_a = d_e_a + 15 = 24 */
    printf("f_e_a:  %d\n", a);
    return 0;
}
  • 添加系统符号表
const el_symbol_t el_symbols[] = {
    { "d_e_a", &d_e_a },
    { "f_e_a", f_e_a },
    { K_NULL, K_NULL },
};

按照以上格式,定义 el_symbols 符号表,符号表中包含 1.o 中依赖的两个外部符号地址。

  • 编写 elfLoader 使用案例代码
void application_entry(void *arg)
{
    int fd;
    el_module_t module;

    extern vfs_blkdev_ops_t sd_dev;
    extern vfs_fs_ops_t fatfs_ops;

    if (tos_vfs_block_device_register("/dev/sd", &sd_dev) != VFS_ERR_NONE) {
        return;
    }

    if (tos_vfs_fs_register("fatfs_sd", &fatfs_ops) != VFS_ERR_NONE) {
        return;
    }

    if (tos_vfs_fs_mount("/dev/sd", "/fs/fatfs_sd", "fatfs_sd") != VFS_ERR_NONE) {
        printf("mount failed!\n");
        return;
    }

    fd = tos_vfs_open("/fs/fatfs_sd/1.o", VFS_OFLAG_READ | VFS_OFLAG_EXISTING);
    if (fd < 0) {
        return;
    }

    if (tos_elfloader_load(&module, fd) != ELFLOADER_ERR_NONE) {
        return;
    }

    void *addr = tos_elfloader_find_symbol(&module, "f_g_a");
    if (!addr) {
        printf("symbol NOT FOUND: %s\n", "f_g_a");
        return;
    }

    printf("addr: %x\n", addr);

    typedef int (*fp_t)(void);
    /* call f_g_a in 1.o */
    ((fp_t)addr)();

    tos_elfloader_unload(&module);

    tos_vfs_close(fd);
}

以上代码:使用 TencentOS tiny 的 vfs 组件挂载 sd 卡上的 FAT32 文件系统,并打开文件系统上的 1.o 获取文件描述符 fd将 fd 作为 tos_elfloader_load 的入参,加载这个模块并获取模块的句柄 module使用 tos_elfloader_find_symbol 接口在 module 中查找 f_g_a 符号(函数)的加载地址,并执行之。

根据 1.c 中 f_g_a 的逻辑,最终会调用 f_e_a 将 d_e_a 最终的值打印出来根据运算逻辑d_e_a 的最终值应该为 24。

  • 调用 tos_elfloader_unload 卸载该模块,调用 tos_vfs_close 接口关闭文件描述符 fd。

3.5 注意

对于一般的 mcu 来说,可执行代码位于 FLASH 上,而模块的可执行代码及数据皆被加载至 RAM 上。而一般来说 FLASH 和 RAM 之间的地址相隔甚远,这会导致加载 object 文件的重定位阶段,某些重定位类型无法顺利完成重定位操作,进而导致 object 文件加载失败。

4. elfLoader 组件的具体使用实例shared-object

共享目标文件 (shared-object) 的装载及运行与 object 文件类似

4.1 编写一个模块的源码

参考 3.1 节

4.2 将模块编译成 shared-object 文件

arm-linux-gnueabihf-gcc -fno-builtin -nostdlib -mthumb -mthumb-interwork -fPIC -mcpu=cortex-m4 -c 1.c -o 1.o

arm-linux-gnueabihf-ld -fno-builtin -nostdlib -fPIC -shared -z max-page-size=0x4 1.o -o 1.so

我这里假设读者已经在本地安装好 arm 交叉编译器,且本实例是基于 TencentOS tiny 官方开发板mcu 是 arm cortex-m4 核心),按照以上编译选项将 1.c 编译为 1.so

4.3 将模块拷贝至文件系统

参考 3.3 节

4.4 运行示例代码

TencentOS-tiny\board\TencentOS_tiny_EVB_MX_Plus\KEIL\elfloader_shared_object

用 keil 打开以上路径中的示例工程,工程中的具体代码与 object 工程类似,参考 3.4 节,不再赘述。

5. elfLoader 组件的文件访问扩展

elfLoader 的组件实现,对于模块文件的访问,是通过 elfloader_fd_read 接口来进行的:

__KNL__ __WEAK__ el_err_t elfloader_fd_read(int fd, uint32_t offset, void *buf, size_t len)
{
    if (tos_vfs_lseek(fd, (vfs_off_t)offset, VFS_SEEK_SET) < 0) {
        return ELFLOADER_ERR_FD_READ_FAILED;
    }

    if (tos_vfs_read(fd, buf, len) < 0) {
        return ELFLOADER_ERR_FD_READ_FAILED;
    }

    return ELFLOADER_ERR_NONE;
}

此接口接收四个参数:

  • fd

文件的描述符 fd。此 fd 可以不用拘泥于文件系统的 fdfd 只是一个文件的抽象。

  • offset

对文件的 offset 偏移处进行读取。

  • buf

文件读取的数据缓冲

  • len

文件读取的长度

elfloader_fd_read 的实现位于 tos_elfloader_fd_read-vfs.c 中,默认实现是基于 TencentOS tiny 的 vfs 接口进行文件访问。

现在假设有这样的场景:用户在使用 elfLoader 时,不希望通过 vfs 框架来进行文件访问(用户的场景可能根本就没有文件系统,或者压根不希望通过文件系统来访问模块),比如用户将编译好的模块烧写进裸 FLASH 的固定偏移处,假设有这种场景:

uint32_t offset_of_modules[] = {
	0x8000 0000,
	0x8000 2000,
	0x8000 4000,
};

用户分别在 FLASH 的 0x8000 0000、0x8000 2000、0x8000 4000 偏移处烧写了三个 shared-object 文件,用户如何不通过文件系统框架来实现模块的加载运行呢?

业务代码中,希望通过 fd 为 0 时,加载 0x8000 0000 处的模块,通过 fd 为 1 时,加载 0x8000 2000 处的模块,通过 fd 为 2 时,加载 0x8000 4000 处的模块。代码如下:

void application_entry(void *arg)
{
    el_module_t module0, module1, module2;

    if (tos_elfloader_load(&module0, 0) != ELFLOADER_ERR_NONE) {
        return;
    }

    void *addr = tos_elfloader_find_symbol(&module0, "f_g_a");
    if (!addr) {
        printf("symbol NOT FOUND: %s\n", "f_g_a");
        return;
    }

    if (tos_elfloader_load(&module1, 1) != ELFLOADER_ERR_NONE) {
        return;
    }

    if (tos_elfloader_load(&module2, 2) != ELFLOADER_ERR_NONE) {
        return;
    }

    tos_elfloader_unload(&module0);
    tos_elfloader_unload(&module1);
    tos_elfloader_unload(&module2);
}

业务代码里并不需要依赖文件系统fd 本质上只是只是一种抽象,它可以与文件系统无关。

现在假设用户已经实现了 FLASH 的读驱动接口 flash_read要做到上述诉求只需要重新实现 elfloader_fd_read 即可:

el_err_t elfloader_fd_read(int fd, uint32_t offset, void *buf, size_t len)
{
    uint32_t offset = offset_of_modules[fd];

	if (flash_read(offset, buf, len) < 0) {
		return ELFLOADER_ERR_FD_READ_FAILED;
	}

    return ELFLOADER_ERR_NONE;
}

可以看出来TencentOS tiny 的组件实现,依赖一定程度的抽象,但此抽象又并不与某个特定框架强耦合。对于 elfLoader 组件来说,用户即可以基于文件系统实现对模块的访问,经过少量的适配工作,也完全可以基于裸 FLASH 驱动来进行模块的访问。