Apa yang dilakukan oleh panggilan sistem brk ()?

184

Menurut manual pemrogram Linux:

brk () dan sbrk () mengubah lokasi jeda program, yang menentukan akhir dari segmen data proses.

Apa arti segmen data di sini? Apakah hanya segmen data atau data, BSS, dan heap yang digabungkan?

Menurut wiki:

Terkadang data, BSS, dan area tumpukan secara kolektif disebut sebagai "segmen data".

Saya tidak melihat alasan untuk mengubah ukuran hanya segmen data. Jika ini adalah data, BSS dan heap secara kolektif maka masuk akal karena heap akan mendapatkan lebih banyak ruang.

Yang membawa saya ke pertanyaan kedua saya. Dalam semua artikel yang saya baca sejauh ini, penulis mengatakan bahwa tumpukan tumbuh ke atas dan tumpukan tumbuh ke bawah. Tapi apa yang tidak mereka jelaskan adalah apa yang terjadi ketika tumpukan menempati semua ruang antara tumpukan dan tumpukan?

masukkan deskripsi gambar di sini

nik
sumber
1
Jadi apa yang Anda lakukan ketika Anda kehabisan ruang? Anda bertukar ke HDD. Ketika Anda telah menggunakan ruang, Anda melepaskannya untuk jenis informasi lain.
Igoris Azanovas
29
@ Igoris: Memori fisik Anda membingungkan (yang dapat ditukar ke disk sesuai kebutuhan, menggunakan memori virtual) dan ruang alamat . Ketika Anda mengisi ruang alamat Anda, tidak ada jumlah pertukaran yang akan memberi Anda kembali alamat-alamat di tengah.
Daniel Pryden
7
Sama seperti pengingat, brk()pemanggilan sistem lebih berguna dalam bahasa assembly daripada dalam C. Dalam C, malloc()harus digunakan alih-alih brk()untuk tujuan alokasi data apa pun - tetapi ini tidak membatalkan pertanyaan yang diajukan dengan cara apa pun.
alecov
2
@Brian: Heap adalah struktur data yang kompleks untuk menangani daerah dengan berbagai ukuran dan keberpihakan, kumpulan bebas, dll. Tumpukan thread selalu bersebelahan (dalam ruang alamat virtual) urutan halaman lengkap. Di sebagian besar OS, ada pengalokasi halaman yang mendasari tumpukan, tumpukan, dan file yang dipetakan memori.
Ben Voigt
2
@ Brian: Siapa bilang ada "tumpukan" dimanipulasi oleh brk()dan sbrk()? Tumpukan dikelola oleh pengalokasi halaman, pada tingkat yang jauh lebih rendah.
Ben Voigt

Jawaban:

233

Dalam diagram yang Anda poskan, "break" - alamat yang dimanipulasi oleh brkdan sbrk- adalah garis putus-putus di bagian atas tumpukan.

gambar yang disederhanakan dari tata letak memori virtual

Dokumentasi yang Anda baca menggambarkan ini sebagai akhir dari "segmen data" karena dalam tradisional (pra-berbagi-perpustakaan, pra- mmap) Unix segmen data terus menerus dengan heap; sebelum program dimulai, kernel akan memuat blok "teks" dan "data" ke dalam RAM mulai dari alamat nol (sebenarnya sedikit di atas alamat nol, sehingga penunjuk NULL benar-benar tidak menunjuk ke apa pun) dan mengatur alamat break ke akhir segmen data. Panggilan pertama untuk mallockemudian akan digunakan sbrkuntuk memindahkan break up dan membuat tumpukan di antara bagian atas segmen data dan yang baru, alamat break yang lebih tinggi, seperti yang ditunjukkan dalam diagram, dan penggunaan selanjutnya mallocakan menggunakannya untuk membuat heap lebih besar seperlunya.

Sementara itu, tumpukan mulai di bagian atas memori dan tumbuh turun. Tumpukan tidak perlu pemanggilan sistem yang eksplisit untuk membuatnya lebih besar; baik itu dimulai dengan RAM yang dialokasikan sebanyak mungkin seperti yang pernah ada (ini adalah pendekatan tradisional) atau ada wilayah alamat yang dicadangkan di bawah tumpukan, di mana kernel secara otomatis mengalokasikan RAM ketika memperhatikan upaya untuk menulis di sana (ini adalah pendekatan modern). Apa pun itu, mungkin ada atau tidak ada wilayah "penjaga" di bagian bawah ruang alamat yang dapat digunakan untuk tumpukan. Jika wilayah ini ada (semua sistem modern melakukan ini), ia tidak dipetakan secara permanen; jika salahtumpukan atau tumpukan mencoba tumbuh ke dalamnya, Anda mendapatkan kesalahan segmentasi. Namun, secara tradisional, kernel tidak berusaha untuk menegakkan batasan; tumpukan bisa tumbuh menjadi tumpukan, atau tumpukan itu bisa tumbuh ke tumpukan, dan baik cara mereka akan mencoret-coret data masing-masing dan program akan macet. Jika Anda sangat beruntung itu akan langsung crash.

Saya tidak yakin dari mana angka 512GB dalam diagram ini berasal. Ini menyiratkan ruang alamat virtual 64-bit, yang tidak konsisten dengan peta memori yang sangat sederhana yang Anda miliki di sana. Ruang alamat 64-bit yang nyata terlihat lebih seperti ini:

ruang alamat yang kurang disederhanakan

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

Ini bukan untuk skala jauh, dan itu tidak boleh ditafsirkan sebagai persis bagaimana OS yang diberikan melakukan hal-hal (setelah saya menggambar saya menemukan bahwa Linux sebenarnya menempatkan executable lebih dekat ke alamat nol daripada yang saya kira, dan perpustakaan bersama di alamat yang sangat tinggi). Wilayah hitam dari diagram ini tidak dipetakan - akses apa pun menyebabkan segfault langsung - dan mereka relatif besar terhadap area abu-abu. Daerah abu-abu terang adalah program dan pustaka bersama (bisa ada lusinan pustaka bersama); masing-masing memiliki yang independensegmen teks dan data (dan segmen "bss", yang juga berisi data global tetapi diinisialisasi ke semua-bit-nol daripada mengambil ruang di executable atau library pada disk). Tumpukan tidak lagi harus terus-menerus dengan segmen data yang dapat dieksekusi - saya menggambar seperti itu, tetapi sepertinya Linux, setidaknya, tidak melakukan itu. Tumpukan tidak lagi dipatok ke atas ruang alamat virtual, dan jarak antara tumpukan dan tumpukan sangat besar sehingga Anda tidak perlu khawatir tentang melewatinya.

Break masih merupakan batas atas heap. Namun, apa yang tidak saya tunjukkan adalah bahwa mungkin ada lusinan alokasi memori independen di sana dalam warna hitam di suatu tempat, dibuat dengan mmapalih - alih brk. (OS akan mencoba untuk menjauhkan ini dari brkdaerah sehingga mereka tidak bertabrakan.)

zwol
sumber
7
+1 untuk penjelasan terperinci. Apakah Anda tahu apakah mallocmasih mengandalkan brkatau jika digunakan mmapuntuk dapat "mengembalikan" blok memori yang terpisah?
Anders Abel
18
Itu tergantung pada implementasi spesifik, tetapi IIUC banyak saat ini mallocmenggunakan brkarea untuk alokasi kecil dan individu mmapuntuk alokasi besar (katakanlah,> 128K). Lihat, misalnya, diskusi tentang MMAP_THRESHOLD di malloc(3)manual Linux .
zwol
1
Sungguh penjelasan yang bagus. Tetapi ketika Anda mengatakan bahwa Stack tidak lagi duduk di bagian atas ruang alamat Virtual. Apakah ini benar hanya untuk ruang alamat 64 bit atau benar bahkan untuk ruang alamat 32 bit. Dan jika tumpukan berada di bagian atas ruang alamat, di mana peta memori anonim terjadi? Apakah itu di bagian atas ruang alamat virtual tepat sebelum tumpukan.
nik
3
@ Nikhil: rumit. Sebagian besar sistem 32-bit menempatkan stack di bagian paling atas ruang mode pengguna, yang seringkali hanya 2 atau 3G lebih rendah dari ruang alamat lengkap (ruang yang tersisa dicadangkan untuk kernel). Saat ini saya tidak bisa memikirkan satu yang tidak, tetapi saya tidak tahu semuanya. Sebagian besar CPU 64-bit tidak benar-benar membiarkan Anda menggunakan seluruh ruang 64-bit; 10 hingga 16 bit alamat harus all-zero atau all-one. Tumpukan umumnya ditempatkan di dekat bagian atas alamat rendah yang dapat digunakan. Saya tidak bisa memberi Anda aturan untuk mmap; ini sangat tergantung pada OS.
zwol
3
@RiccardoBestetti Membuang-buang ruang alamat , tapi itu tidak berbahaya - ruang alamat virtual 64-bit sangat besar sehingga jika Anda membakar satu gigabyte setiap detik , Anda masih perlu waktu 500 tahun untuk kehabisan. [1] Sebagian besar prosesor bahkan tidak mengizinkan penggunaan lebih dari 2 ^ 48 hingga 2 ^ 53 bit alamat virtual (satu-satunya pengecualian yang saya tahu adalah POWER4 dalam mode tabel halaman hash). Itu tidak membuang RAM fisik; alamat yang tidak digunakan tidak ditugaskan untuk RAM.
zwol
26

Contoh runnable minimal

Apa yang dilakukan dengan panggilan sistem brk ()?

Meminta kernel untuk memberi Anda Anda membaca dan menulis ke sepotong memori yang berdekatan yang disebut heap.

Jika Anda tidak bertanya, itu mungkin akan membuat kesalahan Anda.

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

Dengan 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 hulu .

Hal di atas mungkin tidak mengenai halaman baru dan bahkan tanpa segfault brk, jadi di sini adalah versi yang lebih agresif yang mengalokasikan 16MiB dan sangat mungkin untuk melakukan segmentasi tanpa 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;
}

Diuji pada Ubuntu 18.04.

Visualisasi ruang alamat virtual

Sebelum brk:

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

Setelah brk(p + 2):

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

Setelah brk(b):

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

Untuk lebih memahami ruang alamat, Anda harus membiasakan diri dengan paging: Bagaimana cara kerja paging x86? .

Mengapa kita membutuhkan keduanya brkdan sbrk?

brktentu saja dapat diimplementasikan dengan sbrkperhitungan + offset, keduanya ada hanya untuk kenyamanan.

Di backend, kernel Linux v5.0 memiliki panggilan sistem tunggal brkyang digunakan untuk mengimplementasikan keduanya: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23

12  common  brk         __x64_sys_brk

Apakah brkPOSIX?

brkdulu POSIX, tetapi dihapus pada POSIX 2001, sehingga kebutuhan untuk _GNU_SOURCEmengakses pembungkus glibc.

Penghapusan ini kemungkinan disebabkan oleh pengantar mmap, yang merupakan superset yang memungkinkan beberapa rentang untuk dialokasikan dan lebih banyak opsi alokasi.

Saya pikir tidak ada kasus yang valid di mana Anda harus menggunakan, brkbukan mallocatau mmapsaat ini.

brk vs. malloc

brkadalah salah satu kemungkinan lama implementasi malloc.

mmapadalah mekanisme baru yang lebih kuat yang kemungkinan besar semua sistem POSIX saat ini gunakan untuk mengimplementasikan malloc. Berikut adalah contoh alokasi memori runnable minimalmmap .

Bisakah saya campur brkdan malloc?

Jika Anda mallocdiimplementasikan dengan brk, saya tidak tahu bagaimana itu mungkin tidak dapat meledakkan sesuatu, karena brkhanya mengelola satu rentang memori.

Namun saya tidak dapat menemukan apa pun tentang itu di dokumen glibc, misalnya:

Hal kemungkinan hanya bekerja di sana saya kira karena mmapkemungkinan digunakan untuk malloc.

Lihat juga:

Info lebih lanjut

Secara internal, kernel memutuskan apakah proses dapat memiliki banyak memori, dan menyediakan halaman memori untuk penggunaan itu.

Ini menjelaskan bagaimana tumpukan dibandingkan dengan tumpukan: Apa fungsi instruksi push / pop yang digunakan pada register dalam rakitan x86?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
4
Karena ppointer untuk diketik int, bukankah ini seharusnya brk(p + 2);?
Johan Boulé
Catatan kecil: Ekspresi dalam for-loop dari versi agresif mungkin seharusnya*(p + i) = 1;
lima.sierra
Ngomong-ngomong, mengapa kita perlu menggunakan brk(p + 2)alih - alih hanya meningkatkannya sbrk(2)? Apakah brk benar-benar diperlukan?
Yi Lin Liu
1
@YiLinLiu Saya pikir ini hanya dua frontend C yang sangat mirip untuk satu kernel backend ( brksyscall). brksedikit lebih nyaman untuk mengembalikan tumpukan yang sebelumnya dialokasikan.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 996ICU 六四 事件 Mempertimbangkan ukuran int menjadi 4 byte dan ukuran int * sebagai 4 byte (pada mesin 32 bit), saya bertanya-tanya apakah seharusnya tidak bertambah dengan hanya 4 byte (bukan 8 - (2 * sizeof int)). Seharusnya tidak menunjuk ke penyimpanan tumpukan berikutnya yang tersedia - yang akan menjadi 4 byte (bukan 8). Perbaiki saya jika saya kehilangan sesuatu di sini.
Saket Sharad
10

