Язык C предоставляет функции динамического управления памятью. На языке C программисты могут использовать функции malloc() и free() для явного выделения и освобождения памяти. Что касается функций malloc() и free(), стандарт языка C предусматривает только функции. их необходимо реализовать без каких-либо ограничений на метод реализации, что приводит в замешательство тех, кто ищет первопричину. Например, для функции free() предусмотрено, что после освобождения области памяти она не должна двигаться дальше. ссылка на него, Любая ссылка на освобожденную область приведет к непредсказуемым последствиям. Итак, каковы же непредсказуемые последствия? Все зависит от алгоритма, используемого распределителем памяти. В этой статье делается попытка понять Linux. Вот некоторое описание того, как работает распределитель памяти, предоставляемый glibc. , и я надеюсь, что он сможет ответить на вопросы, подобные приведенным выше. Хотя описание здесь ограничено конкретной платформой, общий факт заключается в том, что программное обеспечение с одинаковыми функциями в основном будет использовать схожие технологии. Описанные здесь принципы могут быть применимы и в других обстоятельствах. Следует также подчеркнуть, что эта статья посвящена описанию только общих принципов и не будет слишком запутываться в деталях. Если вам нужны конкретные подробные знания, обратитесь к конкретным. Исходный код распределителя Наконец, аппаратная платформа, описанная в этой статье, — Intel 80x86, и некоторые из задействованных принципов и данных могут быть связаны с платформой.
Прежде чем знакомиться с ptmalloc, мы сначала разберемся с расположением памяти на примере 32-битной системы x86:
Как видно из рисунка выше, стек расширяется вниз сверху, куча расширяется вверх снизу, а область отображения mmap расширяется вниз сверху. Отображенная область и куча mmap относительно расширяются до тех пор, пока оставшаяся область виртуального адресного пространства не будет исчерпана. Эта структура позволяет библиотеке времени выполнения C использовать отображаемую область и кучу mmap для выделения памяти.
Прежде всего, система Linux предоставляет пользователям запрошенную память посредством функций brk (sbrk) и mmap. Давайте сначала посмотрим на эти функции.
#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );
Функция обоих — расширить верхнюю границу кучи brk. Параметры Brk() устанавливаются на новый адрес верхней границы brk, возвращая 1 в случае успеха и 0 в случае неудачи; Параметр Sbrk() представляет собой размер запрошенной памяти и возвращает адрес новой верхней границы brk кучи.
#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);
Первое использование Mmap — сопоставление дискового файла с памятью; второе использование — анонимное сопоставление, которое не отображает дисковый файл, а применяется к фрагменту памяти из области сопоставления. Malloc использует второй вариант использования mmap (анонимное сопоставление). Функция Munmap используется для освобождения памяти.
Распределитель памяти (распределитель) GNU Libc — ptmalloc произошел от malloc Дуга Ли (см. [1]). ptmalloc реализует malloc(), free() и набор других функций для обеспечения поддержки динамического управления памятью. Распределитель находится между пользователем. программа и ядро. Он отвечает на запрос пользователя о выделении, обращается за памятью из операционной системы, а затем возвращает ее пользовательской программе. Для поддержания эффективного распределения используется распределитель. Обычно память, превышающая запрос пользователя, выделяется заранее, и эта память управляется с помощью определенного алгоритма в соответствии с требованиями пользователя к выделению памяти. Память, освобожденная пользователем, не возвращается немедленно в операционную систему. Распределитель будет управлять им. Свободное пространство освобождается в соответствии с будущими требованиями пользователя к выделению памяти. , распределитель Сначала он будет искать подходящую память в свободном пространстве для пользователя и выделять новую память только в том случае, если ее невозможно найти в свободном пространстве. Для реализации эффективного распределителя необходимо учитывать множество факторов. сам распределитель управляет памятью. Объем памяти, занимаемый блоком, должен быть небольшим, а алгоритм выделения должен быть достаточно быстрым. Джонатан Бартлетт предложил простую реализацию распределителя [2]. Возможно, вам будет полезно понять эту статью, если вы посмотрите на нее. заранее Кроме того, Джонатан Бартлетт Книга «Программирование от». Ground Up» — хороший выбор для новичков, которые хотят понять, как собирается и работает Linux.
Поскольку brk, sbrk и mmap — это системные вызовы, если вы вызываете эти три каждый раз, когда обращаетесь за памятью, каждый раз будет генерироваться системный вызов, что влияет на производительность. Во-вторых, запрошенная таким образом память подвержена фрагментации. куча начинается с младшего адреса по старшему адресу, если память по старшему адресу не освобождается, память по младшему адресу не может быть освобождена. Поэтому malloc использует метод управления пулом памяти (ptmalloc) использует метод разметки границ для разделения памяти на множество блоков для управления распределением и переработкой памяти. Чтобы повысить эффективность функции распределения памяти malloc, ptmalloc заранее запросит часть памяти из операционной системы, чтобы пользователи могли ее использовать. Когда мы подаем заявку и освобождаем память, ptmalloc будет управлять памятью и использовать некоторые стратегии для ее использования. определить, следует ли перерабатывать его в операционную систему. Самым большим преимуществом этого подхода является то, что он позволяет пользователям более эффективно использовать и освобождать память и позволяет избежать чрезмерной фрагментации памяти.
Структура malloc_chunk определена в исходном коде реализации ptmalloc для описания этих фрагментов. malloc_chunk определяется следующим образом:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
Определение чанка довольно простое и понятное. Давайте дадим краткое представление о каждом домене: prev_size: Если предыдущий чанк свободен, это поле указывает размер предыдущего чанка. Если предыдущий чанк не свободен, это поле не имеет смысла. (Описание здесь немного расплывчато. Непрерывная память разделена на несколько фрагментов. prev_size записывает размер соседнего предыдущего фрагмента. Зная адрес текущего фрагмента, вычитая prev_size, получаем адрес предыдущего фрагмента. Prev_size в основном используется Слияние с соседними свободными фрагментами) Размер: размер текущего чанка; записывает некоторые атрибуты текущего чанка и предыдущего чанка, в том числе используется ли предыдущий чанк, находится ли текущий чанк в памяти, полученной через mmap, и принадлежит ли текущий чанк не -первичная зона размещения. fd и bk: Указатели fd и bk существуют только тогда, когда блок фрагментов свободен. Их функция заключается в добавлении соответствующего блока свободных фрагментов в список блоков свободных фрагментов для унифицированного управления. Если блок фрагментов выделен приложению, то они. два указателя бесполезны (блок чанка удален из свободной цепочки), поэтому они также используются как пространство для прикладной программы и не будут потрачены впустую. fd_nextsize и bk_nextsize: когда текущий фрагмент существует в больших контейнерах, свободные фрагменты в больших контейнерах сортируются по размеру, но может быть несколько фрагментов одинакового размера. Добавление этих двух полей может ускорить обход свободных фрагментов и поиск. свободный фрагмент, соответствующий потребностям. fd_nextsize указывает на следующий свободный фрагмент, размер которого превышает текущий размер фрагмента. bk_nextszie указывает на предыдущий фрагмент, размер которого превышает текущий размер фрагмента. Первый свободный кусок обоих размеров. (Может быть несколько фрагментов одного и того же размера. Когда общий размер в порядке, и если вы хотите найти следующий фрагмент, больший или меньший, чем вы, вам нужно пройти по всем одинаковым фрагментам, поэтому существуют такие конструкции, как fd_nextsize и bk_nextsize) Если чанк выделен приложению, эти два указателя бесполезны (чанк был удален из цепочки размеров), поэтому он также используется как пространство для приложения и не будет потрачен впустую.
(Как вы можете видеть ниже, когда чанк пуст, существует четыре указателя: fd, bk, fd_nextsize и bd_nextsize. Когда чанк не пуст, пространство этих четырех указателей напрямую передается пользователю)
Структуру чанка можно разделить на используемые и свободные чанки. Структуры данных используемых и свободных фрагментов в основном одинаковы, но есть некоторые дизайнерские приемы, позволяющие экономить память.
проиллюстрировать: 1. Указатель фрагмента указывает на адрес, по которому начинается фрагмент; указатель mem указывает на адрес, по которому начинается блок пользовательской памяти. 2. Когда p=0, это означает, что предыдущий чанк простаивает, а prev_size действителен. 3. Когда p=1, это означает, что предыдущий фрагмент используется, а prev_size недействителен. p в основном используется для объединения блоков памяти; первый блок, выделенный ptmalloc, всегда устанавливает p равным 1, чтобы программа не ссылалась на другие блоки. существующие территории. 4. M=1 — выделение области отображения mmap; M=0 — выделение области кучи; 5. A=0 выделяется основной области распределения; A=1 выделяется неосновной области распределения.
проиллюстрировать: 1. Когда чанк простаивает, его состояние M не существует, а только состояние AP (поскольку M указывает, выделена ли память с помощью brk или mmap, а память, выделенная с помощью mmap, напрямую объединяется, когда она свободна, и не будет Другими словами, все в свободном связанном списке выделяется brk, поэтому дополнительные записи не требуются. 2. В области пользовательских данных хранятся четыре указателя. Указатель fd указывает на следующий свободный фрагмент, а bk указывает на предыдущий свободный фрагмент. Malloc использует эти два указателя для соединения фрагментов одинакового размера в двусвязный список. Свободный фрагмент в большом контейнере также имеет два указателя: fd_nextsize и bk_nextsize, которые используются для ускорения поиска ближайшего подходящего свободного фрагмента в большом контейнере. Различные связанные списки блоков организованы с помощью ячеек или быстрых ячеек.
Чтобы минимизировать пространство, занимаемое чанком, ptmalloc использует повторное использование пространства. Чанк либо используется, либо освобожден, поэтому для достижения этой цели некоторые поля в куске могут выражать разные значения в состоянии использования и состоянии ожидания. эффект мультиплексирования пространства. В режиме ожидания в фрагменте должно быть не менее 4 пробелов размера size_t для хранения prev_size, size, fd и bk, что равно 16. байт (?? Почему не 6 size_t? Разве нет fd_nextsize и bk_nextsize? - Эти два указателя нужны не во всех бинах. Например, в fast_bin есть связанный список каждые 8 Байт, каждый из которых имеет размер всех чанков в связанный список тот же, очевидно, что эти два указателя не используются) Размер патрона должен быть выровнен по 8 байтам. Когда чанк используется, поле prev_size его следующего чанка определенно недействительно. Фактически, это пространство также может использоваться текущим чанком. Это звучит немного невероятно, но это действительно пример разумного повторного использования пространства. Итак, на самом деле формула расчета размера используемого чанка должна быть такой:
in_use_size = (размер запроса пользователя + 8 - 4) выравнивание по 8 байтам. Здесь добавляется 8, потому что prev_size и size должны быть сохранены, но поскольку 4 байта «заимствованы» из следующего фрагмента, нужно вычесть 4. Наконец, потому что. Свободный и используемый фрагменты используют одно и то же пространство. Поэтому в качестве фактического выделенного пространства следует принять самый большой из них. То есть окончательное выделенное пространство chunk_size = max(in_use_size, 16). Это размер памяти, который ptmalloc фактически должен выделить, когда пользователь запрашивает выделение памяти. В следующем введении, если не указано иное, он относится к преобразованному размеру памяти, который фактически необходимо выделить, а не к размеру выделения памяти, запрошенному пользователем. .
Когда пользователь использует функцию free для освобождения памяти, ptmalloc не будет немедленно возвращать ее в операционную систему. Вместо этого ею будут управлять собственные свободные связанные списки ptmalloc. Таким образом, в следующий раз процессу потребуется выполнить malloc. часть памяти, ptmalloc начнет работу со списка свободных, найдите блок памяти подходящего размера в контейнерах, чтобы выделить его пользователю. Это преимущество позволяет избежать частых системных вызовов и снизить накладные расходы на выделение памяти.
malloc связывает фрагменты одинакового размера, используя двусвязный список, и такой связанный список называется корзиной. ptmalloc поддерживает в общей сложности 128bin. Каждый контейнер содержит двусвязный список фрагментов одинакового размера. В зависимости от размера куска доступны следующие контейнеры: 1. Быстрая корзина 2. Несортированная корзина 3. Маленький контейнер 4. Большой контейнер
Структура данных для сохранения этих ячеек:
fastbinsY: этот массив используется для сохранения быстрых ячеек. Ячейки: этот массив используется для хранения несортированных, маленьких и больших ячеек общей емкостью 126:
Когда пользователь вызывает malloc, он может быстро узнать, находится ли объем памяти, который пользователю необходимо выделить, в поддерживаемом бункере. Если он находится в определенном бункере, он может использовать двусвязный список, чтобы найти соответствующий блок памяти фрагмента. пользователю использовать.
Когда программа работает, ей часто необходимо запросить и освободить немного меньшего пространства памяти. После того, как распределитель объединит несколько соседних небольших блоков, сразу же может возникнуть запрос на еще один небольшой блок памяти, поэтому распределителю необходимо вырезать блок из большой свободной памяти, что, несомненно, относительно неэффективно. Поэтому вводятся быстрые бункеры. в malloc во время процесса выделения.
Fast bins — это высокоскоростной буфер для бункеров, имеющий около 10 очередей фиксированной длины. В каждом быстром бункере записывается односвязный список свободных фрагментов (называемый списком бункеров). Односвязный список используется, поскольку фрагменты в связанном списке в быстром бункере не будут удалены. Добавление и удаление фрагментов происходит в начале буфера. связанный список. Fast bins записывает связанный список бинов, размер которого увеличивается на 8 байт (?? На картинке выше кажется, что он с шагом в 4 байта - приращение в 4 байта связано с тем, что указатель занимает 4 байта, а блок, висящий ниже, - действительно 8 слов приращения раздела).
Когда пользователь выпускает чанк размером не больше max_fast (значение по умолчанию 64B), он по умолчанию помещается в быстрые контейнеры. Когда чанк, который необходимо выделить пользователю, меньше или равен max_fast, malloc сначала ищет подходящий чанк в быстрых контейнерах. (Независимо от того, выделены или освобождены фрагменты определенного размера, они сначала будут пропущены через быструю корзину) За исключением особых обстоятельств, два соседних свободных фрагмента не будут объединены в один свободный фрагмент. Отсутствие слияния может вызвать проблемы с фрагментацией, но может значительно ускорить процесс выпуска! При выделении первый фрагмент, полученный в списке бинов, будет удален и возвращен пользователю. Освобожденный фрагмент будет добавлен в начало индексированного списка бинов.
Очередь несортированных ячеек использует первый элемент в массиве ячеек, который представляет собой буфер ячеек для ускорения распределения. Когда объем памяти, освобожденный пользователем, превышает max_fast или фрагменты после слияния быстрых бункеров сначала попадают в несортированный бункер. Размер куска – ограничений по размеру нет, сюда можно добавлять куски любого размера. Этот подход дает glibc malloc второй шанс повторно использовать недавно освобожденный фрагмент, поэтому затраты времени на поиск подходящего контейнера стираются, поэтому выделение и освобождение памяти будет происходить быстрее. Когда пользователь malloc не находит подходящего фрагмента в быстрых контейнерах, malloc сначала будет искать подходящий свободный фрагмент в несортированном контейнере. Если подходящего блока нет, ptmalloc поместит фрагмент из несортированного контейнера в бункеры. а затем перейдите к разделу «Найти подходящие свободные куски в контейнерах».
Чанки размером менее 512 байт называются маленькими блоками, а бункеры, в которых хранятся небольшие фрагменты, называются малыми бункерами. Нумерация массива начинается с 2. Первые 64 ячейки являются маленькими ячейками. Разница между каждой маленькой ячейкой составляет 8 байт. Чанки в одной и той же маленькой ячейке имеют одинаковый размер. Каждая маленькая ячейка включает в себя двусторонний кольцевой связанный список свободных блоков (также называемый списком ячеек). Свободный фрагмент добавляется в начало связанного списка, а необходимый фрагмент удаляется из конца связанного списка. Два соседних свободных фрагмента будут объединены в один свободный фрагмент. Слияние устраняет эффекты фрагментации, но замедляет работу бесплатно. При распределении, когда бункер samll не пуст, соответствующий бункер удалит последний фрагмент из списка бинов и вернет его пользователю. При освобождении чанка проверьте, свободны ли чанки до или после него. Если да, объедините их, то есть удалите их из связанного списка, к которому они принадлежат, и объедините их в новый чанк. передняя часть несортированного связанного списка.
Чанки размером больше или равным 512 байтам называются большими чанками, а бункеры, в которых хранятся большие куски, называются большими бункерами и располагаются за маленькими бункерами. Каждая ячейка в больших ячейках содержит фрагмент в заданном диапазоне. Фрагменты сортируются в порядке убывания размера. Если размер одинаковый, фрагменты сортируются по времени последнего использования. Два соседних свободных фрагмента будут объединены в один свободный фрагмент. При распределении следуйте принципу «сначала наименьший, наиболее подходящий» и перемещайтесь сверху вниз, чтобы найти фрагмент, размер которого наиболее близок к потребностям пользователя. После обнаружения соответствующий adbbchunk будет разделен на два пользовательских фрагмента (размер запроса пользователя) и возвращен пользователю. Часть остатка Остаток добавляется в несортированную корзину. Бесплатно похоже на маленькую корзину.
Не все фрагменты организованы указанным выше способом, есть три исключения. верхний фрагмент, mmaped фрагмент и последний оставшийся фрагмент
Верхний фрагмент эквивалентен свободной памяти в верхней части области выделения (возможно, указатель brk, управляемый вызовом brk). Когда требования к выделению памяти не могут быть выполнены в бункерах, она будет выделена в верхнем фрагменте. Когда размер верхнего фрагмента превышает размер, запрошенный пользователем, верхний фрагмент будет разделен на две части: пользовательский фрагмент (запрошенный пользователем размер) и оставшийся фрагмент (оставшийся размер). Среди них фрагмент Remainder становится новым верхним блоком. Когда размер верхнего чанка меньше размера, запрошенного пользователем, верхний чанк расширяется с помощью системного вызова sbrk (основная арена) или mmap (арена потока).
Если выделенная память очень велика (больше порога выделения, по умолчанию 128 КБ) и ее необходимо отобразить в mmap, она будет помещена в чанк mmaped. Когда память в чанк mmaped будет освобождена, она будет напрямую возвращена в блок mmaped. Операционная система. (Позиция флага M 1 в фрагменте)
Последний оставшийся фрагмент — это еще один специальный фрагмент, как и верхний фрагмент и фрагмент mmaped, этот фрагмент не будет найден ни в каких контейнерах. Когда необходимо выделить небольшой фрагмент, но подходящий фрагмент не может быть найден в маленьких контейнерах, если размер последнего оставшегося фрагмента больше требуемого размера небольшого фрагмента, последний оставшийся фрагмент разделяется на два фрагмента и один кусков возвращается пользователю. Другой фрагмент становится новым последним остаточным куском.
В области кучи start_brk указывает на начало кучи, а brk указывает на вершину кучи. Вы можете использовать системные вызовы brk() и sbrk() для увеличения значения brk, которое идентифицирует вершину кучи, тем самым линейно увеличивая пространство кучи, выделенное пользователю. До malloc значение brk равно start_brk, что означает, что размер кучи равен 0. В начале ptmalloc, если запрошенное пространство меньше порога выделения mmap (порог mmap, значение по умолчанию — 128 КБ), основная область распределения вызовет sbrk(), чтобы добавить блок размером (128 КБ + chunk_size). ) выровнять 4 КБ (выравнивание размера страницы) Пространство в виде кучи. Неосновная область распределения будет вызывать mmap для отображения пространства размером HEAP_MAX_SIZE (по умолчанию — 1 МБ в 32-битных системах и 64 МБ в 64-битных системах) как подкучи. Это выделенное пространство, поддерживаемое упомянутым ранее ptmalloc; Когда пользователь запрашивает выделение памяти, в этой области сначала будет найден подходящий для пользователя фрагмент. Когда пользователь освобождает фрагменты в куче, ptmalloc будет использовать fastbins и bins для организации свободных фрагментов. для подготовки к следующему распределению пользователя. Если размер выделяемого фрагмента меньше порога выделения mmap и места в куче недостаточно, основная область выделения вызывает sbrk() для увеличения размера кучи, а неосновная область выделения вызывает mmap для отображения новая подкуча. Это означает увеличение размера верхнего фрагмента. При каждом увеличении кучи значение будет выравниваться до 4 КБ. Когда запрос пользователя превышает порог выделения mmap и основная область выделения не может быть выделена с помощью sbrk(), или неосновная область выделения не может выделить требуемую память в верхнем фрагменте, ptmalloc попытается использовать mmap() напрямую сопоставить часть памяти с пространством памяти процесса. Чанки, непосредственно отображаемые с помощью mmap(), сразу же отменяются при освобождении и больше не принадлежат пространству памяти процесса. Любой доступ к этой памяти приведет к сбою сегмента. Пространство, выделенное в куче или подкуче, может остаться в пространстве памяти процесса, и на него можно снова обратиться (конечно, это очень опасно).
В распределителе памяти, чтобы решить проблему конкуренции многопоточных блокировок, она разделена на основную область выделения main_area (суть области выделения — это пул памяти, который управляет частями и обычно обозначается английским область) и неосновную область выделения no_main_area. (Разница между основной и неосновной зонами размещения)
1. Основная область распределения и неосновная область распределения образуют циклический связанный список для управления. 2. Каждая область выделения использует блокировку мьютекса, чтобы предотвратить доступ потоков к области выделения. 3. Каждый процесс имеет только одну первичную область размещения; допускается также наличие нескольких неосновных областей размещения. 4. ptmalloc динамически увеличивает размер области распределения в зависимости от конкуренции системы за эту область. Как только количество областей распределения увеличится, оно не уменьшится. 5. Основная область выделения может быть выделена с помощью brk и mmap, тогда как неосновная область выделения может быть выделена только с помощью mmap для сопоставления блоков памяти. 6. При подаче заявки на небольшую память будет сгенерировано много фрагментов памяти. ptmalloc также необходимо блокировать область выделения при сортировке.
Когда потоку необходимо использовать malloc для выделения памяти, он сначала проверяет, существует ли уже область выделения в частной переменной потока. Если он существует. Будет предпринята попытка заблокировать его. Если блокировка успешна, область выделения будет использоваться для выделения памяти. Если это не удастся, циклический связанный список будет пройден для получения разблокированной области выделения. Если во всем связанном списке нет разблокированной области выделения, malloc откроет новую область выделения, добавит ее в глобальный циклический связанный список и заблокирует его, а затем будет использовать эту область выделения для выделения памяти. Когда эта память освобождается, сначала также будет получена блокировка области выделения, в которой находится освобождаемый блок памяти. Если другие потоки используют эту область выделения, вы должны дождаться, пока другие потоки освободят мьютекс области выделения, прежде чем освобождать память.
Следует отметить несколько моментов:
1. Получите блокировку области выделения, чтобы предотвратить многопоточные конфликты. (У процесса есть менеджер malloc, и несколько потоков в процессе используют этот менеджер совместно с конкуренцией и блокировкой)
2. Рассчитайте фактический размер фрагмента памяти, который фактически необходимо выделить.
3. Определить размер чанка. Если он меньше max_fast (64B), попытаться получить подходящий чанк из быстрых бункеров. Если он есть, выделение закончится. В противном случае следующий шаг;
4. Определите, меньше ли размер фрагмента 512 байт. Если да, выполните поиск фрагмента в маленьких контейнерах. Если есть подходящий фрагмент, выделение заканчивается. В противном случае следующий шаг;
5. ptmalloc сначала будет проходить по чанкам в быстрых контейнерах (примечание: это второй раз, когда мы проходим по быстрым бункерам, хотя быстрые контейнеры обычно не объединяются, но в этот раз они будут объединены), объединять соседние фрагменты и ссылаться на несортированный контейнер и затем перемещайтесь по неотсортированным ячейкам. (Как правило, пятая часть пересекает несортированную корзину, но перед перемещением объединяет быструю корзину. При перемещении по несортированной ячейке она пересекает ее, помещая ее в малую корзину и большую корзину)
Если в несортированных бункерах имеется только один фрагмент, и он больше, чем выделяемый фрагмент, он будет обрезан, а оставшиеся фрагменты будут по-прежнему отбрасываться обратно в несортированные бункеры; Если в несортированных бункерах есть фрагмент, равный размеру выделяемого фрагмента, он будет возвращен и удален из несортированных бункеров; Если размер чанка в несортированных бинах попадает в диапазон малых бинов, он помещается в заголовок малых бинов; Если размер определенного фрагмента в неотсортированных контейнерах попадает в диапазон больших контейнеров, найдите подходящее место для его размещения. Если распределение не удалось, перейдите к следующему шагу; 6. Найдя подходящий кусок из больших бункеров, разрежьте его, часть раздайте пользователю, а остальное положите в неотсортированный контейнер.
7. Если при поиске быстрых бункеров и бункеров не найден подходящий чанк, то для выделения необходимо использовать верхний чанк. Когда размер верхнего фрагмента превышает размер, запрошенный пользователем, верхний фрагмент будет разделен на две части: пользовательский фрагмент (запрошенный пользователем размер) и оставшийся фрагмент (оставшийся размер). Среди них фрагмент Remainder становится новым верхним блоком. Когда размер верхнего чанка меньше размера, запрошенного пользователем, верхний чанк расширяется с помощью системного вызова sbrk (основная арена) или mmap (арена потока).
8. На этом этапе это означает, что верхний фрагмент не может соответствовать требованиям к выделению, поэтому есть два варианта: если это основная область распределения, вызвать sbrk(), чтобы увеличить размер верхнего фрагмента, если он не основной; область выделения, вызовите mmap. Выделите новую подкучу и увеличьте размер верхнего фрагмента или используйте mmap() для прямого выделения; Здесь вам нужно полагаться на размер чанка, чтобы решить, какой метод использовать. Определите, превышает ли размер выделяемого фрагмента пороговое значение выделения mmap. Если да, перейдите к следующему шагу и вызовите выделение mmap. В противном случае перейдите к шагу 10, чтобы увеличить размер верхнего фрагмента.
9. Используйте системный вызов mmap, чтобы сопоставить пространство chunk_size align размером 4 КБ с пространством памяти программы. Указатель памяти затем возвращается пользователю.
10. Определите, вызывается ли malloc в первый раз. Если это основная область распределения, вам необходимо выполнить работу по инициализации и выделить пространство размером (chunk_size + 128 КБ), выровняв 4 КБ в качестве начальной кучи. Если она была инициализирована, основная область выделения вызывает sbrk() для увеличения места в куче, а подосновная область выделения вырезает часть верхнего фрагмента в соответствии с требованиями выделения и возвращает указатель памяти пользователю.
Чтобы избежать взрыва памяти Glibc, вам необходимо обратить внимание на:
1. Память, выделенная позже, освобождается первой, поскольку ptmalloc сжимает память, начиная с верхнего фрагмента. Если фрагменты, соседние с верхним фрагментом, не могут быть освобождены, фрагменты ниже верхнего фрагмента не могут быть освобождены. 2. Ptmalloc не подходит для управления памятью с длительным жизненным циклом, особенно для непрерывного и нерегулярного выделения и освобождения памяти с длительным жизненным циклом, что может привести к взрыву памяти ptmalloc. 3. Не отключайте механизм динамической настройки порога выделения mmap ptmalloc, поскольку этот механизм гарантирует, что выделения памяти с коротким жизненным циклом выделяются из фрагментов памяти, кэшированных ptmalloc, в максимально возможной степени, что более эффективно и требует меньше памяти. 4. Программы, выполняемые поэтапно в несколько потоков, не подходят для ptmalloc. Память таких программ больше подходит для управления пулом памяти (поскольку несколько потоков в рамках одного процесса должны быть заблокированы, прежде чем они смогут использовать распределитель malloc). 5. Минимизируйте количество потоков в программе и избегайте частого выделения/освобождения памяти. Частое выделение приведет к конкуренции блокировок, что в конечном итоге приведет к увеличению неосновных областей выделения, увеличению фрагментации памяти и снижению производительности. 6. Предотвращение утечек памяти. ptmalloc очень чувствителен к утечкам памяти. Согласно механизму сжатия памяти, если чанк, прилегающий к верхнему чану, не перерабатывается, большая часть свободной памяти под верхним чанком не будет возвращена операционной системе. . 7. Не позволяйте программе выделять слишком много памяти, иначе системная память исчерпается из-за внезапного увеличения памяти glibc, и программа будет уничтожена системой из-за OOM. Оцените максимальный размер физической памяти, который может использовать программа, настройте системные параметры /proc/sys/vm/overcommit_memory, /proc/sys/vm/overcommit_ratio и используйте ulimit -v, чтобы ограничить размер виртуальной памяти, которую может использовать программа. чтобы предотвратить завершение программы из-за OOM.