動態加載是一種程序加載技術。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 ***************************** *****************************