Làm thế nào để phân bổ ngăn xếp hoạt động trong Linux?


18

Hệ điều hành có dành một lượng không gian ảo hợp lệ cố định cho ngăn xếp hay thứ gì khác không? Tôi có thể tạo ra một ngăn xếp tràn chỉ bằng cách sử dụng các biến cục bộ lớn không?

Tôi đã viết một Cchương trình nhỏ để kiểm tra giả định của mình. Nó đang chạy trên 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;
}

Chạy chương trình cho &a[0] = f0ceabe0&a[n-1] = f16eabdf

Các bản đồ Proc hiển thị ngăn xếp: 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

Sau đó tôi đã cố gắng tăng n = 11240 * 1024

Chạy chương trình cho &a[0] = b6b36690&a[n-1] = b763068f

Các bản đồ Proc hiển thị ngăn xếp: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -sin 10240trong PC của tôi.

Như bạn có thể thấy, trong cả hai trường hợp, kích thước ngăn xếp đều lớn hơn so với kích thước ulimit -s. Và ngăn xếp phát triển với biến cục bộ lớn hơn. Đỉnh của ngăn xếp bằng cách nào đó giảm thêm 3-5kB &a[0](AFAIK vùng màu đỏ là 128B).

Vậy làm thế nào để bản đồ ngăn xếp này được phân bổ?

Câu trả lời:


14

Dường như giới hạn bộ nhớ ngăn xếp không được phân bổ (dù sao, nó không thể với ngăn xếp không giới hạn). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting nói:

Tăng trưởng ngăn xếp ngôn ngữ C thực hiện một mremap ẩn. Nếu bạn muốn đảm bảo tuyệt đối và chạy sát mép, bạn PHẢI mmap ngăn xếp của bạn cho kích thước lớn nhất bạn nghĩ bạn sẽ cần. Đối với việc sử dụng ngăn xếp thông thường, điều này không quan trọng lắm nhưng đó là trường hợp góc nếu bạn thực sự quan tâm

Tuy nhiên, việc sắp xếp ngăn xếp sẽ là mục tiêu của trình biên dịch (nếu nó có tùy chọn cho điều đó).

EDIT: Sau một số thử nghiệm trên máy Debian x84_64, tôi thấy rằng ngăn xếp phát triển mà không có bất kỳ cuộc gọi hệ thống nào (theo strace). Vì vậy, điều này có nghĩa là kernel tự động phát triển nó (đây là ý nghĩa của "ẩn" ở trên), tức là không có tường minh mmap/ mremaptừ quy trình.

Rất khó để tìm thấy thông tin chi tiết xác nhận điều này. Tôi khuyên bạn nên hiểu Trình quản lý bộ nhớ ảo Linux của Mel Gorman. Tôi cho rằng câu trả lời nằm trong Phần 4.6.1 Xử lý lỗi trang , ngoại trừ "Vùng không hợp lệ nhưng nằm bên cạnh một vùng có thể mở rộng như ngăn xếp" và hành động tương ứng "Mở rộng vùng và phân bổ trang". Xem thêm D.5.2 Mở rộng ngăn xếp .

Các tài liệu tham khảo khác về quản lý bộ nhớ Linux (nhưng hầu như không có gì về ngăn xếp):

EDIT 2: Việc triển khai này có một nhược điểm: trong các trường hợp góc, có thể không phát hiện được va chạm heap stack, ngay cả trong trường hợp stack sẽ lớn hơn giới hạn! Lý do là một ghi trong một biến trong ngăn xếp có thể kết thúc trong bộ nhớ heap được phân bổ, trong trường hợp đó không có lỗi trang và kernel không thể biết rằng ngăn xếp cần được mở rộng. Xem ví dụ của tôi trong cuộc thảo luận Xung đột stack-heap im lặng trong GNU / Linux Tôi đã bắt đầu trong danh sách trợ giúp gcc. Để tránh điều đó, trình biên dịch cần thêm một số mã tại hàm gọi; điều này có thể được thực hiện với -fstack-checkGCC (xem câu trả lời của Ian Lance Taylor và trang người đàn ông GCC để biết chi tiết).


Đó dường như là câu trả lời chính xác cho câu hỏi của tôi. Nhưng nó làm tôi bối rối hơn. Khi nào cuộc gọi mremap sẽ được kích hoạt? Nó sẽ là một tòa nhà cao tầng được xây dựng trong chương trình?
A-mốt

@amos Tôi giả sử rằng cuộc gọi mremap sẽ được kích hoạt nếu cần tại một cuộc gọi hàm hoặc khi alloca () được gọi.
vinc17

