Що робить системний дзвінок brk ()?


184

Відповідно до посібника програмістів Linux:

brk () та sbrk () змінюють місце перерви в програмі, що визначає кінець сегмента даних процесу.

Що тут означає сегмент даних? Це просто сегмент даних або дані, BSS та купа разом?

Згідно з wiki:

Іноді ділянки даних, BSS та купи купу називають «сегментом даних».

Я не бачу причин для зміни розміру просто сегменту даних. Якщо це дані, BSS і купа разом, то це має сенс, оскільки купа отримає більше місця.

Що підводить мене до мого другого питання. У всіх прочитаних до цього часу статтях автор говорить, що купа росте вгору, а стек росте вниз. Але те, що вони не пояснюють, - це те, що відбувається, коли купа займає весь простір між купою і стеком?

введіть тут опис зображення


1
Отже, що ти робиш, коли не маєш місця? ви переходите на жорсткий диск. Використовуючи простір, ви випускаєте його для іншої інформації.
Ігоріс Азанова

28
@Igoris: Ви заплутуєте фізичну пам'ять (яку можна поміняти на диск, використовуючи віртуальну пам'ять) та адресний простір . Коли ви заповнюєте свій адресний простір, жодна сума заміни не поверне вам ці адреси в середині.
Даніель Приден

7
Як нагадування, brk()системний виклик є більш корисним у мові складання, ніж у C. В C, він malloc()повинен використовуватися замість brk()будь-яких цілей розподілу даних - але це жодним чином не скасовує запропоноване питання.
alecov

2
@Brian: Куча - це складна структура даних для обробки регіонів різного розміру та вирівнювання, вільного пулу тощо. Стопки потоків завжди суміжні (у віртуальному адресному просторі) послідовності повних сторінок. У більшості ОС є розподільник сторінок, що лежить в основі файлів стеків, купи та файлів, присвячених пам'яті.
Ben Voigt

2
@Brian: Хто сказав, що будь-який "стек" маніпулює brk()та sbrk()? Стеками керує розподільник сторінок на значно нижчому рівні.
Ben Voigt

Відповіді:


233

На діаграмі, яку ви розмістили, "перерва" - адреса, якою маніпулюється brkта sbrk- є пунктирною лінією у верхній частині купи.

спрощене зображення компонування віртуальної пам'яті

Документація, яку ви прочитали, описує це як кінець "сегменту даних", оскільки в традиційних (бібліотеки, що мають загальний доступ,mmap ) сегмент даних Unix був безперервним з купою; перед запуском програми ядро ​​завантажило б блоки "текст" та "дані" в оперативну пам'ять, починаючи з нуля адреси (фактично трохи вище нуля адреси, так що вказівник NULL справді нічого не вказував) і встановив адресу перерви на кінець сегмента даних. Перший виклик до цього mallocбуде використовуватися sbrkдля переміщення розбиття та створення купи між вершиною сегмента даних та новою, більш високою адресою розриву, як показано на схемі, і подальше використання mallocбуде використовувати його для збільшення купи у міру необхідності.

Тим часом стек починається у верхній частині пам'яті і зростає вниз. Стек не потребує явних системних викликів, щоб збільшити його; або він починається з стільки, скільки йому може бути надано оперативної пам’яті (це був традиційний підхід), або є область зарезервованих адрес під стеком, якій ядро ​​автоматично виділяє оперативну пам’ять, коли помічає спробу записати туди (це сучасний підхід). Так чи інакше, у нижній частині адресного простору може бути, а може і не бути, область захисту, яка може бути використана для стека. Якщо цей регіон існує (усі сучасні системи роблять це), він назавжди не має карти; якщо будь-якийстек або купа намагаються перерости в нього, ви отримуєте помилку сегментації. Традиційно, однак, ядро ​​не робило спроб застосувати межу; стек може перерости в купу, або купа може перерости в стек, і в будь-якому випадку вони переписуватимуть дані один одного, і програма вийде з ладу. Якщо вам дуже пощастило, воно б одразу вийшло з ладу.

Я не впевнений, звідки походить число 512 Гб на цій діаграмі. Він передбачає 64-розрядний віртуальний адресний простір, що не відповідає дуже простої карті пам'яті, яка у вас є. Справжній 64-бітний адресний простір виглядає приблизно так:

менш спрощений адресний простір

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

