Linux'ta yığın tahsisi nasıl çalışır?


18

İşletim sistemi, yığın veya başka bir şey için sabit miktarda geçerli sanal alan ayırıyor mu? Yalnızca büyük yerel değişkenler kullanarak yığın taşması üretebilir miyim?

CVarsayımı sınamak için küçük bir program yazdım . X86-64 CentOS 6.5 üzerinde çalışıyor.

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

Programı çalıştırmak &a[0] = f0ceabe0ve&a[n-1] = f16eabdf

Proc haritaları yığını gösterir: 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

Sonra artmaya çalıştım n = 11240 * 1024

Programı çalıştırmak &a[0] = b6b36690ve&a[n-1] = b763068f

Proc haritaları yığını gösterir: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -s10240bilgisayarımda yazdırır .

Gördüğünüz gibi, her iki durumda da yığın boyutu verilenden daha büyüktür ulimit -s. Ve yığın daha büyük yerel değişkenle büyür. Yığının üstü bir şekilde 3-5kB daha fazla &a[0](AFAIK kırmızı bölge 128B).

Peki bu yığın haritası nasıl tahsis edilir?

Yanıtlar:


14

Yığın bellek sınırının ayrılmadığı anlaşılıyor (yine de sınırsız yığınla yapılamadı). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting diyor ki:

C dil yığını büyümesi örtük bir mremap yapar. Mutlak garantiler istiyorsanız ve kenara yakın çalışırsanız, ihtiyacınız olduğunu düşündüğünüz en büyük boyut için yığınızı eşlemelisiniz. Tipik yığın kullanımı için bu çok önemli değil ama gerçekten önemsiyorsanız köşe kılıfı

Bununla birlikte, yığının eşleştirilmesi bir derleyicinin hedefi olacaktır (bunun için bir seçeneği varsa).

DÜZENLEME: x84_64 Debian makinesinde yapılan bazı testlerden sonra, yığının herhangi bir sistem çağrısı olmadan büyüdüğünü gördüm (göre strace). Yani bu, çekirdeğin onu otomatik olarak büyüttüğü anlamına gelir (yukarıdaki "örtük" ifadesi budur), yani açık mmap/ mremapsüreçten.

Bunu doğrulayan ayrıntılı bilgiler bulmak oldukça zordu. Mel Gorman'ın Linux Sanal Bellek Yöneticisini Anlamanızı öneririm . Cevabın "Bölge geçerli değil, ancak yığın gibi genişletilebilir bir bölgenin yanında" ve ilgili eylemi "Bölgeyi genişlet ve bir sayfa ayır" dışında, Bölüm 4.6.1 Sayfa Hatasının İşlenmesi olduğunu varsayalım . Ayrıca bkz. D.5.2 Yığının Genişletilmesi .

Linux bellek yönetimi ile ilgili diğer referanslar (ancak yığınla ilgili neredeyse hiçbir şey yok):

