Что небезопасного/устаревшего в brk/sbrk?

Я слышал во многих местах (список рассылки musl, форумы macOS и т. д.), что brk() и sbrk() небезопасны. Многие из этих мест либо вообще не дают пояснений, либо дают очень расплывчатые пояснения. Например, в ссылке это говорится, что «эти функции принципиально сломаны», и далее говорится, что подсистемы malloc и sbrk полностью сломаны, что они разрушают кучу и т. д.

Мой вопрос: почему это так? Если malloc используется таким образом, что он выделяет блок памяти с sbrk достаточно большим, чтобы подавить или существенно уменьшить потребность в дальнейшем выделении, то не должны ли sbrk и brk быть совершенно безопасными для использования?

Вот мои реализации sbrk и brk:

sbrk:

#include <unistd.h>
#include <stddef.h>

void *sbrk(intptr_t inc)
{
        intptr_t curbrk = syscall(SYS_brk, NULL);

        if ( inc == 0 ) goto ret;

        if ( curbrk < 0 ) return (void *)-1;

        curbrk((void *)(curbrk+inc));
ret:
        return (void *)curbrk;
}

brk:

#include <unistd.h>

intptr_t brk(void *ptr)
{
        if ( (void *)syscall(SYS_brk, ptr) != ptr )
                return -1;
        else
                return 0;
}

В тексте есть объяснение For an application to use them correctly, it must depend on the malloc subsystem never being used, but this is impossible to guarantee since malloc may be used internally by libc functions without documenting this to the application.. Если ваше приложение вызывает sbrk, а ваше приложение вызывает malloc, а malloc вызывает sbrk, а функция sbrk использует глобальный контекст, результатом будет беспорядок. Они хотят исключить функцию sbrk, чтобы пользователи не использовали ее, а не потому, что это плохой дизайн сам по себе.

KamilCuk 27.03.2019 01:01

Что означает «глобальный контекст»? Быстрый поиск в гугле ничего не дает.

S.S. Anne 27.03.2019 02:07
sbrk обычно работает с одной глобальной переменной, например __curbrk в glibc или static char *heap_end; в новая библиотека. Это означает, что все «контексты» (потоки, функции, malloc, printf и пользовательское приложение) совместно используют один и тот же («глобальный» для процесса) сегмент данных.
KamilCuk 27.03.2019 07:48

Это странно, моя реализация просто использует код возврата из syscall(SYS_brk, 0).

S.S. Anne 27.03.2019 13:39

Я предполагаю, что некоторые реализации malloc написаны так, чтобы предположить, что никто другой не звонит sbrk за их спиной. Например, предположим, что вы вызываете p = malloc(N), где N — некоторое большое число (возможно, кратное размеру страницы). malloc делает sbrk(N) для получения памяти от ОС и возвращает этот указатель вам. Позже (без промежуточных вызовов malloc) вы делаете free(p). Теперь malloc «знает», что он может sbrk(-N) вернуть память ОС. Если вы позвонили sbrk между ними, значит, у вас проблема.

Nate Eldredge 27.03.2019 14:53

Альтернативой, которая была бы намного безопаснее, является sbrk кратное N, а затем реализация free(...), которая только помечает память как доступную для других целей. У вас действительно есть большой контроль, когда вы пишете стандартную библиотеку C с нуля :).

S.S. Anne 27.03.2019 15:12

Это уменьшит вероятность того, что sbrk что-то испортит, потому что память malloc уже выделена.

S.S. Anne 27.03.2019 15:18

Тогда, конечно, в конце выполнения программы вся память freed может быть очищена и освобождена.

S.S. Anne 27.03.2019 15:25
неуклюже написано, но хорошее базовое введение в malloc за написание собственного менеджера памяти с помощью brk/sbrk (только начальный уровень)
David C. Rankin 27.03.2019 16:11

Спасибо, я посмотрю.

S.S. Anne 27.03.2019 16:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
10
963
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Реальность сильно зависит от реализации, но вот некоторые элементы:

небезопасный

brk/sbrk были изобретены, чтобы позволить процессу запрашивать больше памяти у системы и освобождать ее в одном непрерывном сегменте. Таким образом, они использовались во многих реализациях malloc и free. Проблема заключалась в том, что, поскольку он возвращает уникальный сегмент, все может пойти не так, поскольку несколько модулей (одного и того же процесса) используют его напрямую. В многопоточном процессе стало еще хуже из-за условий гонки. Предположим, 2 потока хотят добавить новую память. Они будут смотреть на текущий верхний адрес с помощью sbrk(0), видеть тот же адрес, запрашивать новую память с помощью brk или sbrk, и из-за состояния гонки оба будут использовать одну и ту же память.

Даже в однопоточном процессе некоторые реализации malloc и free предполагают, что им разрешено использовать только низкоуровневый интерфейс s/brk и что их должен использовать любой другой код. В этом случае все пойдет не так, если образ сегмента разрыва, который они внутренне поддерживают, больше не является предполагаемым значением. Им придется догадаться, что некоторые части сегмента «зарезервированы» для других целей, что может нарушить возможность освобождения какой-либо памяти.

По этой причине пользовательский код никогда не должен напрямую использовать brk/sbrk, а полагаться только на malloc/free. Если и только если вы пишете реализацию стандартной библиотеки, включающую malloc/realloc/calloc/free, вы можете безопасно использовать brk/sbrk

наследие

В современной системе mmap может гораздо удобнее использовать управление виртуальной памятью. Вы можете использовать столько сегментов динамической памяти, сколько вам нужно, без взаимодействия между ними. Таким образом, в современной системе, если у вас нет особой потребности в выделении памяти с помощью brk/sbrk, вы должны использовать mmap.

портативность

Справочник по FreeBSD для brk и sbrk гласит следующее:

The brk() and sbrk() functions are legacy interfaces from before the advent of modern virtual memory management.

и позже:

BUGS: Mixing brk() or sbrk() with malloc(3), free(3), or similar functions will result in non-portable program behavior.

Это самое хорошее объяснение, которое я видел. Не смешивайте malloc со своими обращениями к brk/sbrk. Я думаю, вы правы. Если malloc не может найти разрыв там, где он его оставил, то, скорее всего, он не освободит часть памяти, которую он первоначально выделил. Я не смотрел на источник malloc, но это имеет смысл.

David C. Rankin 27.03.2019 16:17

Это определенно лучшее объяснение, чем я видел до сих пор. Однако возникает вопрос: если я могу заставить malloc() и free() проверять изменения прерывания программы, можно ли безопасно использовать sbrk() и brk() с моей библиотекой C? Я также дам вам +1 за упоминание темы. Я еще не реализовал их, поэтому я не думал об этом. Спасибо!

S.S. Anne 27.03.2019 16:40

Другие вопросы по теме