Це не на віддаленому масштабі, і це не повинно трактуватися як саме те, що робить будь-яка дана ОС (після того, як я намалював це, я виявив, що Linux насправді ставить виконуваний файл набагато ближче до нульової адреси, ніж я вважав, що це було, і спільні бібліотеки на дивно високі адреси). Чорні області на цій діаграмі не розміщені - будь-який доступ викликає негайне сегментування - і вони є гігантськими щодо сірих областей. Світло-сірі регіони - це програма та її спільні бібліотеки (може бути десятки спільних бібліотек); кожен має незалежнутекст та сегмент даних (і сегмент "bss", який також містить глобальні дані, але ініціалізується на "всі біти-нулі", а не займає місце у виконуваному файлі чи бібліотеці на диску). Купа більше не обов'язково суцільна з сегментом даних виконуваного файлу - я намалював це так, але схоже, що Linux, принаймні, цього не робить. Стек вже не прив’язаний до вершини віртуального адресного простору, а відстань між купою та стеком настільки величезна, що вам не доведеться турбуватися про його перетин.

Перерва все ще є верхньою межею купи. Однак те, що я не показав, - це те, що десь там, в чорному, можуть бути десятки незалежних розподілів пам'яті, зроблених mmapзамість них brk. (ОС буде намагатися тримати їх далеко від brkрайону, щоб вони не стикалися.)


7
+1 для детального пояснення. Чи знаєте ви, чи mallocвсе ще покладаєтесь на brkце, чи використовуєте, mmapщоб мати змогу "повернути" окремі блоки пам'яті?
Андерс Абель

18
Це залежить від конкретної реалізації, але IIUC багато сучасних mallocs використовують brkобласть для невеликих асигнувань та окремі mmaps для великих (скажімо,> 128K) виділень. Див., Наприклад, обговорення MMAP_THRESHOLD на сторінці Linux malloc(3).
zwol

1
Справді гарне пояснення. Але як ви вже говорили, що Стек більше не знаходиться у верхній частині віртуального адресного простору. Це правда лише для 64-бітного адресного простору чи це правда навіть для 32-бітного адресного простору. І якщо стек розташований у верхній частині адресного простору, то де відбуваються анонімні карти пам'яті? Чи знаходиться він у верхній частині віртуального адресного простору безпосередньо перед стеком.
nik

3
@Nikhil: це складно. Більшість 32-бітних систем ставлять стек у саму верхню частину адресного простору в режимі користувача , яка часто є лише нижньою 2 або 3G повного адресного простору (решта місця зарезервована для ядра). В даний час я не можу придумати того, хто цього зробив, але я не знаю їх усіх. Більшість 64-бітних процесорів насправді не дозволяють використовувати весь 64-бітний простір; високі 10 - 16 біт адреси повинні бути абсолютно нульовими або всі-один. Стек зазвичай розміщується біля верхньої частини корисних низьких адрес. Я не можу дати тобі правило mmap; це надзвичайно залежно від ОС.
zwol

3
@RiccardoBestetti Він витрачає адресний простір , але це нешкідливо - 64-розрядний віртуальний адресний простір настільки великий, що якщо ви прогораєте його через гігабайт щосекунди , все одно знадобиться 500 років. [1] Більшість процесорів навіть не дозволяють використовувати більше 2 ^ 48 до 2 ^ 53 бітів віртуальної адреси (єдине відоме мені виключення - POWER4 у режимі таблиці хешованих сторінок). Він не витрачає фізичну оперативну пам'ять; невикористані адреси не призначаються ОЗУ.
zwol

26

Приклад мінімальної експлуатації

Що робить системний дзвінок brk ()?

Просить ядро ​​дозволити вам читати та записувати у суміжний шматок пам'яті, який називається купами.

Якщо ви не запитаєте, це може вас розставити за замовчуванням.

Без 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;
}

З 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 вище за течією .

Вищезгадане може не потрапляти на нову сторінку і не сегментуватись навіть без brk, тому тут є більш агресивна версія, яка виділяє 16MiB і, швидше за все, може бути сегментацією без 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.

Візуалізація віртуального адресного простору

Перед brk:

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

Після brk(p + 2):

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

Після brk(b):

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

Для кращого розуміння адресних просторів вам слід ознайомитись з підкачкою: Як працює підказка x86? .

Навіщо нам потрібно і те, brkі sbrk?

brkможна, звичайно, реалізовуватися за допомогою sbrkрозрахунків + зміщення, обидва існують лише для зручності.

У бекенді ядро ​​Linux v5.0 має єдиний системний виклик, brkякий використовується для реалізації обох: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23

12  common  brk         __x64_sys_brk

Є чи brkPOSIX?

brkраніше був POSIX, але він був видалений у POSIX 2001, тому необхідність _GNU_SOURCEдоступу до обгортки glibc.

Видалення, ймовірно, пов'язане з введенням mmap, який є суперсетом, який дозволяє виділити кілька діапазонів і більше варіантів розподілу.

