Linux
什麼是“自動堆棧擴展”?
getrlimit(2)在手冊頁中有以下定義:
RLIMIT_AS 程序的虛擬記憶體(地址空間)的最大大小,以字節為單位。此限制會影響對 brk(2)、mmap(2) 和 mremap(2) 的呼叫,超過此限制時會失敗並返回錯誤 ENOMEM。自動堆棧擴展也會失敗(如果沒有通過 sigaltstack(2) 提供備用堆棧,則會生成一個終止程序的 SIGSEGV)。由於該值為 long,因此在具有 32 位長度的機器上,此限制最多為 2 GiB,或者此資源是無限的。
這裡的“自動堆棧擴展”是什麼意思?Linux/UNIX 環境中的堆棧是否按需增長?如果是,確切的機制是什麼?
是的,堆棧動態增長。堆棧位於記憶體的頂部,向下朝向堆增長。
-------------- | Stack | -------------- | Free memory| -------------- | Heap | -------------- . .
當呼叫新函式時,堆向上增長(無論何時執行 malloc),堆棧向下增長。堆位於程序的 BSS 部分的正上方。這意味著程序的大小以及它在堆中分配記憶體的方式也會影響該程序的最大堆棧大小。通常堆棧大小是無限的(直到堆和堆棧區域相遇和/或覆蓋,這將導致堆棧溢出和 SIGSEGV :-)
這僅適用於使用者程序,核心堆棧始終是固定的(通常為 8KB)
在 Linux 上,這裡給出了確切的機制:在處理匿名映射的頁面錯誤時,您檢查它是否是應該像堆棧一樣擴展的“增長的分配”。如果 VM 區域記錄表明您應該這樣做,那麼您調整起始地址以擴展堆棧。
當頁面錯誤發生時,根據地址,它可以通過堆棧擴展得到服務(並取消錯誤)。虛擬記憶體的這種“在故障時向下增長”行為可以由任意使用者程序請求,並將
MAP_GROWSDOWN
標誌傳遞給mmap
系統呼叫。您也可以在使用者程序中使用此機制:
#include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> int main() { long page_size = sysconf(_SC_PAGE_SIZE); void *mem = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_GROWSDOWN|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (MAP_FAILED == mem) { perror("failed to create growsdown mapping"); return EXIT_FAILURE; } volatile char *tos = (char *) mem + page_size; int i; for (i = 1; i < 10 * page_size; ++i) tos[-i] = 42; fprintf(stderr, "inspect mappping for originally page-sized %p in /proc... press any key to continue...\n", mem); (void) getchar(); if (munmap(mem, page_size)) perror("failed munmap"); return EXIT_SUCCESS; }
當它提示您找到程序的 pid(通過
ps
)並查看/proc/$THAT_PID/maps
原始區域的增長情況時。