運行時(動態)加載

    動態庫的加載涉及到4個API接口:打開動態庫(dlopen)、查找符號(dlsym)、錯誤處理(dlerror)以及關閉動態庫(dlclose),程序可以通過這幾個API對動態庫進行操作。

1.dlopen()

    dlopen()函數用來打開一個動態庫,並將其加載到進程地址空間,完成初始化的過程,它的C原型定義爲:

void * dlopen(const char *filename, int flag);

    第一個參數是加載動態庫的路徑,如果這個路徑是絕對路徑(以"/"開始的路徑),則該函數將嘗試直接打開該動態庫;如果是相對路徑,那麼dlopen()會嘗試在以一定的順序去查找該動態庫文件:

1.查找有環境變量 LD_LIBRARY_PATH 指定的一系列目錄。

2.查找由/etc/ld.so.cache裏面所指定的共享庫路徑。

3./lib、/usr/lib。

    當然,這在理論上不應該成爲一個問題,因爲所有的庫都應該只存在於某個目錄中,而不應該在多個目錄中有不同的副本,這將會導致系統變得極爲不可靠。

    很有意思的是,如果我們將filename這個參數設置爲0,那麼dlopen返回的將是全局符號表的句柄,也就是說我們可在運行時找到全局符號表裏面的任何一個符號,並且可以執行它們,這有些類似高級語言反射(Reflection)的特性。全局符號表包括了程序的可執行文件本身、被動態鏈接器加載到進程中的所有共享模塊以及在運行時通過dlopen打開且使用了RTLD_GLOBAL方式的模塊中的符號。

    第二個參數flag表示函數符號的解析方式,常量RTLD_LAZY表示使用延遲綁定,當函數第一次被調用到時才進行綁定,即PLT機制;而RTLD_NOW表示當模塊被加載時即完成所有函數的綁定工作,如果有任何未定義符號的引用導致綁定工作沒法完成,那麼dlopen()會返回錯誤。上面的兩種綁定方式必須選其一。另外還有一個常量RTLD_GLOBAL可以跟上面的兩者中的任意一個儀器使用(通過常量的“或”操作),它表示將被加載模塊的全局符號合併到進程的全局符號表中,使得以後加載的模塊可以使用這些符號。(使用 RTLD_NOW會導致加載動態庫的速度變慢)

    dlopen的返回值是被加載模塊的句柄,這個句柄dlsym或者dlclose會用到。如果加載模塊失敗,則返回NULL。如果模塊已經通過dlopen被加載過了,那麼返回的是同一個句柄。另外如果被加載模塊有依賴關係,比如模塊A依賴於模塊B,那麼程序員需手工加載被依賴的模塊,比如先加載B,再加載A。

 

2.dlsym()

    dlsym函數基本上是運行時裝載的核心部分,我們可以通過這個函數找到所需要的符號。它的定義如下:

void * dlsym(void *handle, char *symbol);

    第一個參數是有dlopen返回的動態庫句柄;第二個參數即所要查找的符號的名字,一個以“\0”結尾的C字符串。如果dlsym()找到了相應的符號,則返回該符號的值;沒找到相應的符號,則返回NULL。dlsym()返回值對於不同類型的符號意義是不同的。如果查找的符號是個函數,那麼它返回函數的地址;如果是個變量,它返回變量的地址;如果這個符號是個常量,那麼它返回的是該常量的值。這裏有一個問題是:如果常量的值剛好是NULL或者0呢?我們該如何判斷dlsym()是否找到了該符號呢?通過dlerror()函數可以確認,如果符號找到了,那麼dlerror()返回NULL,如果沒找到,dlerror()就會返回相應的錯誤信息。

 

3.dlerror()

    在調用dlopen()、dlsym()、dlclose()以後,我們都可以調用dlerror()函數來判斷上一次調用是否成功。dlerror()的返回值類型是char*,如果返回NULL,則表示上一次調用成功;如果不是,則返回相應的錯誤信息。

 

4.dlclose()

    dlclose()的作用跟dlopen()剛好相反,它的作用是將一個已經加載的模塊卸載。系統會維持一個加載引用計數器,每次使用dlopen()加載某個模塊時,相應的計數器加一;每次使用dlclose()卸載某個模塊時,相應的計數器減一。只有當計數器值減到0時,模塊才被真正地卸載掉,相應的符號從符號表中去除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章