2017-08-30 141 views
1

據其man pagedlopen()不會加載同一庫兩次:如何規避dlopen()緩存?

如果相同的共享對象被使用dlopen再次加載(),則返回相同 對象的句柄。動態鏈接器維護對象句柄的引用計數 ,因此動態加載的共享對象是 不會被釋放,直到在其上調用dlclose()的次數爲 (因爲dlopen()已成功執行)。任何初始化返回(請參閱下面的 )都只調用一次。但是,隨後的調用 的dlopen()加載與RTLD_NOW相同的共享對象可能會強制使用先前加載了RTLD_LAZY的共享對象的符號 分辨率。

(強調我的)。

但究竟是什麼決定了共享對象的身份呢?我試圖查看代碼,但並未走得太遠。它是:

  • 某種形式的標準化路徑名(如真實路徑?)
  • inode的?
  • libray的內容?

我很確定我可以排除這最後一點,因爲實際的文件系統副本會產生兩個不同的句柄。

爲了解釋這個問題背後的動機:我正在處理一些具有靜態全局變量的代碼。我需要該代碼的多個實例以線程安全的方式運行。我目前的做法是編譯並將所述代碼鏈接到一個動態庫並加載該庫多次。有了一些鏈接魔法,它似乎創建了全局變量的多個副本,並將每個庫中的訪問解析爲它自己的副本。唯一的問題是我的原型爲n個併發使用複製生成的庫n次。這不僅有些醜陋,但我也懷疑它可能會在不同的平臺上崩潰。

那麼根據POSIX標準,dlopen()的確切行爲是什麼?

編輯:因爲它出現在評論和答案中,所以不重構代碼絕對不是一種選擇。這將涉及數月甚至數年的工作,並可能首先犧牲使用代碼的所有好處。有存在一個正在進行的研究項目,可能以更清潔的方式解決這個問題,但它是實際的研究,可能會失敗。我現在需要一個解決方案。

編輯2:因爲人們似乎還不相信用例實際上是有效的。我正在研究一種純粹的函數式語言,它將被嵌入到一個更大的C/C++應用程序中。因爲我需要一個帶有垃圾回收器的原型,一個經過驗證的類型檢查器,並且儘快提供合理的性能,所以我使用OCaml作爲中間代碼。現在,我編譯一個源模塊放入OCaml的模塊,鏈接生成的目標代碼(包括啓動等)到共享庫與OCaml的運行時和dlopen()的共享庫。每個.so都有它自己的運行時副本,包括幾個全局變量(例如指向年輕一代),而且應該是完全正確的。該庫提供了兩個函數:一個初始化程序和一個單獨的導出,它可以完成原始模塊的任何操作。沒有OCaml運行時的符號被導出/共享。當我加載庫時,它的內部符號按預期重新定位,我現在唯一的問題是,我實際上需要在運行時爲每個作業實例複製.so文件。

關於線程本地存儲:這實際上是一個有趣的想法,因爲對運行時的修改確實很簡單。但問題是由OCaml編譯器生成的機器碼,因爲它不能發出tls符號的加載指令(還沒有?)。

+2

'dlopen()'不能解決你基本破壞的庫的問題......如果你可以自己編譯有問題的代碼,你應該更好地解決它是線程安全的:o –

+0

如果庫不是線程意識到,至少可以用線程本地數據替換靜態存儲持續時間數據。這是一項完全機械化的任務,不需要研究工作或幾個月即可完成。 –

+0

這可能是一個有價值的實驗。但是對於這個任務,我需要知道兩件事:1.如何共享庫處理線程本地存儲以及ELF對象文件如何聲明它們? – choeger

回答

5

POSIX說:

只有一個目標文件的一個副本被帶入地址空間中,即使執行dlopen()在參考文件被調用多次,並且即使使用不同路徑名引用該文件。

所以答案是「inode」。複製庫文件「應該有效」,但硬鏈接不會。除。由於他們將公開相同的全局符號,並且當所有(可移植性)投注都關閉時。您正處於通過錯誤修復而不是良好設計發展而來的定義不清的行爲。

當你在洞中時,不要深入挖掘。添加更多可怕的黑客手段以使基本上破壞的庫工作的方法只會導致額外的破壞。花幾個小時的時間來修復這個庫,不使用全局變量,而是花費數天時間來解決動態鏈接問題(最好是不可移植的)。

+1

RTLD_LOCAL應該照顧全局符號。雖然我同意擺脫全球/靜態數據是一條路。 –

+0

那麼,「目標文件」的實際POSIX定義是什麼?只是將它複製到具有不同inode的不同文件可能不夠。畢竟,它仍然是相同的符號。 –

+0

@AndrewHenle POSIX經常模糊不清,因爲不同的系統行爲不同。 「即使不同的路徑名」位意味着重複數據刪除很可能發生在任何「統計」返回。雖然我知道這是在一廂情願的想法和廢話之間的某個地方,因爲如果裝入的對象有一個路徑名,我工作的ld.so就不會對任何東西做任何重複數據刪除操作(在我工作之後,「dlopen」被標準化了很久代碼,我懷疑在這種情況下的POSIX過程是「文檔glibc,擰其他人」)。 – Art