开发指导
使用场景
静态链接将程序各模块文件链接成一个整体,运行时一次性加载进内存,具有代码装载速度快等优点。但当程序规模较大,模块变更升级较为频繁时,会存在内存和磁盘空间浪费、模块更新困难等问题。
动态加载技术可以较好地解决上述静态链接中存在的问题,在程序需要执行外部模块中的代码时,动态地将外部模块加载进内存,不需要该模块时再卸载,可以实现公共代码的共享以及模块的平滑升级等功能。
功能
Huawei LiteOS 的动态加载模块为用户提供下面几种功能,接口详细信息可以查看API参考。
功能分类 |
接口名 |
描述 |
---|---|---|
销毁动态加载 |
LOS_LdDestroy |
销毁动态加载模块 |
动态加载模块文件 |
LOS_SoLoad |
动态加载一个so模块(依赖文件系统) |
LOS_MemLoad |
动态加载一个so模块(不依赖文件系统,从指定内存空间加载) |
|
LOS_ObjLoad |
动态加载一个obj模块(依赖文件系统) |
|
查找符号地址 |
LOS_FindSymByName |
在模块或系统符号表中查找符号地址 |
卸载模块 |
LOS_ModuleUnload |
卸载一个模块 |
设置动态加载的搜索路径/参数/内存池地址 |
LOS_PathAdd |
添加模块的搜索路径 |
LOS_DynParamReg |
设置so模块的动态加载参数,使用该接口需要打开LOSCFG_DYNLOAD_DYN_FROM_FS宏开关,即只支持从文件系统中加载so模块时才能设置其动态加载参数 |
|
LOS_DynMemPoolSet |
设置动态加载使用的内存池地址 |
LOS_DynMemPoolSet接口入参必须是经过LOS_MemInit初始化的内存池地址,即通过Huawei LiteOS内存管理算法管理,并且保证该内存池与系统内存池不重合。该接口需在加载.so或者.o文件前使用。
准备编译环境
- 添加.o和.so模块编译选项。
- .o模块的编译选项中需要添加-nostdlib -fno-PIC选项
- .so模块的编译选项中需要添加-nostdlib -fPIC -shared选项
以下列出的编译选项,可以根据实际需要自行选择。
-z max-page-size=value
设置.o和.so模块可加载的program segment的对齐参数为value值,添加该选项,可以有效减少各相邻可加载的segment的虚拟地址之间由于对齐需要而产生的空白区域。如果不添加该选项,默认对齐参数为0x10000。
- IPC的动态加载需要用户保证所提供的模块文件中所有LD_SHT_PROGBITS、LD_SHT_NOBITS类型的section起始地址都是4字节对齐,否则拒绝加载该模块。
- 生成.o和.so模块时禁止链接编译器中的标准库(即不允许使用-ldl,-lpthread,-lc等),且务必使用编译选项 -nostdlib。
.o和.so模块编译选项添加示例如下:
RM = -rm -rf CC = arm-himix100-linux-gcc SRCS = $(wildcard *.c) OBJS = $(patsubst %.c,%.o,$(SRCS)) SOS = $(patsubst %.c,%.so,$(SRCS)) all: $(SOS) $(OBJS): %.o : %.c @$(CC) -nostdlib -c $< -fno-PIC -o $@ $(SOS): %.so : %.c @$(CC) -nostdlib $< -fPIC -shared -o $@ clean: @$(RM) $(SOS) $(OBJS) .PHONY: all clean
- 修改系统的Makefile。
编译系统镜像的Makefile必须include根目录下的config.mk文件,并使用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) $(LITEOS_DYNLDFLAGS) $(OBJCOPY) -O binary $(SAMPLE_OUT)/sample $(SAMPLE_OUT)/sample.bin $(OBJDUMP) -d $(SAMPLE_OUT)/sample >$(SAMPLE_OUT)/sample.asm
- 编译.o和.so模块,并将运行所需的所有.o和.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作为编译参数,否则需要在加载a.so之前事先加载b.so。
- 进入Huawei_LiteOS/tools/scripts/dynload_tools目录执行sym.sh脚本,示例如下:
$ ./sym.sh /home/wmin/customer/out
- sym.sh脚本的入参/home/wmin/customer/out,是.o和.so文件所在的目录的绝对路径。
- 注意如果所需加载的.o或.so被更新了,需要重新执行该脚本。该脚本会提取.o和.so文件中的所有系统符号(非用户定义的符号),以便在编译系统镜像时由编译器计算出对应符号的地址。
- 必须要在Huawei_LiteOS/tools/scripts/dynload_tools目录下执行该脚本。
- 执行完sym.sh脚本后,确保Huawei_LiteOS/kernel/extended/dynload/src目录下生成了los_dynload_gsymbol.c,并且保证该文件在编译阶段参与编译,以及生成的目标文件参与链接。否则,在加载.o和.so过程中会出现符号不能定位的问题。
编写动态加载的业务代码
- 执行make menuconfig命令,进入Kernel ---> Enable Extend Kernel ---> Enable Dynamic Load Feature菜单,完成动态加载模块的配置。
配置项
含义
取值范围
默认值
依赖
LOSCFG_KERNEL_DYNLOAD
动态加载模块的裁剪开关
YES/NO
YES
LOSCFG_KERNEL_EXTKERNEL
LOSCFG_KERNEL_DYNLOAD_DYN
使能so文件加载
YES/NO
YES
LOSCFG_KERNEL_DYNLOAD
LOSCFG_DYNLOAD_DYN_FROM_FS
使能so文件从文件系统加载
YES/NO
YES
LOSCFG_KERNEL_DYNLOAD_DYN
LOSCFG_DYNLOAD_DYN_FROM_MEM
使能so文件从指定内存中加载
YES/NO
YES
LOSCFG_KERNEL_DYNLOAD_DYN
LOSCFG_KERNEL_DYNLOAD_REL
使能obj文件加载
YES/NO
YES
LOSCFG_KERNEL_DYNLOAD
LOSCFG_DYNLOAD_REL_FROM_FS
使能obj文件从文件系统加载
YES/NO
YES
LOSCFG_KERNEL_DYNLOAD_REL
LOSCFG_KERNLE_DYN_HEAPSIZE
配置动态加载使用的堆大小,单位为M
[0, OS_SYS_MEM_SIZE)
2
LOSCFG_KERNEL_NX && LOSCFG_KERNEL_DYNLOAD,且依赖Cortex-A芯片
如果同时使能LOSCFG_KERNEL_NX(数据段不可执行,依赖Cortex-A芯片,其在menuconfig中对应的菜单项为:Kernel ---> Enable Data Sec NX Feature)和动态加载模块,则只支持动态加载.so文件,其他形式的加载可能会有异常,同时需要配置动态加载使用的堆大小(位于系统动态内存池的尾部)。
- 设置动态加载使用的内存池地址(可选)。
调用LOS_DynMemPoolSet接口可以设置动态加载使用的内存池。如果不设置,默认使用系统内存池。
- 设置so文件的动态加载策略。
在不同的应用场景下,so文件可能以ZIP或者NOZIP的形式存放于存储介质中。如果需要从文件系统中加载so文件,由于压缩文件与非压缩文件读写操作的差异性,在初始化动态加载模块之前需要指明具体的加载策略。
DYNLOAD_PARAM_S dynloadParam = {ZIP}; // 设置ZIP或NOZIP策略 LOS_DynParamReg(&dynloadParam); // 设置具体的加载策略
以ZIP格式存储的so文件必须采用ZIP加载策略,而普通的so文件使用上述两种策略都可以加载成功,建议使用NOZIP策略。如果没有设置,默认采用NOZIP加载策略。
- 使用相对路径(可选)。
如果在动态加载模块时想使用相对路径,可以通过LOS_PathAdd接口添加.so和.o文件所在的绝对路径:
ret = LOS_PathAdd("/yaffs/bin/dynload"); if (ret != LOS_OK) { printf("add relative path failed"); return 1; }
添加路径后,调用LOS_SoLoad、LOS_ObjLoad、LOS_MemLoad接口时传入文件名即可,动态加载会在添加的路径下查找指定文件。
- 只有在调用LOS_PathAdd接口添加路径后,才能在调用动态加载模块接口时使用相对路径。
- 可以多次调用LOS_PathAdd接口添加多个相对路径。
- 如果添加的多个路径下有相同文件名的模块,则在加载模块时按照添加的先后依次在所有路径中查找,且只加载第一个查找到的文件。
- 加载用户模块。
动态加载模块支持加载.o和.so模块。
- 使用LOS_ObjLoad接口动态加载obj文件(注意只能从文件系统中加载obj模块):
if ((handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o")) == NULL) { printf("load module ERROR!!!!!!\n"); return 1; }
- 使用LOS_SoLoad接口动态加载so文件:
if ((handle = LOS_SoLoad("/yaffs/bin/dynload/foo.so")) == NULL) { printf("load module ERROR!!!!!!\n"); return 1; }
对于so文件的动态加载:
- 如果模块A需要模块B,即模块A依赖模块B,如果明确指明了A.so依赖B.so(编译A.so时将B.so作为编译参数),那么加载A模块时会自动将B模块也加载进来。如果没有明确指明A模块与B模块的依赖关系,那么在加载A模块之前,必须保证B模块已经被成功加载。
- 对于不支持文件系统的平台,支持直接从指定内存空间动态加载so,可以调用LOS_MemLoad接口实现。
- 使用LOS_ObjLoad接口动态加载obj文件(注意只能从文件系统中加载obj模块):
- 获取用户模块中的符号地址。
- 使用获取到的符号地址。
LOS_FindSymByName返回符号地址(VOID *指针),对该符号地址转换类型后,可以使用该符号。下面针对数据类型符号和函数类型符号举例说明。
- 整数类型符号
现有待加载的test.c,有一全局变量UINT32 g_test = 0,可以通过如下代码获取g_test的地址。
const char *g_pscOsOSSymtblFilePath = "/yaffs/bin/dynload/test.so"; UINT32 * g_testPtr = NULL; INT8 *ptr = (INT8 *)NULL; if ((pOSSymtblHandler = LOS_SoLoad(g_pscOsOSSymtblFilePath)) == NULL) { return LOS_NOK; } if ((ptr = LOS_FindSymByName(pOSSymtblHandler, "g_test")) == NULL) { printf("g_uwTest not found\n"); return LOS_NOK; } g_testPtr = (UINT32 *)ptr; /* 强制类型转换成真实的指针类型 */
- 函数类型符号
foo.c中定义了一个无参的函数test_0和一个有两个参数的函数test_2,编译生成foo.o。
foo.c: int test_0(void) { return 0; } int test_2(int i, int j) { return 0; }
以下代码演示在demo.c中获取foo.o模块中的函数并调用。
demo.c: typedef int (* TST_CASE_FUNC)(); /* 无形参函数指针类型声明 */ typedef int (* TST_CASE_FUNC1)(UINT32); /* 单形参函数指针类型声明 */ typedef int (* TST_CASE_FUNC2)(UINT32, UINT32); /* 双形参函数指针类型声明 */ TST_CASE_FUNC funTestCase0 = NULL; /* 函数指针定义 */ TST_CASE_FUNC2 funTestCase2 = NULL; int ret; handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o"); funTestCase0 = LOS_FindSymByName(handle, "test_0"); if (funTestCase0 == NULL) { printf("can not find the function name\n"); return 1; } ret = funTestCase0(); funTestCase2 = LOS_FindSymByName(NULL, "test_2"); if (funTestCase2 == NULL){ printf("can not find the function name\n"); return 1; } ret = funTestCase2(42, 57);
- 整数类型符号
- 卸载模块。
调用LOS_ModuleUnload接口卸载某个模块,将需要卸载的模块句柄作为参数传入该接口。对于已被加载过的obj或so文件的句柄,卸载时统一使用LOS_ModuleUnload接口。
ret = LOS_ModuleUnload(handle); if (ret != LOS_OK) { printf("unload module failed"); return 1; }
- 销毁动态加载模块。
不再需要动态加载功能时,调用LOS_LdDestroy接口,销毁动态加载模块。
- 销毁动态加载模块时会自动卸载所有已被加载的模块,销毁后模块不能再使用。
- 销毁动态加载模块前需确认模块不再使用。
编译系统镜像
在Huawei LiteOS源码根目录下执行make,编译系统镜像。
如果待加载的.o和.so中包含了未定义的外部符号(既没有定义在这些.o和.so文件中,也不是一个合法的系统全局符号),在编译系统镜像文件时会提示相应错误,需排查错误信息,确保系统镜像编译正确。
准备系统环境
.so文件(或.o文件)需要和系统镜像文件配合使用。
- 如果选择从文件系统加载模块,则模块文件必须放置在文件系统中,例如jffs2、yaffs、fat等文件系统。
- 如果选择从指定内存空间加载(只能加载.so文件),则忽略下文的步骤2,只需要将模块文件烧写到指定内存空间即可。
建议操作顺序:
- 烧写系统镜像文件到开发板的flash中。
- 将.so文件(或.o文件)存储到开发板的flash中,这里分为两种情况:
- 如果模块文件保存在可热拔插的SD卡设备上,直接将SD卡插到开发板上即可。如果需要更新.so文件(或.o文件),可将SD卡插到电脑上更新。
- 如果模块文件保存在不可热插拔的存储设备上,可通过如下两种方式更新文件:
- 将模块文件编译进文件系统镜像,然后烧写文件系统镜像到开发板的flash中。
- 启动Huawei LiteOS系统后,通过tftp命令下载.so文件(或.o文件),示例命令如下:
tftp -g -l /yaffs/bin/dynload/foo.so -r foo.so 10.67.211.235
- 启动系统进行验证。
Shell调试
在Shell里封装了一系列与动态加载有关的命令,方便用户进行调试。具体的Shell命令详细说明参见动态加载命令参考。