簡體   English   中英

有沒有辦法在 Linux 上兩個不相關的進程之間進行共享地址映射?

[英]Is there a way to an share address mapping between two unrelated processes on Linux?

我假設如果這是可能的,它很可能是通過mmap

我最初的問題是mmap( ) 可以為同一塊共享內存的每個用戶返回相同的指針嗎? 最終,每個進程地址空間中的一個地址映射到相同的物理內存。 問題是提供給每個進程的地址是否可以以某種方式相同。


為什么這很重要?

考慮共享包含指針的數據結構的情況。 如果地址映射是共享的,那么這些指針對兩個進程都有效。 如果地址映射不共享,則指針可能指向寫入它的進程的地址空間。 如果其他進程試圖讀取它,它將觸發訪問沖突。

對於共享庫,我們依賴與位置無關的代碼,但仍然可能存在加載程序需要在加載時調整的指針。 (我不清楚這是否只執行一次,或者每個進程是否獲得自己的私有頁面副本,其中包含調整以適應其自己的地址空間的此類指針)。

如果地址映射不共享,那么對於數據,我們要么需要使用偏移量而不是絕對指針來創建與位置無關的結構(不幸的首字母縮寫詞),要么將自己限制為不包含任何指針的數據結構。

在我的應用程序中,我希望分享幾個數據結構,它們本質上是向量。 我不希望持久化或序列化/反序列化它們。 我希望每個進程使用相同的物理內存。 基本上它是一個搜索問題 - 共享數據是大海撈針,每個進程都搜索自己的一組針。 由於各種原因,我們不希望它們成為線程,盡管它會完全消除這個問題,因為線程確實共享地址空間。

我可以使用自定義分配器在共享內存中創建向量。 說:

SharedMemoryAllocator sharedAlloc(somePointerFromMmap);
class Foo
{
   std::vector<Bar, SharedMemoryAllocator>   A;
   std::vector<Snafu, SharedMemoryAllocator> B;

   Foo(SharedMemoryAllocator& sharedAlloc):
      A(sharedAlloc)
      B(sharedAlloc)
   {
   }
};

如果地址映射是共享的,那么對完整結構使用這樣的分配器是安全的:

unique_ptr<Foo> fubar = sharedAlloc.new(Foo(sharedAlloc));

因為帶有*fubar的 A 和 B 的存儲指針將位於兩者的相同地址空間中。

另一方面,如果地址映射不共享,我只能安全地共享 A 和 B 指向的底層內存塊。

也就是說,每個進程都必須有自己的 Foo 本地實例,其中每個向量都是通過附加到分配器提供的共享內存塊來構造的。 這更便攜但更丑陋。

從技術上講,如果不添加std::bless()和其他魔法,它們都不符合 C++ 規范。 所以無論如何我們都處於未定義(或者更確切地說是平台定義)的行為。 但是, malloc的“安全”使用也是如此(參見上面的鏈接)。


據我所知,Posix 或 Linux 保證mmap()返回的地址一無所知,除非您要求特定地址並且調用沒有失敗。

因此,如果它碰巧起作用,它可能充其量只是未定義的行為。 由於所有常見的原因,依賴未定義的行為通常是一個壞主意。 但也許它實際上是平台定義的而不是未定義的?

如前所述, 固定地址映射的例外情況是,但是如何讓兩個獨立的進程預先就同一個安全地址達成一致? 這里給出了一種可能性

這表明您需要第二個共享內存段或其他一些 IPC 機制來共享分配給您希望使用的共享內存塊的創建者的地址。

這是正確的方法嗎? 這是唯一的方法嗎?


查看何時使用 mmap MAP_FIXED? - MAP_FIXED 的主要合法用途是在加載庫時重新映射需要彼此位於相同相對地址的不同類型的內存段

然而,其他人正在按照我建議的方式使用它。 該問題的另一個答案提到:

  • 共享內存可能包含指針。

我發現了一些其他使用 MAP_FIXED 來執行此操作的人的參考資料,但我還沒有找到一個可行的示例。

以這種方式使用 MAP_FIXED 是否已被ASLR呈現為非功能性?

實際上,您只能在非常特定的情況下共享地址空間:

例如,僅當:

那么 MAP_FIXED_NOREPLACE 會在這種情況下工作嗎?

使用 shm_open() 還是 open() 有區別嗎?

我查看了boost interprocess的代碼,它似乎既沒有使用 MAP_FIXED 也沒有使用 sendmsg。 這似乎不受支持

我目前正在嘗試的方法是簡化:

過程A:

fd = open("foobar", O_RDWR);
void* address = mmap(nullptr, PROT_READ|PROT_WRITE, MAP_SHARED, fd);
(*address) = address;

過程乙:

fd = open("foobar", O_RDONLY);
void* addressRequested = readAddressFromFoobar();       
void* address = mmap(addressRequested, PROT_READ, MAP_SHARED|MAP_FIXED, fd);

E_NOMEM 失敗。 雖然如果我用 nullptr 替換 addressRequested 它成功並且我可以訪問相同的數據但不能依賴任何指針。

有人可以演示或鏈接到一種可行的方法,或者明確解釋為什么這在當前的 Linux 中永遠無法工作。

我很清楚我們可以使用內部偏移量而不是指針來共享對象,但會失去很多便利。 STL 類型通常假定它們可以使用指針。 如果可以避免的話,我不想為我希望使用的每個容器編寫自己的版本。


顯然 boost 通過提供使用智能指針而不是原始指針的容器來解決這個問題。 我沒有意識到這一點。 這是對一般問題的一個很好的解決方案,但與這個問題不同。 找到一個規范的解釋將有助於更好地回答是否提升進程間支持在進程之間共享包含指針的對象? 或者確實是一個更好的問題。

有人可以演示或鏈接到一種可行的方法,或者明確解釋為什么這在當前的 Linux 中永遠無法工作。

您現有的方式已經可以使用。

它有點脆弱,而且很繁瑣,我不確定這是一個好主意,但它可以工作。

  1. ASLR

    由於兩個進程(可能)對於 mmap 基址具有不同的ASLR偏移量,因此您不能只獲取mmap在第一個進程中返回的地址並假設它會在第二個進程中工作。

    對於當前的實現,映射一個小區域(使用mmap addr=NULL )來發現隨機基址就足夠了,然后在下一個 1MB 邊界處請求真正的映射。

  2. 運行時差異

    單獨來看,這可能就足夠了,但其他進程可能已經使用了相同的地址。 例如,共享庫可以在到達用戶代碼之前調用mmap ,這可能因 ASLR、庫更新、預加載或其他原因而在每次運行時有所不同。

    可能第二個進程也應該做一個沒有請求地址的非固定映射來建立它自己的有效基礎(包括 ASLR 和任何現有的mmap ),因此它可以在使用MMAP_FIXED嘗試之前對廣告地址進行健全性檢查。

    所以,你需要一個反饋/共識機制。 如果第二個進程無法映射到第一個通告的地址,讓它創建自己的映射並將該地址通告回第一個進程(它應該在嘗試之前釋放自己的映射,以防它們重疊)。

    面對未來的 ASLR 變化,這種迭代也使第一點變得不那么脆弱。

請注意,即使這樣有效,您不區分共享地址空間和私有地址空間指針的事實也是一個重大風險。 您可以很容易地將指向非共享內存的指針存儲在您的一個共享結構中,因為它們是相同的類型。

我強烈建議使用 Boost.Interprocess 分配器、offset_ptrs 等來明確區分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM