簡體   English   中英

在 malloc 中,為什么要使用 brk? 為什么不直接使用 mmap?

[英]In malloc, why use brk at all? Why not just use mmap?

malloc的典型實現使用brk / sbrk作為從操作系統申請內存的主要方式。 然而,他們也使用mmap來獲取大分配的塊。 使用brk而不是mmap真的有好處,還是只是傳統? 使用mmap來完成這一切不是同樣有效嗎?

(注意:我在這里互換使用sbrkbrk因為它們是同一個 Linux 系統調用brk的接口。)


作為參考,這里有一些描述 glibc malloc的文檔:

GNU C 庫參考手冊:GNU 分配器
https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html

glibc wiki:Malloc 概述
https://sourceware.org/glibc/wiki/MallocInternals

這些文檔描述的是sbrk用於為小分配聲明一個主要區域, mmap用於聲明次要區域, mmap還用於為大對象(“比頁面大得多”)聲明空間。

應用程序堆(使用sbrk聲明)和mmap的使用引入了一些可能不必要的額外復雜性:

分配的競技場 - 主競技場使用應用程序的堆。 其他競技場使用mmap的堆。 要將塊映射到堆,您需要知道適用於哪種情況。 如果該位為 0,則 chunk 來自 main arena 和 main heap。 如果該位為 1,則該塊來自mmap的內存,並且可以根據塊的地址計算堆的位置。

[Glibc malloc 派生自ptmalloc ,派生自 1987 年開始的dlmalloc 。]