Anda dapat menggunakan brkdan sbrkdiri Anda sendiri untuk menghindari "malloc overhead" yang selalu dikeluhkan semua orang. Tetapi Anda tidak dapat dengan mudah menggunakan metode ini dalam hubungannya dengan mallocsehingga hanya tepat ketika Anda tidak perlu freeapa - apa. Karena kamu tidak bisa. Anda juga harus menghindari panggilan perpustakaan apa pun yang dapat digunakan secara mallocinternal. Yaitu. strlenmungkin aman, tapi fopenmungkin juga tidak.

Panggil sbrkpersis seperti Anda akan menelepon malloc. Ini mengembalikan pointer ke break saat ini dan menambah break dengan jumlah itu.

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

Meskipun Anda tidak dapat membebaskan alokasi individual (karena tidak ada malloc-overhead , ingat), Anda dapat membebaskan seluruh ruang dengan menelepon brkdengan nilai yang dikembalikan oleh panggilan pertama sbrk, sehingga memutar brk .

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

Anda bahkan bisa menumpuk wilayah ini, membuang wilayah paling baru dengan memutar ulang jeda ke awal wilayah.


Satu hal lagi ...

sbrkjuga berguna dalam golf kode karena 2 karakter lebih pendek dari malloc.

luser droog
sumber
7
-1 karena: malloc/ freepaling pasti bisa (dan lakukan) mengembalikan memori ke OS. Mereka mungkin tidak selalu melakukannya ketika Anda menginginkannya, tetapi itu adalah masalah heuristik yang tidak disetel dengan sempurna untuk kasus penggunaan Anda. Lebih penting lagi, tidak aman untuk memanggil sbrkdengan argumen tidak nol dalam program apa pun yang mungkin pernah memanggil malloc- dan hampir semua fungsi pustaka C diizinkan untuk memanggil secara mallocinternal. Satu-satunya yang pasti tidak akan adalah fungsi async-signal-safe .
zwol
Dan dengan "tidak aman", maksud saya "program Anda akan macet."
zwol
Saya telah mengedit untuk menghapus memori yang kembali membanggakan, dan menyebutkan bahaya fungsi perpustakaan menggunakan internal malloc.
luser droog
1
Jika Anda ingin melakukan alokasi memori yang bagus, baik basiskannya di atas malloc, atau di atas mmap. Jangan menyentuh brk dan sbrk, mereka adalah peninggalan dari masa lalu yang lebih berbahaya daripada kebaikan (bahkan halaman manual memberitahu Anda untuk menjauh dari mereka!)
Eloff
3
Ini konyol. Jika Anda ingin menghindari overhead malloc untuk banyak alokasi kecil, lakukan satu alokasi besar (dengan malloc atau mmap, bukan sbrk) dan bagikan sendiri. Jika Anda menyimpan node pohon biner Anda dalam array, Anda bisa menggunakan indeks 8b atau 16b alih-alih 64b pointer. Ini berfungsi baik ketika Anda tidak perlu menghapus simpul apa pun sampai Anda siap untuk menghapus semua node. (misalnya, membangun kamus yang diurutkan dengan cepat.) Menggunakan sbrkuntuk ini hanya berguna untuk kode-golf, karena menggunakan secara manual mmap(MAP_ANONYMOUS)lebih baik dalam segala hal kecuali ukuran kode sumber.
Peter Cordes
3

Ada pemetaan memori pribadi anonim khusus yang ditunjuk (secara tradisional terletak tepat di luar data / bss, tetapi Linux modern benar-benar akan menyesuaikan lokasi dengan ASLR). Pada prinsipnya itu tidak lebih baik daripada pemetaan lainnya Anda bisa membuat dengan mmap, tetapi Linux memiliki beberapa optimasi yang memungkinkan untuk memperluas akhir pemetaan ini (menggunakan brksyscall) ke atas dengan biaya berkurang penguncian relatif terhadap apa mmapatau mremapakan dikenakan. Ini membuatnya menarik untuk mallocimplementasi untuk digunakan ketika mengimplementasikan tumpukan utama.

