Brk () sistem çağrısı ne yapar?


183

Linux programcıları el kitabına göre:

brk () ve sbrk (), işlemin veri segmentinin sonunu tanımlayan program sonu konumunu değiştirir.

Veri segmenti burada ne anlama geliyor? Yalnızca veri segmenti veya veri, BSS ve yığın birleştirildi mi?

Wiki'ye göre:

Bazen veriler, BSS ve yığın alanları topluca "veri segmenti" olarak adlandırılır.

Yalnızca veri segmentinin boyutunu değiştirmek için bir neden göremiyorum. Veri ise, BSS ve yığın toplu olarak, yığın daha fazla alan alacağından mantıklıdır.

Bu da beni ikinci soruma getiriyor. Şimdiye kadar okuduğum tüm makalelerde yazar, yığının yukarı doğru büyüdüğünü ve yığının aşağı doğru büyüdüğünü söylüyor. Ama açıklamamış oldukları şey, yığın yığın ve yığın arasındaki tüm alanı kapladığında ne olur?

resim açıklamasını buraya girin


1
Peki alanınız bittiğinde ne yaparsınız? HDD'ye takas edersiniz. Alan kullandığınızda, başka tür bilgiler için serbest bırakırsınız.
Igoris Azanovas

29
@Igoris: Fiziksel belleği (gerektiğinde diske sanal bellek kullanarak takas edebilirsiniz) ve adres alanını karıştırıyorsunuz . Adres alanınızı doldurduğunuzda, bu adresleri ortadan kaldırmanız için hiçbir miktarda takas yapılmaz.
Daniel Pryden

7
Bir hatırlatma olarak, brk()sistem çağrısı montaj dilinde C'den daha yararlıdır. C'de, herhangi bir veri tahsis amacı malloc()yerine brk()kullanılmalıdır - ancak bu, önerilen soruyu hiçbir şekilde geçersiz kılmaz.
alecov

2
@Brian: Öbek, farklı boyutlarda ve hizalamalarda, boş havuzda vb. Bölgeleri işlemek için karmaşık bir veri yapısıdır. İş parçacığı yığınları her zaman tam sayfaların bitişik (sanal adres alanında) dizileridir. Çoğu işletim sisteminde yığınların, yığınların ve bellek eşlemeli dosyaların altında bir sayfa ayırıcı bulunur.
Ben Voigt

2
@Brian: Herhangi "Yığın" tarafından manipüle olduğunu söyledi Kim brk()ve sbrk()? Yığınlar, sayfa ayırıcı tarafından çok daha düşük bir düzeyde yönetilir.
Ben Voigt

Yanıtlar:


233

Gönderdiğiniz şemada, "sonu" - tarafından değiştirilen adres brkve sbrk- yığının üstündeki noktalı çizgidir.

sanal bellek düzeninin basitleştirilmiş görüntüsü

Okuduğunuz belgeler bunu "veri segmentinin" sonu olarak tanımlıyor çünkü geleneksel (önceden paylaşılan kütüphaneler, ön- mmap) Unix'te veri segmenti yığınla süreklidir; program başlamadan önce, çekirdek "metin" ve "veri" bloklarını sıfır adresinden başlayarak RAM'e yükler (aslında adres sıfırının biraz üzerindedir, böylece NULL işaretçisi gerçekten hiçbir şeye işaret etmez) ve kesme adresini veri segmentinin sonu. Daha mallocsonra yapılan ilk çağrı sbrk, kesmeyi yukarı taşımak ve veri segmentinin üst kısmı ile şemada gösterildiği gibi yeni, daha yüksek kesme adresi arasında yığın oluşturmak için kullanılır ve daha sonra kullanılması malloc, yığını daha büyük hale getirmek için kullanır gerektiği gibi.

Bu arada, yığın hafızanın en üstünde başlar ve büyür. Yığının daha büyük olması için açık sistem çağrılarına ihtiyacı yoktur; ya olabildiğince fazla RAM tahsis edilir (bu geleneksel yaklaşımdı) ya da yığının altında ayrılmış adreslerin bulunduğu bir bölge var, oraya yazma girişimi fark ettiğinde çekirdek otomatik olarak RAM'i ayırıyor (bu modern yaklaşımdır). Her iki durumda da, adres alanının altında yığın için kullanılabilecek bir "koruma" bölgesi olabilir veya olmayabilir. Bu bölge varsa (tüm modern sistemler bunu yapar) kalıcı olarak eşleştirilmez; eğer ikisinden biriyığın veya yığın içine büyümeye çalışır, bir segmentasyon hatası alırsınız. Bununla birlikte, geleneksel olarak, çekirdek bir sınırı zorlama girişiminde bulunmamıştır; yığın yığının içine büyüyebilir veya yığın yığının içine büyüyebilir ve her iki şekilde de birbirlerinin verilerinin üzerine yazılabilir ve program çökebilir. Çok şanslı olsaydın hemen çökecekti.

