TencentOS-tiny/doc/22.ElfLoader_Quick_Start.md

292 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 驱动来进行模块的访问。