Có lẽ sẽ là một ý tưởng tốt khi đề cập đến mmap là gì, cho những người không biết.
Faheem Mitha

@FaheemMitha Tôi đã thêm một số thông tin. Đối với những người không biết mmap là gì, hãy xem Câu hỏi thường gặp về bộ nhớ được đề cập ở trên. Ở đây, đối với ngăn xếp, nó sẽ là "ánh xạ ẩn danh" để không gian không sử dụng sẽ không chiếm bất kỳ bộ nhớ vật lý nào, nhưng theo giải thích của Mel Gorman, hạt nhân thực hiện ánh xạ (bộ nhớ ảo) và phân bổ vật lý cùng một lúc .
vinc17

1
@max Tôi đã thử chương trình của OP với ulimit -s10240, như trong điều kiện của OP và tôi nhận được SIGSEGV như mong đợi (đây là yêu cầu của POSIX: "Nếu vượt quá giới hạn này, SIGSEGV sẽ được tạo cho luồng. "). Tôi nghi ngờ một lỗi trong kernel của OP.
vinc17

6

Nhân Linux 4.2

Chương trình thử nghiệm tối thiểu

Sau đó, chúng tôi có thể kiểm tra nó với chương trình NASM 64 bit tối thiểu:

global _start
_start:
    sub rsp, 0x7FF000
    mov [rsp], rax
    mov rax, 60
    mov rdi, 0
    syscall

Đảm bảo rằng bạn tắt ASLR và xóa các biến môi trường vì chúng sẽ xuất hiện trên ngăn xếp và chiếm dung lượng:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
env -i ./main.out

Giới hạn ở đâu đó hơi thấp hơn tôi ulimit -s(8MiB đối với tôi). Có vẻ như điều này là do dữ liệu được chỉ định của Hệ thống V bổ sung ban đầu được đưa vào ngăn xếp ngoài môi trường: Các tham số dòng lệnh Linux 64 trong hội | Tràn ngăn xếp

Nếu bạn nghiêm túc về điều này, TODO tạo một hình ảnh initrd tối thiểu bắt đầu viết từ đỉnh ngăn xếp và đi xuống, sau đó chạy nó với QEMU + GDB . Đặt một dprintfvòng lặp in địa chỉ ngăn xếp và điểm dừng tại acct_stack_growth. Nó sẽ rất vinh quang.

Liên quan:


2

Theo mặc định, kích thước ngăn xếp tối đa được định cấu hình là 8 MB cho mỗi quy trình,
nhưng nó có thể được thay đổi bằng cách sử dụng ulimit:

Hiển thị mặc định trong kB:

$ ulimit -s
8192

Đặt thành không giới hạn:

ulimit -s unlimited

ảnh hưởng đến lớp vỏ và lớp vỏ hiện tại và các quá trình con của chúng.
( ulimitlà một lệnh dựng sẵn shell)

Bạn có thể hiển thị phạm vi địa chỉ ngăn xếp thực tế được sử dụng với:
cat /proc/$PID/maps | grep -F '[stack]'
trên Linux.


Vì vậy, khi một chương trình được tải bởi trình bao hiện tại, HĐH sẽ tạo một phân đoạn bộ nhớ ulimit -sKB hợp lệ cho chương trình. Trong trường hợp của tôi, nó là 10240KB. Nhưng khi tôi khai báo một mảng cục bộ char a[10240*1024]và thiết lập a[0]=1, chương trình thoát chính xác. Tại sao?
A-mốt

Cố gắng thiết lập các yếu tố cuối cùng quá. Và chắc chắn rằng chúng không được tối ưu hóa.
vinc17

@amos Tôi nghĩ rằng vinc17 có nghĩa là bạn đã đặt tên cho vùng nhớ không phù hợp với ngăn xếp trong chương trình của mình , nhưng vì bạn không thực sự truy cập vào phần không phù hợp , nên máy không bao giờ nhận thấy rằng - nó không thậm chí có được thông tin đó .
Volker Siegel

@amos Thử int n = 10240*1024; char a[n]; memset(a,'x',n);... lỗi seg.
goldilocks

2
@amos Vì vậy, như bạn có thể thấy, a[]chưa được phân bổ trong ngăn xếp 10MB của bạn. Trình biên dịch có thể đã thấy rằng không thể có một cuộc gọi đệ quy và đã thực hiện phân bổ đặc biệt, hoặc một cái gì đó khác như một ngăn xếp không liên tục hoặc một số lỗi.
vinc17
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.