Linux

Linux 中的堆棧分配是如何工作的?

  • September 25, 2019

作業系統是否為堆棧或其他東西保留了固定數量的有效虛擬空間?我是否能夠僅通過使用大的局部變數來產生堆棧溢出?

我寫了一個小C程序來測試我的假設。它在 X86-64 CentOS 6.5 上執行。

#include <string.h>
#include <stdio.h>
int main()
{
   int n = 10240 * 1024;
   char a[n];
   memset(a, 'x', n);
   printf("%x\n%x\n", &a[0], &a[n-1]);
   getchar();
   return 0;
}

執行程序給出&a[0] = f0ceabe0&a[n-1] = f16eabdf

proc 映射顯示堆棧:7ffff0cea000-7ffff16ec000. (10248 * 1024B)

然後我嘗試增加n = 11240 * 1024

執行程序給出&a[0] = b6b36690&a[n-1] = b763068f

proc 映射顯示堆棧:7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -s``10240在我的電腦上列印。

正如你所看到的,在這兩種情況下,堆棧大小都比ulimit -s給出的要大。並且堆棧隨著更大的局部變數而增長。堆棧頂部以某種方式多出 3-5kB &a[0](AFAIK 紅色區域為 128B)。

那麼這個堆棧映射是如何分配的呢?

似乎沒有分配堆棧記憶體限制(無論如何,它不能無限堆棧)。https://www.kernel.org/doc/Documentation/vm/overcommit-accounting說:

C 語言堆棧增長執行隱式 mremap。如果您想要絕對保證並靠近邊緣執行,您必須將您的堆棧映射為您認為需要的最大尺寸。對於典型的堆棧使用,這並不重要,但如果你真的很在乎,這是一個極端情況

然而,映射堆棧將是編譯器的目標(如果它有一個選項)。

編輯:在 x84_64 Debian 機器上進行一些測試後,我發現堆棧在沒有任何系統呼叫的情況下增長(根據strace)。所以,這意味著核心會自動增長它(這就是上面“隱式”的意思),即沒有顯式mmap/mremap來自程序。

很難找到證實這一點的詳細資訊。我推薦Mel Gorman的《Understanding The Linux Virtual Memory Manager 》。我想答案在第 4.6.1 節處理頁面錯誤,除了“區域無效,但在像堆棧這樣的可擴展區域旁邊”和相應的操作“擴展區域並分配頁面”。另見 D.5.2擴展堆棧

關於 Linux 記憶體管理的其他參考資料(但幾乎沒有關於堆棧的內容):

編輯 2:此實現有一個缺點:在極端情況下,即使堆棧大於限制,也可能無法檢測到堆棧衝突!原因是在堆棧中的變數中寫入可能最終會在分配的堆記憶體中,在這種情況下沒有頁面錯誤並且核心無法知道需要擴展堆棧。請參閱我在 gcc-help 列表中開始的GNU/Linux 下的靜默堆棧衝突討論中的範例。為了避免這種情況,編譯器需要在函式呼叫時添加一些程式碼;這可以通過-fstack-checkGCC 完成(有關詳細資訊,請參閱 Ian Lance Taylor 的回復和 GCC 手冊頁)。

引用自:https://unix.stackexchange.com/questions/145557