Bu diyagramdaki 512GB sayısının nereden geldiğinden emin değilim. Orada sahip olduğunuz çok basit bellek haritasına aykırı olan 64 bitlik sanal adres alanı anlamına geliyor. Gerçek bir 64 bit adres alanı daha çok şuna benzer:

daha az basitleştirilmiş adres alanı

              Legend:  t: text, d: data, b: BSS

Bu uzaktan ölçeklenmek değildir ve herhangi bir işletim sisteminin tam olarak nasıl yaptığını yorumlamamalıdır (çizdikten sonra Linux'un çalıştırılabilir dosyayı aslında düşündüğümden daha fazla adrese yakınlaştırdığını keşfettim ve paylaşılan kütüphaneler şaşırtıcı derecede yüksek adreslerde). Bu diyagram siyah bölgeler bağlanılmayan - herhangi bir erişim acil segfault neden olur - ve bunlar devasa gri alanlarda göreli. Açık gri bölgeler program ve onun paylaşılan kütüphaneleridir (düzinelerce paylaşılan kütüphane olabilir); her birinin bağımsızmetin ve veri segmenti (ve global veri de içeren "ancak" bss "segmenti, ancak diskteki yürütülebilir dosyada veya kütüphanede yer kaplamak yerine tamamen sıfır olarak başlatılır). Yığın artık yürütülebilir dosyanın veri segmentiyle sürekli değil - bu şekilde çizdim, ancak en azından Linux bunu yapmıyor gibi görünüyor. Yığın artık sanal adres alanının üst kısmına sabitlenmemiştir ve yığın ile yığın arasındaki mesafe o kadar büyüktür ki onu geçme konusunda endişelenmenize gerek yoktur.

Mola hala yığının üst sınırıdır. Ancak, göstermediğim şey, siyah mmapyerine bir yerde düzinelerce bağımsız bellek tahsisi yapılabileceğiydi brk. (OS, brkçarpışmamak için bunları alandan uzak tutmaya çalışacaktır .)


7
Ayrıntılı bir açıklama için +1. Eğer biliyor musunuz mallochala dayanıyor brko kullanıyorsa veya mmap"geri vermek" ayrı hafıza blokları muktedir?
Anders Abel

18
Spesifik uygulamaya bağlıdır, ancak IIUC birçok akım alanı küçük tahsisler mallociçin brkalanı ve mmapbüyük (örneğin,> 128K) tahsisler için alanı kullanır. Örneğin, bkz malloc(3). Linux kılavuzundaki MMAP_THRESHOLD .
zwol

1
Gerçekten iyi bir açıklama. Ancak, Stack'in artık Sanal adres alanının üstünde yer almadığını söylediğiniz gibi. Bu yalnızca 64 bit adres alanı için mi, yoksa 32 bit adres alanı için de geçerlidir. Yığın adres alanının en üstünde bulunursa, anonim bellek haritaları nerede olur? Yığından hemen önce sanal adres alanının en üstündedir.
nik

3
@Nikhil: karmaşık. Çoğu 32-bit sistem, yığını , genellikle tam adres alanının sadece 2 veya 3G'si olan kalan mod çekirdeği için ayrılmış olan kullanıcı modu adres alanının en üstüne yerleştirir . Şu anda böyle bir şey düşünemiyorum ama hepsini bilmiyorum. Çoğu 64 bit CPU aslında 64 bit alanın tamamını kullanmanıza izin vermez; adresin yüksek 10 ila 16 biti tamamen sıfır veya hepsi bir olmalıdır. Yığın genellikle kullanılabilir düşük adreslerin üst kısmına yakın yerleştirilir. Size bir kural veremem mmap; son derece işletim sistemine bağımlıdır.
zwol

3
@RiccardoBestetti Adres alanını boşa harcar , ancak bu zararsızdır - 64 bitlik sanal adres alanı o kadar büyüktür ki, bir saniyede bir gigabayttan yakarsanız, 500 yıl geçmesi yine de sürer. [1] Çoğu işlemci, 2 ^ 48 ila 2 ^ 53 bitten fazla sanal adres kullanılmasına bile izin vermez (bildiğim tek istisna karma sayfa tablosu modunda POWER4'tür). Fiziksel RAM'i boşa harcamaz; kullanılmayan adresler RAM'e atanmaz.
zwol

26

Minimum çalıştırılabilir örnek

Brk () sistem çağrısı ne yapar?

Çekirdekten, yığın adı verilen bitişik bir bellek yığınını okumanıza ve yazmanıza izin vermesini ister.

Eğer sormazsanız, sizi parçalayabilir.

Olmadan brk:

#define _GNU_SOURCE
#include <unistd.h>

int main(void) {
    /* Get the first address beyond the end of the heap. */
    void *b = sbrk(0);
    int *p = (int *)b;
    /* May segfault because it is outside of the heap. */
    *p = 1;
    return 0;
}

İle brk:

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b = sbrk(0);
    int *p = (int *)b;

    /* Move it 2 ints forward */
    brk(p + 2);

    /* Use the ints. */
    *p = 1;
    *(p + 1) = 2;
    assert(*p == 1);
    assert(*(p + 1) == 2);

    /* Deallocate back. */
    brk(b);

    return 0;
}

GitHub akış yukarı .

Yukarıdakiler, yeni bir sayfaya brkçarpmayabilir ve olmadan bile segfault yapamaz , bu nedenle 16MiB'yi ayıran ve agresif olmayan brk:

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b;
    char *p, *end;

    b = sbrk(0);
    p = (char *)b;
    end = p + 0x1000000;
    brk(end);
    while (p < end) {
        *(p++) = 1;
    }
    brk(b);
    return 0;
}

