动态加载是一种程序加载技术。linux
静态连接是在连接阶段将程序各模块文件连接成一个完整的可执行文件,运行时做为总体一次性加载进内存。动态加载容许用户将程序各模块编译成独立的文件而不将它们连接起来,在须要使用到模块时再动态地将其加载到内存中。shell
静态连接将程序各模块文件连接成一个总体,运行时一次性加载入内存,具备代码装载速度快等优势。但当程序的规模较大,模块的变动升级较为频繁时,会存在内存和磁盘空间浪费、模块更新困难等问题。编程
动态加载技术能够较好地解决上述静态连接中存在的问题,在程序须要执行所依赖的模块中的代码时,动态地将外部模块加载连接到内存中,不须要该模块时能够卸载,能够提供公共代码的共享以及模块的平滑升级等功能。Huawei LiteOS提供支持OBJ目标文件和SO共享目标文件的动态加载机制。数组
Huawei LiteOS的动态加载功能须要SO共享目标文件(或OBJ目标文件)、基础符号表elf_symbol.so、系统镜像bin文件配合使用。函数
符号表ui
符号表在表现形式上是记录了符号名及其所在内存地址信息的数组,符号表在动态加载模块初始化时被载入到动态加载模块的符号管理结构中。在加载用户模块进行符号重定位时,动态加载模块经过查找符号管理结构获得相应符号所在地址,对相应重定位项进行重定位。spa
接口名 | 描述 |
---|---|
LOS_LdInit | 初始化动态加载模块 |
LOS_LdDestroy | 销毁动态加载模块 |
LOS_SoLoad | 动态加载一个so模块 |
LOS_ObjLoad | 动态加载一个obj模块 |
LOS_FindSymByName | 在模块或系统符号表中查找符号地址 |
LOS_ModuleUnload | 卸载一个模块 |
LOS_PathAdd | 添加一个相对路径 |
动态加载主要有如下几个步骤:指针
步骤1 添加.o和.so模块编译选项调试
IPC的动态加载须要用户保证所提供的模块文件中全部LD_SHT_PROGBITS、LD_SHT_NOBITS类型节区起始地址都4字节对齐,不然拒绝加载该模块code
.o和.so模块编译选项添加示例以下:
RM = -rm -rf CC = arm-hisiv500-linux-gcc SRCS = $(wildcard *.c) OBJS = $(patsubst %.c,%.o,$(SRCS)) SOS = $(patsubst %.c,%.so,$(SRCS)) all: $(SOS) $(OBJS): %.o : %.c @$(CC) -mlong-calls -nostdlib -c $< -o $@ $(SOS): %.so : %.c @$(CC) -mlong-calls -nostdlib $< -fPIC -shared -o $@ clean: @$(RM) $(SOS) $(OBJS) .PHONY: all clean
步骤2 系统镜像
系统镜像bin文件编译makefile必须include根目录下config.mk文件,并使用其中的LITEOS_CFLAGS或LITEOS_CXXFLAGS编译选项,示例以下:
LITEOSTOPDIR ?= ../.. SAMPLE_OUT = . include $(LITEOSTOPDIR)/config.mk RM = -rm -rf LITEOS_LIBDEPS := --start-group $(LITEOS_LIBDEP) --end-group SRCS = $(wildcard sample.c) OBJS = $(patsubst %.c,$(SAMPLE_OUT)/%.o,$(SRCS)) all: $(OBJS) clean: @$(RM) *.o sample *.bin *.map *.asm $(OBJS): $(SAMPLE_OUT)/%.o : %.c $(CC) $(LITEOS_CFLAGS) -c $< -o $@ $(LD) $(LITEOS_LDFLAGS) -uinit_jffspar_param --gc-sections -Map=$(SAMPLE_OUT)/sample.map -o $ (SAMPLE_OUT)/sample ./$@ $(LITEOS_LIBDEPS) $(LITEOS_TABLES_LDFLAGS) $(OBJCOPY) -O binary $(SAMPLE_OUT)/sample $(SAMPLE_OUT)/sample.bin $(OBJDUMP) -d $(SAMPLE_OUT)/sample >$(SAMPLE_OUT)/sample.asm
请严格按以下步骤进行编译。
步骤1 编译.o和.so模块,并将系统运行所需的全部.o和.so文件拷贝到一个目录下,例如
/home/wmin/customer/out/so
说明
- 若是a.so须要调用b.so中的函数,或者a.so引用到了b.so中的数据,则称a.so依赖b.so。
- 当用户中a.so模块依赖b.so,且须要在加载a.so时自动将b.so也加载进来,则在编译a.so时须要将b.so做为编译参数。
步骤2 进入Huawei_LiteOS/tools/scripts/dynload_tools目录执行以下脚本命令
$ ./ldsym.sh /home/wmin/customer/out/so
步骤3 编译系统镜像bin文件, 同时生成镜像文件,例如该目录在/home/wmin/customer/out/bin/,编译后在该目录下生成了sample镜像文件和用于烧写flash的sample.bin文件。
步骤4 进入Huaw_LiteOS/tools/scripts/dynload_tools目录执行sym.sh脚本获得基础符号表elf_symbol.so文件,示例以下:
$ ./sym.sh /home/wmin/customer/out/so arm-hisiv500-linux- /home/wmin/customer/out/bin/vs_server
- sym.sh的三个参数分别是:系统运行所需的全部.o和.so文件所在的那个目录绝对路径,编译
器类型,系统镜像文件(不是烧写flash用的bin文件)。- 全部参数中路径都必须是绝对路径。
- 第三个参数必须是系统镜像文件,不是烧写flash用的bin文件。
- 基础符号表elf_symbol.so文件生成在系统镜像文件同一路径下。
- 注意每次系统镜像的从新编译,都要将基础符号表elf_symbol.so从新生成并更新。
- 必需要在Huawei_LiteOS/tools/scripts/dynload_tools目录下执行该命令。
步骤5 进入Huawi_LiteOS/tools/scripts/dynload_tools目录执行failed2reloc.py脚本获得用户模块中没法完成重定位的符号:
$ ./failed2reloc.py /home/wmin/customer/out/bin/vs_server /home/wmin/customer/out/so
- failed2reloc.py的两个参数分别是:系统镜像文件(不是烧写flash用的bin文件),系统运行所
需的全部.o和.so文件所在的那个目录绝对路径。- 全部参数中路径都必须是绝对路径。
- 第一个参数必须是系统镜像文件,不是烧写flash用的bin文件。
- 该脚本输出用户模块中没法完成重定位的符号信息。
步骤1 初始化动态加载模块
if (LOS_OK != LOS_LdInit("/yaffs/bin/dynload/elf_symbol.so", NULL, 0)) { printf("ld_init failed!!!!!!\n"); return 1; }
LOS_LdInit函数第一个参数是基础符号表文件路径,第二个参数是动态加载模块进行内存分配的堆起始地址,第三个参数是这块做为堆使用的内存的长度,在LOS_LdInit接口中会将这段内存初始化为堆;若是用户但愿动态加载模块从系统堆上分配内存,第二个参数传入NULL,第三个参数被忽略。
- 动态加载模块的初始化只须要在业务启动时调用一次便可,重复初始化动态加载模块会返回失败。
上面这段代码演示使用系统堆,以下代码演示自定义堆:
#define HEAP_SIZE0x8000 INT8 usrHeap[HEAP_SIZE]; if (LOS_OK != LOS_LdInit("/yaffs/bin/dynload/elf_symbol.so, usrHeap,sizeof(usrHeap)")) { printf("ld_init failed!!!!!!\n") return 1; }
- 用户不须要对自定义的这块内存进行初始化动做。
- 动态加载所需分配的堆内存大小视要加载的模块而定,所以若是用户须要指定自定义堆时,须要保证堆长度足够大,不然建议使用系统堆。
步骤2 加载用户模块
if ((handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o")) == NULL){ printf("load module ERROR!!!!!!\n"); return 1; }
if ((handle = LOS_SoLoad("/yaffs/bin/dynload/foo.so")) == NULL){ printf("load module ERROR!!!!!!\n"); return 1; }
对于so文件的动态加载,若是一个模块A须要另外一个模块B,也就是存在模块A依赖于模块B的关系,则在加载A模块时会将模块B也加载进来。
步骤3 获取用户模块中的符号地址
须要在某个特定模块中查找用户模块的符号地址时,调用LOS_FindSymByName接口,并将LOS_FindSymByName的第一个参数置为须要查找的用户模块的句柄。
if ((ptr_magic = LOS_FindSymByName(handle, "os_symbol_table")) == NULL) { printf("symbol not found\n"); return 1; }
if ((pFunTestCase0 = LOS_FindSymByName(NULL, "printf")) == NULL) { printf("symbol not found\n"); return 1; }
步骤4 使用获取到的符号地址: LOS_FindSymByName返回一个符号的地址(VOID *指针),用户在拿到这个指针以后能够作相应的类型转换来使用该符号,下面举两个例子说明,一个针对数据类型符号,一个针对函数类型符号。
typedef struct KERNEL_SYMBOL { UINT32 uwAddr; INT8 *pscName; } KERNEL_SYMBOL;
"/bin/dynload/elf_symbol.so"模块中定义告终构体数组:
KERNEL_SYMBOL los_elf_symbol_table[LENGTH_ARRAY] = { {0x83040000, "__exception_handlers"}, {0x8313b864, "cmd_version"}, … {0, 0}, };
经过以下代码遍历los_elf_symbol_table中的各项:
const char *g_pscOsOSSymtblFilePath = "/yaffs/bin/dynload/elf_symbol.so"; const char *g_pscOsSymtblInnerArrayName = "los_elf_symbol_table"; typedef KERNEL_SYMBOL (*OS_SYMTBL_ARRAY_PTR)[LENGTH_ARRAY];/* 结构体数组指针类型声明 */ OS_SYMTBL_ARRAY_PTR pstSymtblPtr = (OS_SYMTBL_ARRAY_PTR)NULL; VOID *pPtr = (VOID *)NULL; UINT32 uwIdx = 0; UINT32 uwAddr; INT8 *pscName = (INT8 *)NULL; if ((pOSSymtblHandler = LOS_SoLoad(g_pscOsOSSymtblFilePath)) == NULL) { return LOS_NOK; } if ((pPtr = LOS_FindSymByName(pOSSymtblHandler, g_pscOsSymtblInnerArrayName)) == NULL) { printf("os_symtbl not found\n"); return LOS_NOK; } pstSymtblPtr = (OS_SYMTBL_ARRAY_PTR)pPtr;/* 强制类型转换成真实的指针类型 */ uwAddr = (*pstSymtblPtr)[0].uwAddr; pscName = (*pstSymtblPtr)[0].pscName; while (uwAddr != 0 && pscName != 0) { ++uwIdx; uwAddr= (*pstSymtblPtr)[uwIdx].uwAddr; pscName= (*pstSymtblPtr)[uwIdx].pscName; }
foo.c: int test_0(void) { return 0; } int test_2(int i, int j) { return 0; } demo.c typedef unsigned int (* TST_CASE_FUNC)();/* 无形参函数指针类型声明 */ typedef unsigned int (* TST_CASE_FUNC1)(UINT32); /* 单形参函数指针类型声明 */ typedef unsigned int (* TST_CASE_FUNC2)(UINT32, UINT32); /* 双形参函数指针类型声明 */ TST_CASE_FUNC pFunTestCase0 = NULL;/* 函数指针定义 */ TST_CASE_FUNC2 pFunTestCase2 = NULL; handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o"); pFunTestCase0 = NULL; pFunTestCase0 = LOS_FindSymByName(handle, "test_0"); if (pFunTestCase0 == NULL){ printf("can not find the function name\n"); return 1; } uwRet = pFunTestCase0(); pFunTestCase2 = NULL; pFunTestCase2 = LOS_FindSymByName(NULL, "test_2"); if (pFunTestCase2 == NULL){ printf("can not find the function name\n"); return 1; } uwRet = pFunTestCase2(42, 57);
步骤5 卸载模块
当要卸载一个模块时,调用LOS_ModuleUnload接口,将须要卸载的模块句柄做为参数传入该接口。对于已被加载过的obj或so文件的句柄,卸载时统一使用LOS_ModuleUnload接口。
uwRet = LOS_ModuleUnload(handle); if (uwRet != LOS_OK) { printf("unload module failed"); return 1; }
步骤6 销毁动态加载模块
再也不须要动态加载功能时,调用LOS_LdDestroy接口,卸载动态加载模块。
销毁动态加载模块时会自动卸载掉全部已被加载的模块。
uwRet = LOS_LdDestroy(); if (uwRet != LOS_OK) { printf("destroy dynamic loader failed"); return 1; }
在业务再也不须要动态加载模块时销毁动态加载模块,该接口是与LOS_LdInit配对的接口。在销毁动态加载模块后,若是业务后续再须要动态加载必须再调用LOS_LdInit从新初始化动态加载模块。
步骤7 使用相对路径
用户在使用动态加载接口时,若是想使用相对路径,也即便用相似环境变量的机制时,须要经过LOS_PathAdd接口添加相对路径:
uwRet = LOS_PathAdd("/yaffs/bin/dynload"); if (uwRet != LOS_OK) { printf("add relative path failed"); return 1; }
添加相对路径后,用户在调用LOS_LdInit、 LOS_SoLoad、 LOS_ObjLoad接口时传入文件名便可,而无需再传入完整的绝对路径,动态加载会在用户添加的相对路径下查找相应模块。
若是用户传入的多个路径下有相同文件名的模块,则动态加载在加载模块时按照添加的前后依次在全部相对路径中查找,且只加载第一个查找到的文件。
- 只有在调用LOS_PathAdd接口添加相对路径后,才能在调用动态加载接口时使用相对路径。
- 用户能够经过屡次调用LOS_PathAdd接口添加多个相对路径
SO共享目标文件(或OBJ目标文件)、基础符号表elf_symbol.so、系统镜像bin文件配合使用。
其中SO共享目标文件(或OBJ目标文件)、基础符号表elf_symbol.so必须放置在文件系统中,例如jffs二、 yaffs、 fat等文件系统。
建议操做顺序:
tftp -g -l /yaffs0/foo.so -r foo.so 10.67.211.235 tftp -g -l / yaffs0/elf_symbol.so -r elf_symbol.so 10.67.211.235
在Shell里咱们封装了一系列与动态加载有关的命令,方便用户进行调试。
具体的Shell命令详细说明参见命令参考。
当用户须要在Shell中调试动态加载特性的时候,须要首先初始化动态加载模块。
Shell命令: ldinit
Huawei LiteOS# ldinit /yaffs/bin/dynload/elf_symbol.so Huawei LiteOS#
动态加载过程当中发现符号重定义只做为一个warning而不做为error处理,因此仅反馈符号重定义而不返回其余错误信息表示动态加载模块初始化成功.
Shell命令: mopen
Huawei LiteOS# mopen /yaffs/bin/dynload/foo.o module handle: 0x80391928 Huawei LiteOS#
(1)模块路径必需要用绝对路径
(2)必需要先初始化动态加载模块再加载模块。
Huawei LiteOS# findsym 0 printf symbol address:0x8004500c Huawei LiteOS# Huawei LiteOS# findsym 0x80391928 test_0 symbol address:0x8030f241 Huawei LiteOS#
Huawei LiteOS# call 0x8030f241 test_0 Huawei LiteOS#
Shell命令: mclose
Huawei LiteOS# mclose 0x80391928 Huawei LiteOS#
foo.c: int test_0(void) { printf("test_0\n"); return 0; } int test_2(int i, int j) { printf("test_2: %d %d\n", i, j); return 0; } demo.c: typedef int (* TST_CASE_FUNC)(); /* 无形参函数指针类型声明 */ typedef int (* TST_CASE_FUNC1)(UINT32); /* 单形参函数指针类型声明 */ typedef int (* TST_CASE_FUNC2)(UINT32, UINT32); /* 双形参函数指针类型声明 */ if (LOS_OK != LOS_LdInit("/yaffs/bin/dynload/elf_symbol.so", NULL, 0)) { printf("ld_init failed!!!!!!\n"); return 1; } unsigned int uwRet; TST_CASE_FUNC pFunTestCase0 = NULL;/* 函数指针定义 */ TST_CASE_FUNC2 pFunTestCase2 = NULL; handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o"); pFunTestCase0 = NULL; pFunTestCase0 = LOS_FindSymByName(handle, "test_0"); if (pFunTestCase0 == NULL){ printf("can not find the function name\n"); return 1; } uwRet = pFunTestCase0(); /* 调用该函数指针 */ pFunTestCase2 = NULL; pFunTestCase2 = LOS_FindSymByName(NULL, "test_2"); if (pFunTestCase2 == NULL){ printf("can not find the function name\n"); return 1; } uwRet = pFunTestCase2(42, 57); /* 调用该函数指针 */ uwRet = LOS_ModuleUnload(handle); if (uwRet != LOS_OK) { printf("unload module failed"); return 1; } uwRet = LOS_LD_Destroy(); if (uwRet != LOS_OK) { printf("destroy dynamic loader failed"); return 1; }
编译运行获得的结果为:
Huawei LiteOS# ***************************** ***************************** test_0 test_2:42 57 ***************************** *****************************