jemalloc聯機幫助頁 ( http://jemalloc.net/jemalloc.3.html ) 是這樣說的:

傳統上,分配器使用sbrk(2)來獲取內存,這不是最優的,原因有幾個,包括競爭條件、增加的碎片和對最大可用內存的人為限制。 如果操作系統支持sbrk(2) ,則此分配器按優先順序使用mmap(2)和 sbrk(2); 否則只使用mmap(2)

因此,他們甚至在這里說sbrk不是最優的,但他們仍然使用它,即使他們已經不厭其煩地編寫代碼以使其在沒有它的情況下也能正常工作。

[jemalloc 的編寫始於 2005 年。]

更新:更多地考慮這個問題,關於“按優先順序”的那一點給了我一個查詢線。 為什么優先順序? 他們是否只是使用sbrk作為后備以防不支持mmap (或缺少必要的功能),或者進程是否有可能進入可以使用sbrk但不能使用mmap的狀態? 我將查看他們的代碼,看看我是否能弄清楚它在做什么。


我問是因為我正在用 C 實現垃圾收集系統,到目前為止我認為沒有理由使用mmap之外的任何東西。 不過,我想知道我是否遺漏了什么。

(在我的例子中,我還有一個避免brk的額外原因,那就是我可能需要在某些時候使用malloc 。)

系統調用brk()的優點是只有一個數據項來跟蹤內存使用情況,這也與堆的總大小直接相關。

自 1975 年的 Unix V6 以來,這一直是完全相同的形式。 請注意,V6 支持 65,535 字節的用戶地址空間。 因此,沒有太多考慮管理超過 64K 的數據,當然不是 TB。

使用mmap似乎是合理的,直到我開始想知道如何改變或添加垃圾收集可以使用mmap無需重寫分配算法。

這能很好地與realloc()fork()等一起使用嗎?

每次內存分配調用mmap(2)一次對於通用內存分配器來說不是一種可行的方法,因為 mmap(2) 的分配粒度(一次可以分配的最小單個單元mmap(2)PAGESIZE (通常為 4096 字節),並且因為它需要一個緩慢而復雜的系統調用。 具有低碎片的小分配的分配器快速路徑應該不需要系統調用。

所以無論你使用什么策略,你仍然需要支持多個 glibc 所謂的 memory arenas, GNU 手冊中提到: “多個 arenas 的存在允許多個線程在單獨的 arenas 中同時分配內存,從而提高性能。”


jemalloc 聯機幫助頁 ( http://jemalloc.net/jemalloc.3.html ) 是這樣說的:

傳統上,分配器使用 sbrk(2) 來獲取內存,這不是最優的,原因有幾個,包括競爭條件、增加的碎片和對最大可用內存的人為限制。 如果操作系統支持 sbrk(2),則此分配器按優先順序同時使用 mmap(2) 和 sbrk(2); 否則只使用 mmap(2)。

據我所知,我看不出這些如何適用於sbrk(2)的現代使用。 競爭條件由線程原語處理。 碎片的處理方式與mmap(2)分配的內存區域的處理方式相同。 最大可用內存是無關緊要的,因為mmap(2)應該用於任何大的分配以減少碎片並在free(3)上立即將內存釋放回操作系統。


應用程序堆(使用 sbrk 聲明)和 mmap 的使用引入了一些可能不必要的額外復雜性:

分配的競技場 - 主競技場使用應用程序的堆。 其他競技場使用 mmap'd 堆。 要將塊映射到堆,您需要知道適用於哪種情況。 如果該位為 0,則 chunk 來自 main arena 和 main heap。 如果該位為 1,則塊來自 mmap 的內存,堆的位置可以根據塊的地址計算得出。

所以現在的問題是,如果我們已經在使用mmap(2) ,為什么不在進程開始時使用mmap(2)而不是使用sbrk(2)分配一個競技場? 尤其是如果如引用的那樣,有必要跟蹤使用了哪種分配類型。 有幾個原因:

  1. 可能不支持mmap(2)
  2. sbrk(2)已經為進程初始化,而mmap(2)會引入額外的要求。
  3. 正如glibc wiki所說, “如果請求足夠大,mmap() 用於直接從操作系統請求內存 [...] 並且一次可以有多少這樣的映射可能會有限制。”
  4. mmap(2)分配的內存映射不能輕易擴展。 Linux 有mremap(2) ,但它的使用將分配器限制為支持它的內核。 預映射許多具有PROT_NONE訪問權限的頁面會使用過多的虛擬內存。 使用MMAP_FIXED取消映射之前可能存在的任何映射,而不會發出警告。 sbrk(2)沒有這些問題,並且明確設計為允許安全地擴展其內存。

mmap()在 Unix 的早期版本中不存在。 brk()是當時增加進程數據段大小的唯一方法。 第一個帶有mmap()的 Unix 版本是 80 年代中期的SunOS ,第一個開源版本是 1990 年的 BSD-Reno。

並且要可用於malloc()你不想需要一個真實的文件來備份內存。 1988 年,SunOS 為此目的實現了/dev/zero ,並在 1990 年代的 HP-UX 中實現了MAP_ANONYMOUS標志。

現在的mmap()版本提供了多種分配堆的方法。

明顯的優點是您可以就地增加最后的分配,這是您不能用mmap(2)做的事情( mremap(2)是 Linux 擴展,不可移植)。

對於使用realloc(3)的天真的(和不太天真的)程序,例如。 附加到一個字符串,這轉化為 1 或 2 個數量級的速度提升;-)

我不知道具體在 Linux 上的細節,但在 FreeBSD 上已經有好幾年了,現在 mmap 是首選,而 FreeBSD 的 libc 中的 jemalloc 已經完全禁用了 sbrk() 。 brk()/sbrk() 未在 aarch64 和 risc-v 的較新端口上的內核中實現。

如果我正確理解 jemalloc 的歷史,它最初是 FreeBSD 的 libc 中的新分配器,然后才被打破並變得可移植。 現在 FreeBSD 是 jemalloc 的下游消費者。 它對 mmap() 而不是 sbrk() 的偏好很可能源於 FreeBSD VM 系統的特性,該系統是圍繞實現 mmap 接口而構建的。

值得注意的是,在 SUS 和 POSIX 中,brk/sbrk 已被棄用,此時應將其視為不可移植的 如果您正在使用新的分配器,您可能不想依賴它們。

暫無
暫無

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

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