Tự động mở rộng stack stack là gì?


13

getrlimit (2) có định nghĩa sau trong các trang man:

RLIMIT_AS Kích thước tối đa của bộ nhớ ảo của quy trình (không gian địa chỉ) tính bằng byte. Giới hạn này ảnh hưởng đến các lệnh gọi brk (2), mmap (2) và mremap (2), không thành công với lỗi ENOMEM khi vượt quá giới hạn này. Ngoài ra việc mở rộng ngăn xếp tự động sẽ thất bại (và tạo SIGSEGV giết chết quá trình nếu không có ngăn xếp thay thế nào được cung cấp qua sigaltstack (2)). Vì giá trị là dài, trên các máy có độ dài 32 bit, giới hạn này tối đa là 2 GiB hoặc tài nguyên này là không giới hạn.

"Mở rộng ngăn xếp tự động" có nghĩa là gì ở đây? Ngăn xếp trong môi trường Linux / UNIX có phát triển khi cần không? Nếu có, cơ chế chính xác là gì?

Câu trả lời:


1

Có ngăn xếp phát triển năng động. Ngăn xếp nằm trong đỉnh của bộ nhớ phát triển dần về phía heap.

--------------
| Stack      |
--------------
| Free memory|
--------------
| Heap       |
--------------
     .
     .

Heap phát triển lên (bất cứ khi nào bạn thực hiện malloc) và ngăn xếp phát triển xuống dưới và khi các chức năng mới được gọi. Heap có mặt ngay phía trên phần BSS của chương trình. Điều đó có nghĩa là kích thước của chương trình của bạn và cách nó phân bổ bộ nhớ trong heap cũng ảnh hưởng đến kích thước ngăn xếp tối đa cho quá trình đó. Thông thường kích thước ngăn xếp là không giới hạn (cho đến khi vùng heap và ngăn xếp gặp nhau và / hoặc ghi đè lên, điều này sẽ tạo ra tràn ngăn xếp và SIGSEGV :-)

Điều này chỉ dành cho các quy trình người dùng, Ngăn xếp kernel luôn được cố định (thường là 8KB)


"Thông thường kích thước ngăn xếp là không giới hạn", không, thường thì nó bị giới hạn bởi 8Mb ( ulimit -s).
Eddy_Em

vâng, bạn đúng trong hầu hết các hệ thống. Bạn đang kiểm tra lệnh ulimit của shell, nếu vậy có giới hạn cứng đối với kích thước ngăn xếp, không giới hạn (ulimit -Hs). Dù sao, điểm đó là để nhấn mạnh rằng đống và đống phát triển theo hướng ngược lại.
Santosh

Vậy thì "mở rộng tự động" có khác gì với "đẩy các phần tử lên ngăn xếp" không? Từ lời giải thích của bạn, tôi có cảm giác rằng chúng giống nhau. Ngoài ra, tôi cảm thấy như điểm bắt đầu của stack và heap là hơn 8MB, vì vậy stack có thể phát triển bao nhiêu tùy ý (hoặc nó đạt được heap). Điều đó có đúng không? Nếu có, làm thế nào để hệ điều hành quyết định nơi đặt heap và stack?
tiếng

Chúng giống nhau, nhưng ngăn xếp không có kích thước cố định trừ khi bạn khó giới hạn kích thước bằng rlimit. Ngăn xếp được đặt ở cuối vùng nhớ ảo và heap ngay lập tức là phân đoạn dữ liệu của tệp thực thi.
Santosh

Tôi hiểu, cảm ơn. Tuy nhiên, tôi không nghĩ rằng tôi có phần "ngăn xếp không có kích thước cố định". Nếu đó là trường hợp, giới hạn mềm 8Mb để làm gì?
tiếng

8

Cơ chế chính xác được đưa ra ở đây, trên Linux: trong việc xử lý lỗi trang trên ánh xạ ẩn danh, bạn kiểm tra xem liệu đó có phải là "phân bổ không tăng trưởng" mà bạn nên mở rộng như một ngăn xếp hay không. Nếu bản ghi khu vực VM nói rằng bạn nên, thì bạn điều chỉnh địa chỉ bắt đầu để mở rộng ngăn xếp.

Khi xảy ra lỗi trang, tùy thuộc vào địa chỉ, nó có thể được bảo dưỡng (và lỗi được dập tắt) thông qua việc mở rộng ngăn xếp. Hành vi "phát triển xuống trên một lỗi" này đối với bộ nhớ ảo có thể được yêu cầu bởi các chương trình người dùng tùy ý với MAP_GROWSDOWNcờ được chuyển đến tòa nhà mmap.

Bạn cũng có thể loay hoay với cơ chế này trong một chương trình người dùng:

#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;
}

Khi nó nhắc bạn tìm pid của chương trình (thông qua ps) và nhìn vào /proc/$THAT_PID/mapsđể xem khu vực ban đầu đã phát triển như thế nào.


Bạn có thể gọi munmap cho mem gốc và page_size ngay cả khi vùng nhớ đã phát triển thông qua MAP_GlawSDOWN không? Tôi đoán là có, bởi vì nếu không nó sẽ là một API rất phức tạp để sử dụng, nhưng tài liệu không nói rõ ràng về vấn đề này
i.petruk

2
MAP_GWAYSDOWN không nên được sử dụng và đã bị xóa khỏi glibc (xem lwn.net/Articles/294001 để biết lý do).
Collin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.