DÜZENLEME 2: Bu uygulamanın bir dezavantajı vardır: Köşe durumlarda, yığının sınırdan daha büyük olması durumunda bile bir yığın-yığın çarpışması algılanmayabilir! Bunun nedeni, yığındaki bir değişkenteki bir yazmanın ayrılmış yığın bellekle sonuçlanabilmesidir, bu durumda sayfa hatası yoktur ve çekirdek yığının genişletilmesi gerektiğini bilemez. Tartışmada benim örneğe bakın GNU / Linux altında Sessiz yığını-yığın çarpışma I gcc yardım listesinde başladı. Bundan kaçınmak için, derleyicinin işlev çağrısında bir kod eklemesi gerekir; bu -fstack-checkGCC için yapılabilir (ayrıntılar için Ian Lance Taylor'ın yanıtı ve GCC kılavuz sayfasına bakınız).


Sorumun cevabı doğru görünüyor. Ama bu beni daha çok karıştırıyor. Mremap çağrısı ne zaman tetiklenir? Programa dahil edilmiş bir sistem çağrısı olacak mı?
Amos

@ amos Gerekirse veya alloca () çağrıldığında mremap çağrısının tetikleneceğini varsayıyorum.
vinc17

Bilmeyenler için mmap'in ne olduğunu söylemek iyi bir fikir olabilir.
Faheem Mitha

@FaheemMitha Biraz bilgi ekledim. Mmap'in ne olduğunu bilmeyenler için yukarıda belirtilen bellek SSS bölümüne bakın. Burada, yığın için, "anonim haritalama" olurdu, böylece kullanılmayan alan herhangi bir fiziksel bellek almazdı, ancak Mel Gorman tarafından açıklandığı gibi, çekirdek haritalamayı (sanal bellek) ve fiziksel tahsisi aynı anda yapar .
vinc17

1
@max OP'nin programı ulimit -sOP'nin koşulları altında olduğu gibi 10240 vererek denedim ve beklediğim gibi bir SIGSEGV alıyorum (POSIX için gerekli olan budur: "Bu sınır aşılırsa, iş parçacığı için SIGSEGV üretilecektir. "). OP'nin çekirdeğinde bir hata olduğundan şüpheleniyorum.
vinc17

6

Linux çekirdeği 4.2

Minimum test programı

Daha sonra minimal bir NASM 64 bit programı ile test edebiliriz:

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

ASLR'yi kapattığınızdan ve ortam değişkenlerini kaldırdığınızdan emin olun, çünkü bunlar yığına geçer ve yer kaplar:

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

Sınır benim ulimit -s(8MiB benim için) biraz altında bir yerde . Bunun nedeni, ortama ek olarak başlangıçta yığına yerleştirilen fazladan Sistem V tarafından belirtilen verilerden kaynaklanıyor: Assembly'de Linux 64 komut satırı parametreleri | Yığın Taşması

Bu konuda ciddi iseniz, TODO , yığının üstünden yazmaya başlayıp aşağı inen ve ardından QEMU + GDB ile çalıştıran minimal bir initrd görüntü oluşturur . Yığın adresini ve bir kesme noktasını yazdırarak döngüye bir koyun . Muhteşem olacak.dprintfacct_stack_growth

İlişkili:


2

Varsayılan olarak, maksimum yığın boyutu işlem başına 8 MB olacak şekilde yapılandırılmıştır,
ancak aşağıdakiler kullanılarak değiştirilebilir ulimit:

Varsayılan değer kB olarak gösteriliyor:

$ ulimit -s
8192

Sınırsız olarak ayarla:

ulimit -s unlimited

mevcut kabuk ve alt kabukları ve bunların alt süreçlerini etkiler.
( ulimitbir kabuk yerleşik komutudur)


cat /proc/$PID/maps | grep -F '[stack]'
Linux'ta : ile kullanılan gerçek yığın adres aralığını gösterebilirsiniz .


Dolayısıyla, bir program geçerli kabuk tarafından yüklendiğinde, işletim sistemi ulimit -sKB'nin bir bellek bölümünü program için geçerli hale getirir. Benim durumumda 10240KB. Ancak yerel bir dizi bildirip char a[10240*1024]ayarladığımda a[0]=1program doğru şekilde çıkıyor. Neden?
Amos

Son elemanı da ayarlamaya çalışın. Ve bunların optimize edilmediğinden emin olun.
vinc17

@amos Sana yığın uygun olmaz bir bellek bölgesini adlı bu ne vinc17 yoludur düşünüyorum programınızda , ama aslında yok gibi erişim uymuyor kısmen - makine olduğunu fark asla öyle değil bu bilgiyi bile alabilirsiniz .
Volker Siegel

@ amos Deneyin int n = 10240*1024; char a[n]; memset(a,'x',n);... seg hatası.
goldilocks

2
@amos Gördüğünüz gibi a[]10MB yığına tahsis edilmedi. Derleyici, yinelemeli bir çağrı yapılamayacağını ve özel ayırma veya süreksiz bir yığın veya bir miktar dolaylama gibi başka bir şey yaptığını görmüş olabilir.
vinc17
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.