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?
brk()
pemanggilan sistem lebih berguna dalam bahasa assembly daripada dalam C. Dalam C,malloc()
harus digunakan alih-alihbrk()
untuk tujuan alokasi data apa pun - tetapi ini tidak membatalkan pertanyaan yang diajukan dengan cara apa pun.brk()
dansbrk()
? Tumpukan dikelola oleh pengalokasi halaman, pada tingkat yang jauh lebih rendah.Jawaban:
Dalam diagram yang Anda poskan, "break" - alamat yang dimanipulasi oleh
brk
dansbrk
- adalah garis putus-putus di bagian atas tumpukan.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 untukmalloc
kemudian akan digunakansbrk
untuk 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 selanjutnyamalloc
akan 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:
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
mmap
alih - alihbrk
. (OS akan mencoba untuk menjauhkan ini daribrk
daerah sehingga mereka tidak bertabrakan.)sumber
malloc
masih mengandalkanbrk
atau jika digunakanmmap
untuk dapat "mengembalikan" blok memori yang terpisah?malloc
menggunakanbrk
area untuk alokasi kecil dan individummap
untuk alokasi besar (katakanlah,> 128K). Lihat, misalnya, diskusi tentang MMAP_THRESHOLD dimalloc(3)
manual Linux .mmap
; ini sangat tergantung pada OS.Contoh runnable minimal
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
:Dengan
brk
: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 tanpabrk
:Diuji pada Ubuntu 18.04.
Visualisasi ruang alamat virtual
Sebelum
brk
:Setelah
brk(p + 2)
:Setelah
brk(b)
:Untuk lebih memahami ruang alamat, Anda harus membiasakan diri dengan paging: Bagaimana cara kerja paging x86? .
Mengapa kita membutuhkan keduanya
brk
dansbrk
?brk
tentu saja dapat diimplementasikan dengansbrk
perhitungan + offset, keduanya ada hanya untuk kenyamanan.Di backend, kernel Linux v5.0 memiliki panggilan sistem tunggal
brk
yang digunakan untuk mengimplementasikan keduanya: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23Apakah
brk
POSIX?brk
dulu POSIX, tetapi dihapus pada POSIX 2001, sehingga kebutuhan untuk_GNU_SOURCE
mengakses 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,
brk
bukanmalloc
ataummap
saat ini.brk
vs.malloc
brk
adalah salah satu kemungkinan lama implementasimalloc
.mmap
adalah mekanisme baru yang lebih kuat yang kemungkinan besar semua sistem POSIX saat ini gunakan untuk mengimplementasikanmalloc
. Berikut adalah contoh alokasi memori runnable minimalmmap
.Bisakah saya campur
brk
dan malloc?Jika Anda
malloc
diimplementasikan denganbrk
, saya tidak tahu bagaimana itu mungkin tidak dapat meledakkan sesuatu, karenabrk
hanya 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
mmap
kemungkinan digunakan untukmalloc
.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?
sumber
p
pointer untuk diketikint
, bukankah ini seharusnyabrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
alih - alih hanya meningkatkannyasbrk(2)
? Apakah brk benar-benar diperlukan?brk
syscall).brk
sedikit lebih nyaman untuk mengembalikan tumpukan yang sebelumnya dialokasikan.Anda dapat menggunakan
brk
dansbrk
diri Anda sendiri untuk menghindari "malloc overhead" yang selalu dikeluhkan semua orang. Tetapi Anda tidak dapat dengan mudah menggunakan metode ini dalam hubungannya denganmalloc
sehingga hanya tepat ketika Anda tidak perlufree
apa - apa. Karena kamu tidak bisa. Anda juga harus menghindari panggilan perpustakaan apa pun yang dapat digunakan secaramalloc
internal. Yaitu.strlen
mungkin aman, tapifopen
mungkin juga tidak.Panggil
sbrk
persis seperti Anda akan meneleponmalloc
. Ini mengembalikan pointer ke break saat ini dan menambah break dengan jumlah itu.Meskipun Anda tidak dapat membebaskan alokasi individual (karena tidak ada malloc-overhead , ingat), Anda dapat membebaskan seluruh ruang dengan menelepon
brk
dengan nilai yang dikembalikan oleh panggilan pertamasbrk
, sehingga memutar brk .Anda bahkan bisa menumpuk wilayah ini, membuang wilayah paling baru dengan memutar ulang jeda ke awal wilayah.
Satu hal lagi ...
sbrk
juga berguna dalam golf kode karena 2 karakter lebih pendek darimalloc
.sumber
malloc
/free
paling 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 memanggilsbrk
dengan argumen tidak nol dalam program apa pun yang mungkin pernah memanggilmalloc
- dan hampir semua fungsi pustaka C diizinkan untuk memanggil secaramalloc
internal. Satu-satunya yang pasti tidak akan adalah fungsi async-signal-safe .malloc
.sbrk
untuk ini hanya berguna untuk kode-golf, karena menggunakan secara manualmmap(MAP_ANONYMOUS)
lebih baik dalam segala hal kecuali ukuran kode sumber.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 (menggunakanbrk
syscall) ke atas dengan biaya berkurang penguncian relatif terhadap apammap
ataumremap
akan dikenakan. Ini membuatnya menarik untukmalloc
implementasi untuk digunakan ketika mengimplementasikan tumpukan utama.sumber
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.
sumber
malloc()
akan menggunakanbrk()
dan / atau disbrk()
bawah tenda - dan Anda juga bisa, jika Anda ingin mengimplementasikan versi kustom Anda sendirimalloc()
.Tumpukan ditempatkan terakhir di segmen data program.
brk()
digunakan untuk mengubah (memperluas) ukuran heap. Ketika tumpukan tidak dapat tumbuh lagi,malloc
panggilan apa pun akan gagal.sumber
Segmen data adalah bagian dari memori yang menyimpan semua data statis Anda, dibaca dari yang dapat dijalankan pada saat peluncuran dan biasanya diisi nol.
sumber
.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)mmap
, tapi saya berasumsi.bss
masih akan nol. Ruang BSS mungkin merupakan cara paling ringkas untuk mengungkapkan fakta bahwa suatu program menginginkan beberapa zerod array..bss
dan tidak nol.bss
akan menjadi tidak sesuai. Tetapi tidak ada yang memaksa implementasi C untuk digunakan.bss
sama sekali atau bahkan memiliki hal seperti itu.main
; kode itu bisa membidik.bss
area daripada meminta kernel melakukannya, dan itu masih sesuai.malloc menggunakan panggilan sistem brk untuk mengalokasikan memori.
termasuk
Jalankan program sederhana ini dengan strace, itu akan memanggil sistem brk.
sumber