Linux 中的堆棧分配是如何工作的?
作業系統是否為堆棧或其他東西保留了固定數量的有效虛擬空間?我是否能夠僅通過使用大的局部變數來產生堆棧溢出?
我寫了一個小
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 記憶體管理的其他參考資料(但幾乎沒有關於堆棧的內容):
- 記憶體常見問題
- Ulrich Drepper每個程序員都應該知道的關於記憶體的知識
編輯 2:此實現有一個缺點:在極端情況下,即使堆棧大於限制,也可能無法檢測到堆棧衝突!原因是在堆棧中的變數中寫入可能最終會在分配的堆記憶體中,在這種情況下沒有頁面錯誤並且核心無法知道需要擴展堆棧。請參閱我在 gcc-help 列表中開始的GNU/Linux 下的靜默堆棧衝突討論中的範例。為了避免這種情況,編譯器需要在函式呼叫時添加一些程式碼;這可以通過
-fstack-check
GCC 完成(有關詳細資訊,請參閱 Ian Lance Taylor 的回復和 GCC 手冊頁)。