Ubuntu 18.04'te test edildi.

Sanal adres alanı görselleştirme

Önce brk:

+------+ <-- Heap Start == Heap End

Sonra brk(p + 2):

+------+ <-- Heap Start + 2 * sizof(int) == Heap End 
|      |
| You can now write your ints
| in this memory area.
|      |
+------+ <-- Heap Start

Sonra brk(b):

+------+ <-- Heap Start == Heap End

Adres alanlarını daha iyi anlamak için, disk belleği hakkında bilgi sahibi olmalısınız: x86 disk belleği nasıl çalışır? .

Neden ikisine de ihtiyacımız var brkve sbrk?

brkElbette sbrk+ ofset hesaplamaları ile uygulanabilir , her ikisi de sadece kolaylık sağlamak için mevcuttur.

Arka uçta, Linux çekirdeği v5.0, brkher ikisini de uygulamak için kullanılan tek bir sistem çağrısına sahiptir: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23

12  common  brk         __x64_sys_brk

brkPOSIX?

brkeskiden POSIX idi, ancak POSIX 2001'de kaldırıldı, bu nedenle _GNU_SOURCEglibc sarmalayıcısına erişme ihtiyacı .

Kaldırma işlemi büyük olasılıkla mmap, birden fazla aralığın atanmasına izin veren bir üst küme ve daha fazla ayırma seçeneğinden kaynaklanmaktadır.

Bugün brkyerine mallocya da yerine kullanmanız gereken geçerli bir durum olmadığını düşünüyorum mmap.

brk vs malloc

brkeski bir uygulama olasılığıdır malloc.

mmapşu anda tüm POSIX sistemlerinin uygulamak için kullandığı daha yeni ve daha güçlü bir mekanizmadır malloc. İşte asgari çalıştırılabilir mmapbellek ayırma örneği .

Karıştırıp brkyanlış yerleştirebilir miyim ?

Eğer mallocuygulandıysa brk, bunun nasıl bir şeyleri havaya uçuramayacağı hakkında hiçbir fikrim yok, çünkü brksadece tek bir bellek aralığını yönetiyor.

Ancak glibc belgelerinde bu konuda bir şey bulamadım, örneğin:

Muhtemelen orada mmapkullanıldığından beri işler muhtemelen orada çalışacaktır malloc.

Ayrıca bakınız:

Daha fazla bilgi

Dahili olarak, işlemin bu kadar fazla belleğe sahip olup olamayacağına karar verir ve bu kullanım için bellek sayfalarını belirler .

Bu, yığının yığınla nasıl karşılaştırıldığını açıklar: x86 derlemesindeki kayıtlarda kullanılan push / pop yönergelerinin işlevi nedir?


4
Yazmak piçin bir işaretçi olduğundan int, bu olmamalı mıydı brk(p + 2);?
Johan Boulé

Küçük not: Agresif versiyonun for döngüsündeki ifade muhtemelen*(p + i) = 1;
lima.sierra

Bu arada, neden brk(p + 2)sadece artırmak yerine kullanmak zorundayız sbrk(2)? Brk gerçekten gerekli mi?
Yi Lin Liu

