Create 22.ElfLoader_Quick_Start.md
This commit is contained in:
291
doc/22.ElfLoader_Quick_Start.md
Normal file
291
doc/22.ElfLoader_Quick_Start.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# 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可以不用拘泥于文件系统的fd,fd只是一个文件的抽象。
|
||||||
|
|
||||||
|
- 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驱动来进行模块的访问。
|
Reference in New Issue
Block a user