liteos動態加載(十三)

1. 概述

1.1 基本概念

動態加載是一種程序加載技術。linux

靜態連接是在連接階段將程序各模塊文件連接成一個完整的可執行文件,運行時做爲總體一次性加載進內存。動態加載容許用戶將程序各模塊編譯成獨立的文件而不將它們連接起來,在須要使用到模塊時再動態地將其加載到內存中。shell

靜態連接將程序各模塊文件連接成一個總體,運行時一次性加載入內存,具備代碼裝載速度快等優勢。但當程序的規模較大,模塊的變動升級較爲頻繁時,會存在內存和磁盤空間浪費、模塊更新困難等問題。編程

動態加載技術能夠較好地解決上述靜態連接中存在的問題,在程序須要執行所依賴的模塊中的代碼時,動態地將外部模塊加載連接到內存中,不須要該模塊時能夠卸載,能夠提供公共代碼的共享以及模塊的平滑升級等功能。Huawei LiteOS提供支持OBJ目標文件和SO共享目標文件的動態加載機制。數組

Huawei LiteOS的動態加載功能須要SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so、系統鏡像bin文件配合使用。函數

1.2 動態加載相關概念

符號表ui

符號表在表現形式上是記錄了符號名及其所在內存地址信息的數組,符號表在動態加載模塊初始化時被載入到動態加載模塊的符號管理結構中。在加載用戶模塊進行符號重定位時,動態加載模塊經過查找符號管理結構獲得相應符號所在地址,對相應重定位項進行重定位。spa

2. 開發指導

接口名 描述
LOS_LdInit 初始化動態加載模塊
LOS_LdDestroy 銷燬動態加載模塊
LOS_SoLoad 動態加載一個so模塊
LOS_ObjLoad 動態加載一個obj模塊
LOS_FindSymByName 在模塊或系統符號表中查找符號地址
LOS_ModuleUnload 卸載一個模塊
LOS_PathAdd 添加一個相對路徑

2.1 開發流程

動態加載主要有如下幾個步驟:指針

  1. 編譯環境準備
  2. 基礎符號表elf_symbol.so的獲取及鏡像編譯
  3. 動態加載接口使用
  4. 系統環境準備

2.2 編譯環境準備

步驟1 添加.o和.so模塊編譯選項調試

  • .o模塊的編譯選項中須要添加-mlong-calls -nostdlib選項
  • .so模塊的編譯選項中須要添加-nostdlib -fPIC -shared選項

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

2.3 基礎符號表 elf_symbol.so 的獲取及鏡像編譯

請嚴格按以下步驟進行編譯。

步驟1 編譯.o和.so模塊,並將系統運行所需的全部.o和.so文件拷貝到一個目錄下,例如

/home/wmin/customer/out/so

說明

  1. 若是a.so須要調用b.so中的函數,或者a.so引用到了b.so中的數據,則稱a.so依賴b.so。
  2. 當用戶中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
  1. 「$」是linux shell提示符,下同
  2. ldsym.sh腳本只需傳入系統運行所需的全部.o和.so文件所在的那個目錄絕對路徑便可。
  3. 目錄路徑必須是絕對路徑。
  4. 必需要在Huawei_LiteOS/tools/scripts/dynload_tools目錄下執行該命令

步驟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
  1. sym.sh的三個參數分別是:系統運行所需的全部.o和.so文件所在的那個目錄絕對路徑,編譯
    器類型,系統鏡像文件(不是燒寫flash用的bin文件)。
  2. 全部參數中路徑都必須是絕對路徑。
  3. 第三個參數必須是系統鏡像文件,不是燒寫flash用的bin文件。
  4. 基礎符號表elf_symbol.so文件生成在系統鏡像文件同一路徑下。
  5. 注意每次系統鏡像的從新編譯,都要將基礎符號表elf_symbol.so從新生成並更新。
  6. 必需要在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
  1. failed2reloc.py的兩個參數分別是:系統鏡像文件(不是燒寫flash用的bin文件),系統運行所
    需的全部.o和.so文件所在的那個目錄絕對路徑。
  2. 全部參數中路徑都必須是絕對路徑。
  3. 第一個參數必須是系統鏡像文件,不是燒寫flash用的bin文件。
  4. 該腳本輸出用戶模塊中沒法完成重定位的符號信息。