1
@YiLinLiu Tek bir çekirdek arka uç ( brksyscall) için sadece iki benzer C ön ucu olduğunu düşünüyorum . brkönceden ayrılmış yığını geri yüklemek için biraz daha uygun olur.
Ciro Santilli 法轮功 17 病 六四 事件 法轮功

1
@CiroSantilli 中心 改造 中心 996ICU 六四 事件 int boyutu 4 bayt ve int * boyutu 4 bayt (32 bitlik bir makinede) olarak göz önüne alındığında, bunun yerine sadece 4 bayt artırılmaması gerektiğini merak ediyordum. 8 - (2 * int boyut)). Bir sonraki kullanılabilir yığın depolamaya işaret etmemelidir - 4 bayt uzakta olacak (8 değil). Burada bir şey eksikse beni düzeltin.
Saket Sharad

10

Sen kullanabilirsiniz brkve sbrkkendinizi "malloc havai" herkes her zaman hakkında şikayetçi önlemek için. Ancak bu yöntemi kolayca birlikte kullanamazsınız, bu mallocyüzden sadece freehiçbir şeye ihtiyacınız olmadığında uygundur . Çünkü yapamazsın. Ayrıca, mallocdahili olarak kullanılabilecek kütüphane çağrılarından kaçınmalısınız . Yani. strlenmuhtemelen güvenli, ama fopenmuhtemelen değil.

Çağrı sbrksadece çağırır gibi malloc. Geçerli kesmeye bir işaretçi döndürür ve kesmeyi bu miktar kadar artırır.

void *myallocate(int n){
    return sbrk(n);
}

Özgür birey tahsisleri (hayır olmadığından edemesek malloc-havai unutmayın), sen yapabilirsiniz özgür tüm alanı arayarak brkilk çağrı tarafından döndürülen değerle sbrkböylece brk geri sarma .

void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

Bölgeyi başlangıç ​​noktasına geri sararak en son bölgeyi atayarak bu bölgeleri yığınlayabilirsiniz.


Bir şey daha ...

sbrk2 karakterden daha kısa olduğu için kod golfünde de kullanışlıdır malloc.


7
-1 çünkü: malloc/ freekesinlikle can OS (ve yapılacak) vermek hafıza geri. İstediğiniz zaman bunu her zaman yapmayabilirler, ancak bu, sezgisel taramaların kullanım durumunuz için kusursuz bir şekilde ayarlanması meselesidir. Daha da önemlisi, herhangi bir programda çağırabilecek herhangi bir programda sıfır olmayan bir argümanla çağrı sbrkyapmak güvenli değildirmalloc ve neredeyse tüm C kütüphanesi işlevlerinin mallocdahili olarak çağrılmasına izin verilir . Kesinlikle olmayacak olanlar asenkron sinyal güvenli fonksiyonlardır.
zwol

Ve "güvenli değil" demekle, "programınız çökecektir."
zwol

Geri dönen bellek övünmesini kaldırmak için düzenledim ve dahili olarak kullanılan kütüphane işlevlerinin tehlikesinden bahsettim malloc.
luser droog

1
Süslü bellek ayırma yapmak istiyorsanız, ya bunu malloc'un üstüne ya da mmap'in üstüne dayandırın. Brk ve sbrk'a dokunmayın, geçmişten iyi olandan daha fazla zarar veren kalıntılar (hatta
manajlar

3
Bu aptalca. Çok sayıda küçük ayırma için malloc ek yükünü önlemek istiyorsanız, büyük bir ayırma yapın (sbrk değil malloc veya mmap ile ) ve kendiniz yapın. İkili ağacınızın düğümlerini bir dizide tutarsanız, 64b işaretçileri yerine 8b veya 16b dizinleri kullanabilirsiniz. Tüm düğümleri silmeye hazır olana kadar herhangi bir düğümü silmeniz gerekmediğinde bu harika çalışır . (örneğin anında sıralanmış bir sözlük oluşturmak.) kullanarak sbrkbu için sadece kod golf için kullanışlı elle kullanırken, çünkü mmap(MAP_ANONYMOUS)daha iyi kaynak kod boyutu hariç her şekilde olduğunu.
Peter Cordes

3

