Что небезопасно / наследие в brk / sbrk? - PullRequest
6 голосов
/ 27 марта 2019

Я слышал во многих местах (список рассылки 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;
}

1 Ответ

3 голосов
/ 27 марта 2019

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

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

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 гласит:

Функции brk () и sbrk () являются устаревшими интерфейсами до Появление современного управления виртуальной памятью.

и позже:

ОШИБКА: Смешивание функций brk () или sbrk () с malloc (3), free (3) или аналогичными функциями приведет к приводит к непереносимому поведению программы.

...