Я думаю, що не існує дійсного випадку, коли слід використовувати brkзамість цього mallocабо mmapзараз.

brk проти malloc

brkце одна стара можливість реалізації malloc.

mmapце новіший суворо потужніший механізм, який, ймовірно, всі POSIX системи в даний час використовують для реалізації malloc. Ось мінімальний mmapприклад розподілу пам'яті, який можна виконати .

Чи можу я змішувати brkі лопатити

Якщо ваша mallocреалізована brk, я не маю уявлення, як це може не підірвати речі, оскільки brkкерує лише одним діапазоном пам'яті.

Однак я нічого не можу знайти про це у документах glibc, наприклад:

Тут, ймовірно, просто працюватимуть речі, оскільки mmap, ймовірно, вони використовуютьсяmalloc .

Дивитися також:

Більше інформації

Внутрішньо ядро ​​вирішує, чи може процес мати стільки пам'яті, і виділяє сторінки пам'яті для цього використання.

Це пояснює, як стек порівнюється з купою: Яка функція інструкцій push / pop, що використовуються в регістрах у складі x86?


4
Оскільки pвказівник на тип intне повинен був бути brk(p + 2);?
Йохан Буле

Невелика примітка: Вираз у *(p + i) = 1;
фор

До речі, чому нам потрібно використовувати brk(p + 2)замість того, щоб просто збільшити його на sbrk(2)? Чи справді необхідний brk?
Інь Лі Лю

1
@YiLinLiu Я думаю, що це всього лише два дуже схожих фронтальних brkфайли C для одного резервного ядра ядра ( syscall). brkбуде трохи зручніше відновити раніше виділений стек.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 996ICU 六四 事件 Враховуючи розмір int до 4 байт, а розмір int * як 4 байти (на 32-бітній машині), мені було цікаво, чи не слід його збільшувати лише на 4 байти (замість 8 - (2 * sizeof int)). Чи не слід вказувати на наступне доступне сховище - це буде 4 байти (не 8). Виправте мене, якщо я щось тут пропускаю.
Сакет Шарад

10

Ви можете використовувати brkі sbrkсамостійно, щоб уникнути "головної накладки", на яку завжди скаржаться всі. Але ви не можете легко використовувати цей метод спільно з mallocтим, що він підходить лише тоді, коли вам нічого не потрібно free. Тому що ти не можеш. Також слід уникати будь-яких дзвінків з бібліотеки, які можуть використовуватись mallocвнутрішньо. Тобто strlenнапевно, безпечно, але, fopenмабуть, ні.

Телефонуйте sbrkтак, як ви б зателефонували malloc. Він повертає вказівник на поточний розрив і збільшує перерву на цю суму.

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

Поки ви не можете звільнити окремі асигнування (тому що їх немає надмірних витрат , пам’ятайте), ви можете звільнити весь простір , зателефонувавши brkзі значенням, поверненим першим викликом sbrk, таким чином, перемотавши brk .

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

Ви навіть можете скласти ці регіони, відкинувши останній регіон, перемотавши перерву до початку регіону.


І ще одна річ ...

sbrkтакож корисний у коді гольфу, оскільки він на 2 символи коротший malloc.


7
-1 тому що: malloc/ freeбільшість, безумовно, може (і це зробити) повернути пам'ять в ОС. Вони можуть не завжди робити це, коли ви цього хочете, але це справа в тому, що евристика буде недосконало налаштована для вашого випадку використання. Що ще важливіше, небезпечно дзвонити sbrkз ненульовим аргументом у будь-яку програму, яка могла б коли-небудь викликати, malloc- і майже всі функції бібліотеки С дозволяють здійснювати mallocвнутрішні дзвінки . Єдині, яких точно не буде, це безпечні для сигналу функції.
zwol

А під "це небезпечно", я маю на увазі "ваша програма вийде з ладу".
zwol

Я редагував, щоб видалити хвалебну пам'ять, що повертається , і згадав про небезпеку функціонування бібліотеки внутрішньо malloc.
luser droog

1
Якщо ви хочете зробити фантазійне розподілення пам’яті, підставте його на верхній частині malloc або поверх mmap. Не торкайся brk та sbrk, вони є реліквіями минулого, які приносять більше шкоди, ніж користі (навіть рукописи говорять тобі, щоб не було їх!)
Eloff

3
Це нерозумно. Якщо ви хочете уникнути накладних витрат у великій кількості невеликих виділень, зробіть одне велике виділення (з malloc чи mmap, не sbrk) і виконайте це самостійно. Якщо ви зберігаєте вузли бінарного дерева в масиві, ви можете використовувати індекси 8b або 16b замість покажчиків 64b. Це чудово працює, коли вам не потрібно видалити жоден вузол, поки ви не будете готові видалити всі вузли. (наприклад, побудувати відсортований словник на льоту). Використання sbrkдля цього корисно лише для коду-гольфу, оскільки використання вручну mmap(MAP_ANONYMOUS)краще будь-яким способом, крім розміру вихідного коду.
Пітер Кордес