Özel olarak belirlenmiş anonim bir özel bellek eşlemesi vardır (geleneksel olarak veri / bss'nin hemen dışında bulunur, ancak modern Linux aslında konumu ASLR ile ayarlayacaktır). Prensipte, oluşturabileceğiniz diğer eşlemelerden daha iyi değildir mmap, ancak Linux, bu eşlemenin sonunu ( brksistem çağrısı kullanarak) ortaya çıkan mmapveya ne mremapolacağına göre düşük kilitleme maliyetiyle yukarı doğru genişletmeyi mümkün kılan bazı optimizasyonlara sahiptir . Bu malloc, ana yığını uygularken uygulamaların kullanılmasını cazip hale getirir .


Sen demek mümkün evet yukarı bu haritalama sonunu genişletmek?
zwol

Evet, sabit. Bunun için üzgünüm!
R .. GitHub BUZA YARDIMCI DURDUR

0

İkinci sorunuza cevap verebilirim. Malloc başarısız olur ve bir boş gösterici döndürür. Bu nedenle, belleği dinamik olarak ayırırken her zaman boş gösterici olup olmadığını kontrol edersiniz.


peki brk ve sbrk ne işe yarar?
nik

3
@NikhilRathod: ve / veya kaputun altında malloc()kullanacak - ve kendi özelleştirilmiş versiyonunuzu uygulamak istiyorsanız da yapabilirsiniz . brk()sbrk()malloc()
Daniel Pryden

@Daniel Pryden: brk ve sbrk, yukarıdaki diyagramda gösterildiği gibi yığın ve veri segmenti arasında olduğunda yığın üzerinde nasıl çalışabilir? Bunun işe yaraması için sonunda olmalı. Haklı mıyım?
nik

2
@Brian: Daniel, işletim sisteminin yığın işaretçisini değil yığın segmentini yönettiğini söyledi ... çok farklı şeyler. Mesele şu ki, yığın segmenti için sbrk / brk sistem çağrısı yok - Linux, yığın segmentinin sonuna yazma girişimleri üzerine sayfaları otomatik olarak tahsis ediyor.
Jim Balter

1
Ve Brian, sorunun yarısının yarısını cevapladın. Diğer yarısı, yer olmadığında yığının üzerine itmeye çalışırsanız ne olur ... bir segmentasyon hatası alırsınız.
Jim Balter

0

Yığın en son programın veri segmentine yerleştirilir. brk()yığının boyutunu değiştirmek (genişletmek) için kullanılır. Yığın daha fazla büyüyemezse, mallocçağrı başarısız olur.


Diyorsunuz ki internetteki tüm diyagramlar, sorumdaki gibi yanlış. Mümkünse lütfen beni doğru bir şemaya yönlendirebilir misiniz?
nik

2
@Nikkhil Bu diyagramın üst kısmının belleğin sonu olduğunu unutmayın . Yığının üstü , yığın büyüdükçe diyagram üzerinde aşağı doğru hareket eder . Yığının üstü , genişledikçe diyagram üzerinde yukarı doğru hareket eder .
Brian Gordon

0

Veri segmenti, belleğin açılışta yürütülebilir dosyadan okunan ve genellikle sıfır dolu olan tüm statik verilerinizi tutan kısmıdır.


Ayrıca, çöp olabilen başlatılmamış statik verileri (yürütülebilir dosyada mevcut değildir) tutar.
luser droog

Başlatılmamış statik veriler ( .bss), program başlatılmadan önce işletim sistemi tarafından sıfır bitine sıfırlanır; bu aslında C standardı tarafından garanti edilmektedir. Bazı gömülü sistemler rahatsız etmeyebilir, sanırım (hiç görmedim, ama tüm bu gömülü çalışmaz)
zwol

@zwol: Linux tarafından döndürülen sayfaları sıfırlamak için bir derleme zamanı seçeneği vardır mmap, ancak .bssyine de sıfırlanacağını varsayıyorum . BSS alanı muhtemelen bir programın bazı diziler istediği gerçeğini ifade etmenin en kompakt yoludur.
Peter Cordes

1
@PeterCordes C standardının söylediği şey, başlatıcı olmadan bildirilen global değişkenlerin sıfıra ilklendirilmiş gibi kabul edilmesidir. Bu tür değişkenleri .bsssıfırlayan ve sıfırlamayan AC uygulaması bu .bssnedenle uygun olmaz. Ancak hiçbir şey bir C uygulamasını hiç kullanmaya .bssveya hatta böyle bir şeye sahip olmaya zorlamaz.
zwol

@PeterCordes Ayrıca, "C uygulaması" ve program arasındaki çizgi çok bulanık olabilir, örneğin uygulamadan genellikle daha önce çalışan her yürütülebilir dosyaya statik olarak bağlı küçük bir kod parçası vardır main; bu kod .bss, çekirdeğin bunu yapmak yerine alanı sıfırlayabilir ve bu yine de uygun olur.
zwol

0

malloc belleği ayırmak için brk sistem çağrısını kullanır.

Dahil etmek

int main(void){

char *a = malloc(10); 
return 0;
}

strace ile bu basit programı çalıştırmak, brk sistemi çağırır.

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.