R .. GitHub BERHENTI MEMBANTU ICE
sumber
Maksudmu mungkin untuk memperluas akhir pemetaan ini ke atas, ya?
zwol
Ya, sudah diperbaiki. Maaf soal itu!
R .. GitHub BERHENTI MEMBANTU ICE
0

Saya bisa menjawab pertanyaan kedua Anda. Malloc akan gagal dan mengembalikan pointer nol. Itu sebabnya Anda selalu memeriksa penunjuk nol saat mengalokasikan memori secara dinamis.

Brian Gordon
sumber
lalu apa gunanya srk ​​dan srk?
nik
3
@NikhilRathod: malloc()akan menggunakan brk()dan / atau di sbrk()bawah tenda - dan Anda juga bisa, jika Anda ingin mengimplementasikan versi kustom Anda sendiri malloc().
Daniel Pryden
@Daniel Pryden: bagaimana brk dan sbrk dapat bekerja pada heap ketika berada di antara stack dan segmen data seperti yang ditunjukkan pada diagram di atas. untuk ini bekerja tumpukan harus pada akhirnya. Apakah saya benar?
nik
2
@ Brian: Daniel mengatakan bahwa OS mengelola segmen stack , bukan pointer stack ... hal yang sangat berbeda. Intinya adalah bahwa tidak ada sbrk / brk syscall untuk segmen stack - Linux secara otomatis mengalokasikan halaman pada upaya untuk menulis ke akhir segmen stack.
Jim Balter
1
Dan Brian, Anda hanya menjawab setengah dari pertanyaan itu. Setengah lainnya adalah apa yang terjadi jika Anda mencoba mendorong ke tumpukan ketika tidak ada ruang tersedia ... Anda mendapatkan kesalahan segmentasi.
Jim Balter
0

Tumpukan ditempatkan terakhir di segmen data program. brk()digunakan untuk mengubah (memperluas) ukuran heap. Ketika tumpukan tidak dapat tumbuh lagi, mallocpanggilan apa pun akan gagal.

Anders Abel
sumber
Jadi Anda mengatakan bahwa semua diagram di internet, seperti yang ada di pertanyaan saya salah. Jika memungkinkan, tolong tunjukkan saya diagram yang benar.
nik
2
@Nikkhil Perlu diingat bahwa bagian atas diagram itu adalah akhir dari memori. Bagian atas tumpukan bergerak ke bawah pada diagram saat tumpukan tumbuh. Bagian atas tumpukan bergerak ke atas pada diagram saat diperluas.
Brian Gordon
0

Segmen data adalah bagian dari memori yang menyimpan semua data statis Anda, dibaca dari yang dapat dijalankan pada saat peluncuran dan biasanya diisi nol.

Monchalve
sumber
Itu juga menyimpan data statis tidak diinisialisasi (tidak ada dalam executable) yang mungkin sampah.
luser droog
Data statis tidak diinisialisasi ( .bss) diinisialisasi ke semua-bit-nol oleh OS sebelum program dimulai; ini sebenarnya dijamin oleh standar C. Beberapa sistem embedded mungkin tidak mengganggu, saya kira (saya belum pernah melihatnya, tapi saya tidak bekerja dengan semua yang tertanam)
zwol
@ zwol: Linux memiliki opsi waktu kompilasi untuk tidak nol halaman yang dikembalikan oleh mmap, tapi saya berasumsi .bssmasih akan nol. Ruang BSS mungkin merupakan cara paling ringkas untuk mengungkapkan fakta bahwa suatu program menginginkan beberapa zerod array.
Peter Cordes
1
@PeterCordes Apa yang dikatakan standar C adalah bahwa variabel global yang dideklarasikan tanpa inisialisasi diperlakukan seolah-olah diinisialisasi ke nol. Implementasi AC yang menempatkan variabel seperti itu .bssdan tidak nol .bssakan menjadi tidak sesuai. Tetapi tidak ada yang memaksa implementasi C untuk digunakan .bsssama sekali atau bahkan memiliki hal seperti itu.
zwol
@PeterCordes Juga, garis antara "implementasi C" dan program bisa sangat kabur, misalnya biasanya ada sejumlah kecil kode dari implementasi, secara statis terhubung ke setiap executable, yang berjalan sebelumnya main; kode itu bisa membidik .bssarea daripada meminta kernel melakukannya, dan itu masih sesuai.
zwol
0

malloc menggunakan panggilan sistem brk untuk mengalokasikan memori.

termasuk

int main(void){

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

Jalankan program sederhana ini dengan strace, itu akan memanggil sistem brk.

skanzariya
sumber