3

Існує спеціальне призначене анонімне відображення приватної пам’яті (традиційно розташоване за межами даних / bss, але сучасний Linux фактично коригує розташування за допомогою ASLR). В принципі, це не краще, ніж будь-яке інше відображення, яке ви могли б створити за допомогою mmap, але Linux має деякі оптимізації, які дозволяють розширити кінець цього відображення (за допомогою brksyscall) вгору зі зниженою вартістю блокування відносно того, що mmapабо mremapпонесе. Це робить привабливими для mallocреалізації, що використовуються при реалізації основної маси.


Ви мали на увазі можливе розширення кінця цього відображення вгору, так?
zwol

Так, виправлено. Вибач за це!
R .. GitHub СТОП ДОПОМОГА ВІД

0

Я можу відповісти на ваше друге запитання. Malloc вийде з ладу і поверне нульовий покажчик. Тому ви завжди перевіряєте нульовий покажчик, коли динамічно розподіляєте пам'ять.


то в чому користь brk та sbrk?
nik

3
@NikhilRathod: malloc()буде використовувати brk()та / або sbrk()під кришкою - і ви також можете, якщо хочете реалізувати власну індивідуальну версію malloc().
Даніель Приден

@Daniel Pryden: як brk та sbrk можуть працювати на купі, коли він знаходиться між стеком та сегментом даних, як показано на схемі вище. для цього попрацювати купу слід в кінці. Маю рацію?
nik

2
@Brian: Даніель сказав, що ОС управляє сегментом стека , а не вказівником стека ... дуже різні речі. Справа в тому, що для сегменту стека немає sbrk / brk syscall - Linux автоматично розподіляє сторінки при спробах запису до кінця сегмента стека.
Джим Балтер

1
А Брайан, ти відповів лише на половину половини питання. Інша половина - це те, що відбувається, якщо ви намагаєтесь натиснути на стек, коли місця немає, ви отримаєте помилку сегментації.
Джим Балтер

0

Купа розміщується останньою в сегменті даних програми. brk()використовується для зміни (розширення) розміру купи. Якщо купа більше не може вирости, будь-який mallocдзвінок не вдасться.


Отже, ви говорите, що всі діаграми в Інтернеті, як і моє запитання, помиляються. Якщо можливо, можете, будь ласка, вказати мені правильну схему.
nik

2
@Nikkhil Майте на увазі, що вершина цієї діаграми - це кінець пам'яті. Верхівка стека рухається вниз по діаграмі в міру зростання стека. Верхівка купи рухається вгору по схемі в міру її розширення.
Брайан Гордон

0

Сегмент даних - це частина пам'яті, яка містить всі ваші статичні дані, зчитувані з виконуваного файлу при запуску і зазвичай заповнені з нулем.


Він також містить неініціалізовані статичні дані (відсутні у виконуваному файлі), які можуть бути сміттям.
luser droog

Неініціалізовані статичні дані ( .bss) ОС ініціалізуються до всіх біт-нулів ОС перед запуском програми; це фактично гарантується стандартом С. Деякі вбудовані системи можуть не турбувати, я думаю (я ніколи не бачив, але я не працюю над усім, що вбудовується)
zwol

@zwol: У Linux є час компіляції, щоб не нульові сторінки поверталися mmap, але я вважаю, що .bssвсе одно буде нульовим. Простір BSS - це, мабуть, найбільш компактний спосіб виразити той факт, що програма бажає декількох нульових масивів.
Пітер Кордес

1
@PeterCordes Що говорить стандарт C, це те, що глобальні змінні, оголошені без ініціалізатора, трактуються як якщо ініціалізовані до нуля. Реалізація змінного струму, яка ставить такі змінні .bssта не нульові .bss, тому була б невідповідною. Але ніщо не змушує реалізацію C використовувати .bssвзагалі та навіть мати таке.
zwol

@PeterCordes Крім того, рядок між "реалізацією C" та програмою може бути дуже нечітким, наприклад, зазвичай існує невеликий фрагмент коду від реалізації, статично пов'язаний з кожним виконуваним файлом, який працює раніше main; цей код міг би нульову .bssобласть, а не ядро ​​це робити, і це все одно відповідатиме.
zwol

0

malloc використовує системний виклик brk для розподілу пам'яті.

включати

int main(void){

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

запустіть цю просту програму з strace, вона буде викликати brk-систему.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.