GNU/Linux >> Belajar Linux >  >> Linux

Apa yang dilakukan panggilan sistem brk()?

Ada pemetaan memori pribadi anonim khusus yang ditunjuk (biasanya terletak tepat di luar data/bss, tetapi Linux modern sebenarnya akan menyesuaikan lokasi dengan ASLR). Pada prinsipnya tidak lebih baik dari pemetaan lain yang dapat Anda buat dengan mmap , tetapi Linux memiliki beberapa pengoptimalan yang memungkinkan perluasan akhir pemetaan ini (menggunakan kode brk syscall) ke atas dengan pengurangan biaya penguncian dibandingkan dengan mmap atau mremap akan dikenakan. Ini membuatnya menarik untuk malloc implementasi untuk digunakan saat mengimplementasikan heap utama.


Anda dapat menggunakan brk dan sbrk diri Anda sendiri untuk menghindari "malloc overhead" yang selalu dikeluhkan semua orang. Tetapi Anda tidak dapat dengan mudah menggunakan metode ini bersamaan dengan malloc jadi ini hanya sesuai jika Anda tidak perlu free apa pun. Karena kamu tidak bisa. Selain itu, Anda harus menghindari panggilan pustaka apa pun yang mungkin menggunakan malloc secara internal. Yaitu. strlen mungkin aman, tapi fopen mungkin tidak.

Hubungi sbrk sama seperti Anda memanggil malloc . Ini mengembalikan penunjuk ke jeda saat ini dan menambah jeda dengan jumlah itu.

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

Meskipun Anda tidak dapat membebaskan alokasi individual (karena tidak ada malloc-overhead , ingat), Anda bisa membebaskan seluruh ruang dengan memanggil brk dengan nilai yang dikembalikan oleh panggilan pertama ke sbrk , jadi memutar ulang brk .

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

Anda bahkan dapat menumpuk wilayah ini, membuang wilayah terbaru dengan memundurkan jeda ke awal wilayah.

Satu hal lagi ...

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


Contoh minimal yang dapat dijalankan

Apa fungsi panggilan sistem brk( )?

Meminta kernel untuk mengizinkan Anda membaca dan menulis ke bagian memori yang berdekatan yang disebut heap.

Jika Anda tidak bertanya, itu mungkin membuat Anda segfault.

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 upstream.

Di atas mungkin tidak mengenai halaman baru dan tidak segfault bahkan tanpa brk , jadi ini adalah versi yang lebih agresif yang mengalokasikan 16MiB dan sangat mungkin untuk segfault 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 brk keduanya dan sbrk ?

brk tentu saja dapat diimplementasikan dengan sbrk + 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#L23

12  common  brk         __x64_sys_brk

Apakah brk POSIX?

brk dulunya POSIX, tetapi dihapus di POSIX 2001, sehingga perlu _GNU_SOURCE untuk mengakses pembungkus glibc.

Penghapusan kemungkinan karena pengenalan mmap , yang merupakan superset yang memungkinkan beberapa rentang dialokasikan dan lebih banyak opsi alokasi.

Saya pikir tidak ada kasus yang valid di mana Anda harus menggunakan brk bukannya malloc atau mmap saat ini.

brk vs malloc

brk adalah salah satu kemungkinan lama untuk mengimplementasikan malloc .

mmap adalah mekanisme baru yang jauh lebih kuat yang kemungkinan besar digunakan oleh semua sistem POSIX saat ini untuk mengimplementasikan malloc . Ini adalah mmap minimal yang dapat dijalankan contoh alokasi memori.

Bisakah saya mencampur brk dan malloc?

Jika malloc Anda diimplementasikan dengan brk , saya tidak tahu bagaimana hal itu tidak dapat meledakkan sesuatu, karena brk hanya mengelola satu rentang memori.

Namun saya tidak dapat menemukan apa pun tentangnya di glibc docs, mis.:

  • https://www.gnu.org/software/libc/manual/html_mono/libc.html#Resizing-the-Data-Segment

Hal-hal mungkin hanya akan berfungsi di sana, saya kira sejak mmap kemungkinan digunakan untuk malloc .

Lihat juga:

  • Apa yang tidak aman/lama tentang brk/sbrk?
  • Mengapa memanggil sbrk(0) dua kali memberikan nilai yang berbeda?

Info lebih lanjut

Secara internal, kernel memutuskan apakah proses dapat memiliki memori sebanyak itu, dan mengalokasikan halaman memori untuk penggunaan tersebut.

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


Dalam diagram yang Anda posting, "break"—alamat dimanipulasi oleh brk dan sbrk —adalah garis putus-putus di bagian atas heap.

Dokumentasi yang telah Anda baca menggambarkan ini sebagai akhir dari "segmen data" karena dalam tradisional (perpustakaan yang dibagikan sebelumnya, pra-mmap ) Unix segmen data kontinu 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 menyetel alamat istirahat ke akhir segmen data. Panggilan pertama ke malloc kemudian akan menggunakan sbrk untuk memindahkan pemisahan dan membuat tumpukan di antaranya bagian atas segmen data dan alamat break baru yang lebih tinggi, seperti yang ditunjukkan pada diagram, dan selanjutnya menggunakan malloc akan menggunakannya untuk membuat heap lebih besar seperlunya.

Sementara itu, tumpukan dimulai dari bagian atas memori dan tumbuh ke bawah. Tumpukan tidak memerlukan panggilan sistem eksplisit untuk membuatnya lebih besar; baik itu dimulai dengan sebanyak RAM yang dialokasikan untuknya (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), wilayah ini tidak akan dipetakan secara permanen; jika salah satu tumpukan atau tumpukan mencoba tumbuh ke dalamnya, Anda mendapatkan kesalahan segmentasi. Namun, secara tradisional, kernel tidak berusaha memaksakan batasan; tumpukan dapat tumbuh menjadi tumpukan, atau tumpukan dapat tumbuh menjadi tumpukan, dan dengan cara apa pun mereka akan mencoret data satu sama lain dan program akan macet. Jika Anda sangat beruntung, itu akan langsung mogok.

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

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

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

Istirahat masih merupakan batas atas tumpukan. Namun, yang tidak saya tunjukkan adalah bahwa mungkin ada lusinan alokasi memori independen di luar sana di suatu tempat, dibuat dengan mmap bukannya brk . (OS akan mencoba menjauhkannya dari brk area agar tidak bertabrakan.)


Linux
  1. Memeriksa apakah errno !=EINTR:apa artinya?

  2. Apa arti 'rc' di `.bashrc`, dll.?

  3. Apa yang dimaksud dengan kemampuan ep?

  1. Apa runlevel sistem Linux saat ini?

  2. Deteksi Sistem Init Menggunakan Shell?

  3. Apa arti akhiran .d di Linux?

  1. Apa Artinya Dalam Keluaran Dari Ps?

  2. Inti dari Uniq -u Dan Apa Fungsinya??

  3. Apa yang Dilakukan Pintasan Ctrl-alt-+?