2.4 動態加載接口

步驟1 初始化動態加載模塊

  • 在使用動態加載特性前,須要調用LOS_LdInit接口初始化動態加載模塊:
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,第三個參數被忽略。

  1. 動態加載模塊的初始化只須要在業務啓動時調用一次便可,重複初始化動態加載模塊會返回失敗。

上面這段代碼演示使用系統堆,以下代碼演示自定義堆:

#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;
}
  1. 用戶不須要對自定義的這塊內存進行初始化動做。
  2. 動態加載所需分配的堆內存大小視要加載的模塊而定,所以若是用戶須要指定自定義堆時,須要保證堆長度足夠大,不然建議使用系統堆。

步驟2 加載用戶模塊

  • IPC的動態加載模塊支持對.o以及.so模塊的動態加載,對於obj文件的動態加載使用LOS_ObjLoad接口:
if ((handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o")) == NULL){
    printf("load module ERROR!!!!!!\n");
    return 1;
}
  • 對於so文件的動態加載使用LOS_SoLoad接口:
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;
}
  • 在全局符號表中查找符號
    須要在全局符號表(即OS模塊,包括本模塊和全部其餘用戶模塊)中查找某個符號的地址時,調用LOS_FindSymByName接口,並將LOS_FindSymByName的第一個參數置NULL。
if ((pFunTestCase0 = LOS_FindSymByName(NULL, "printf")) == NULL) {
    printf("symbol not found\n");
    return 1;
}

步驟4 使用獲取到的符號地址: LOS_FindSymByName返回一個符號的地址(VOID *指針),用戶在拿到這個指針以後能夠作相應的類型轉換來使用該符號,下面舉兩個例子說明,一個針對數據類型符號,一個針對函數類型符號。

  • 結構體數組類型符號(演示說明數據類型的符號使用)現有結構體KERNEL_SYMBOL定義以下:
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中定義了一個無參的函數test_0和一個有兩個參數的函數test_2,編譯生成foo.o,代碼演示在demo.c中獲取foo.o模塊中的函數並調用。
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接口時傳入文件名便可,而無需再傳入完整的絕對路徑,動態加載會在用戶添加的相對路徑下查找相應模塊。

若是用戶傳入的多個路徑下有相同文件名的模塊,則動態加載在加載模塊時按照添加的前後依次在全部相對路徑中查找,且只加載第一個查找到的文件。

  1. 只有在調用LOS_PathAdd接口添加相對路徑後,才能在調用動態加載接口時使用相對路徑。
  2. 用戶能夠經過屢次調用LOS_PathAdd接口添加多個相對路徑

2.5 系統環境準備

SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so、系統鏡像bin文件配合使用。

其中SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so必須放置在文件系統中,例如jffs二、 yaffs、 fat等文件系統。

建議操做順序:

  1. 燒寫系統鏡像bin文件到flash中,該鏡像默認不啓動動態加載功能
  2. 若是SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so路徑爲可熱拔插的SD卡設備,則可在電腦更新SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so到SD卡指定路徑
  3. 若是SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so路徑爲jffs二、 yaffs文件系統,則可經過以下兩種方式更新:
    1. 燒寫文件系統鏡像
    2. 系統啓動後tftp命令下載SO共享目標文件(或OBJ目標文件)、基礎符號表elf_symbol.so,示例命令以下:
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
  1. 啓動系統動態加載功能,進行驗證

2.6 Shell 調試

在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)必需要先初始化動態加載模塊再加載模塊。

  • 查找一個符號
    Shell命令: findsym
Huawei LiteOS# findsym 0 printf
symbol address:0x8004500c
Huawei LiteOS#
Huawei LiteOS# findsym 0x80391928 test_0
symbol address:0x8030f241
Huawei LiteOS#
  • 調用一個符號
    Shell 命令: call
Huawei LiteOS# call 0x8030f241
test_0
Huawei LiteOS#
  • 卸載一個模塊

Shell命令: mclose

Huawei LiteOS# mclose 0x80391928
Huawei LiteOS#

3. 編程實例

3.1 實例描述

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;
}

3.2 結果驗證

編譯運行獲得的結果爲:

Huawei LiteOS#
*****************************
*****************************
test_0
test_2:42 57
*****************************
*****************************
相關文章
相關標籤/搜索