Пользовательский дизайн заголовка реализации malloc()

Я пытаюсь написать собственный распределитель для целей отладки (в качестве упражнения) на C, где я буду использовать один связанный список для хранения свободного списка памяти с использованием алгоритма первого соответствия. Ниже я показал структуру, которую я хотел бы создать в "пустом узле памяти".

Как записать блок заголовка (точнее, объединение) в первых нескольких байтах памяти, которые я получаю (я использую malloc() для первоначального получения фрагмента памяти), чтобы оставшиеся байты были свободны?

Это союз, который я использую:

/*Define Header Structure for proper alignment*/
union header {
struct{
    union header* next;
    unsigned size ; /*Make it size_t*/
}s; 
double dummy_align_var;
};

-------------------------------------------------------------------------------
|Next        |Size of  |16Byte| User is concerned only about |16Byte|         |
|Free Memory |Allocated|Header| this portion  of memory      |Footer|Checksum |
|Address     |Block    |Picket| and has no knowledge of rest |Picket|         |
-------------------------------------------------------------------------------
|-------Header---------|      ^Address Returned to user
                              ^------User Requested Size-----^
^-------------Memory Obtained From The Operating System-----------------------^
*/

[EDIT] Изменена структура блока в соответствии с предоставленными предложениями.


person BumbleBee    schedule 09.11.2009    source источник
comment
HappyUser, если это домашнее задание .. пожалуйста, просто отметьте его как таковое (я не буду редактировать этот пост). Вы проделали значительный объем работы самостоятельно и, очевидно, понимаете, что пытаетесь реализовать, у вас не возникнет проблем с получением полезных ответов. Пожалуйста, рассмотрите возможность повторной пометки, если это действительно домашнее задание, или отметьте, что в противном случае его нет в вашем вопросе.   -  person Tim Post♦    schedule 09.11.2009
comment
Нет, это НЕ домашнее задание! Я даже не студент CS :-) У меня родственный опыт (ошибаюсь EE). Я пишу этот код как тест на мое понимание вещей, структур данных и, возможно, программирования на низком уровне C. Уважаю все комментарии :-)   -  person BumbleBee    schedule 09.11.2009
comment
Хороший вопрос. Мне нравится ascii-графика расположения памяти и т. д.   -  person Bill Forster    schedule 10.11.2009


Ответы (7)


Для отладки malloc рассмотрите возможность добавления пробела между вашей управляющей структурой и началом пользовательских данных, а также между концом пользовательских данных и контрольной суммой. Один байт заполнения должен быть нулевым байтом 0x00, поэтому строковые операции останавливаются; подумайте о том, чтобы поставить другой как 0xFF. Если у вас есть фиксированный шаблон и вы заметили, что он изменился, вы знаете, что что-то вышло за пределы границ, но есть большая вероятность, что ваши конфиденциальные управляющие данные не были растоптаны. Если вы используете 16 байтов заполнения по обе стороны от пространства, выделенного пользователю, вы можете дойти до того, что поместите 4 байта нулей с соответствующим выравниванием (следовательно, нулевое 4-байтовое целое число) и, возможно, 0xFFFFFFFF для -1. Кроме того, поскольку вы, вероятно, округлите запрошенный размер до кратного размера вашего базового блока, установите известное значение для байтов, которые не предназначены для использования пользователем, и убедитесь, что они остаются неизменными. Это обнаружит модификации «один сверх выделенной длины» или всего несколько байтов сверх выделенной длины, которые в противном случае могут остаться незамеченными.

Единственным недостатком нулевого байта в заполнении является то, что вы не сможете легко обнаружить операции чтения, которые не останавливаются в конце выделенной памяти при поиске нулевого байта. Вы можете получить представление об этом, если у вас есть альтернативный вариант, который использует заполнение без нулевых байтов.

Другой вариант, который следует рассмотреть, — это попытка полностью отделить ваши управляющие данные от памяти, возвращаемой пользователю. Конечно, полное разделение невозможно, но, по крайней мере, поддерживайте список выделений (с размерами и указателями) отдельно от выделенных блоков. Опять же, это дает вам защиту, помещая ваши драгоценные управляющие данные дальше от неконтролируемых операций по вытаптыванию памяти. Вы не полностью защищены от ошибочных указателей, но вы защищены лучше. (И вы все еще можете обеспечить буферные зоны вокруг выделенного пространства для обнаружения неконтролируемой записи.) Однако этот дизайн заметно отличается от вопроса.


Предполагая, что вы получаете свой блок памяти от 'malloc()', вы должны сделать - примерно:

void *my_malloc(size_t nbytes)
{
    size_t reqblocks = (nbytes + sizeof(header) - 1) / sizeof(header);
    size_t reqspace  = (reqblocks + 2) * sizeof(header) + 2 * sizeof(padding);
    void *space = malloc(reqspace);
    if (space == 0)
        return space;
    void *retval = (char *)space + sizeof(header) + sizeof(padding);
    header *head = space;
    head->next = ...next...;
    head->size = nbytes;
    ...set head padding to chosen value...
    ...set tail padding to chosen value...
    ...set gap between nbytes and block boundary to chosen value...
    return retval;
}

Осталось интерпретировать...

person Jonathan Leffler    schedule 10.11.2009

я бы сделал что-то вроде

#define MEM_ALIGN 4 // 8 on 64b eventually

struct header {
    union aligned_header {
        struct _s {
            union aligned_header* next;
            size_t size;
        } data;
        char dummy_align_var[sizeof(struct _s) + sizeof(struct _s)%MEM_ALIGN];
    } header_data;
    char user_address;
};

и вернуть &user_address.

person Aszarsha    schedule 09.11.2009
comment
Какое объяснение вам нужно? - person Aszarsha; 09.11.2009
comment
На самом деле несколько вопросов: - 1. dummy_align_var[] имеет тип char, а не просто двойной, потому что user_address имеет тот же тип (проблемы заполнения структуры IMO), верно? 2.Почему dummy_align_var[sizeof(struct _s) + sizeof(struct _s)%MEM_ALIGN]; а не dummy_align_var[MEM_ALIGN]; Я думаю, мне нужно правильно понять выравнивание. 3.char user_address используется только для того, чтобы взять адрес (один после заголовка), который будет передан пользователю, и только из-за этого используется внешняя структура. Верно? Буду признателен, если вы подтвердите/опровергнете мое понимание. С уважением - person BumbleBee; 09.11.2009
comment
Рациональное значение union aligned_header и char dummy_align_var[sizeof(struct _s) + sizeof(struct _s)%MEM_ALIGN] заключается в следующем: вы хотите, чтобы пользовательские данные были выровнены (см. ошибки шины, это также может привести к снижению производительности в зависимости от цели). MEM_ALIGN используется для определения этого выравнивания. В x86 объединение + фиктивная переменная не является обязательным, поскольку указатель + size_t выравниваются. dummy_align_var представляет собой массив байтов такого размера, так как он должен быть не менее sizeof(struct _s) в объединении, чтобы быть полезным, плюс sizeof(struct _s)%MEM_ALIGN байтов, если sizeof(struct _s) не кратно MEM_ALIGN. да user_address только для адреса - person Aszarsha; 09.11.2009
comment
вы должны использовать sizeof(void*) вместо жесткого кодирования 4 и комментируя, что оно должно быть 8 на 64-битном... ... ... НА САМОМ ДЕЛЕ, если подумать, вы должны жестко кодировать 8, так как вы хотите 64-битные величины должны быть выровнены даже на 32-битных, и вызывающая сторона может захотеть использовать compxchg8b (64-битное сравнение и подкачка) или подобное для адреса кучи. - person asveikau; 10.11.2009
comment
Я бы использовал что-то вроде char user_address[0], чтобы структура не выделяла память для char, но вы все равно можете использовать ее для ссылки на данные после структуры. Кроме того, ваш dummy_align_var выглядит довольно хрупким... Я думаю, что (sizeof(struct _s) + MEM_ALIGN - 1) / MEM_ALIGN * MEM_ALIGN будет работать лучше (если только я не правильно понимаю ваши намерения). - person strager; 10.11.2009

Почему вы используете союз? Просто используйте struct и верните &dummy_align_var пользователю в качестве начала свободного блока.

Да, и так как это для отладки, я предлагаю вам добавить mungwall: поместите 16 байт с каждой стороны пользовательской области и заполните их каким-нибудь шаблоном (например, 0xdeadbeef, повторенным четыре раза). Во время free() проверьте, чтобы эти байты не изменились.

[EDIT] Вот некоторый псевдокод:

struct header {
    struct header * next;
    unsigned size;
    // put mungwall here
    double user_data;
};

init()
    int blockSize = 1024;
    char * bigMem = original_malloc(blockSize);
    struct header * first = (struct header *)bigMem;
    first->next = NULL;
    first->size = blockSize - (sizeof(struct header) - sizeof(double));
person Aaron Digulla    schedule 09.11.2009
comment
Когда вы назначаете распределенный буфер struct header *, все, что вам нужно сделать, это присвоить значения атрибутов. Планируете ли вы выделять 1 большой блок памяти и распределять блоки из него, или выделять каждый запрос, который вы получаете (по сути, добавляя только небольшой размер служебных данных для вашего собственного администрирования?) - person rsp; 09.11.2009
comment
Просто используйте код, который вы разместили в комментарии. Поскольку указатель p будет указывать на нужную память, данные будут заканчиваться в нужных местах. Ваша ошибка в том, что вы используете союз: пользователь получит тот же адрес, что и p, и перезапишет структуру, которую вы только что заполнили. - person Aaron Digulla; 09.11.2009
comment
Планируете ли вы выделять 1 большой блок памяти и выделять из него блоки или выделять каждый запрос, который вы получаете (по сути, добавляя только небольшой размер служебных данных для вашего собственного администрирования? Да, пока начальный блок не будет полностью израсходован, я не называю снова malloc.Все запросы пользователей памяти назначаются из начального фрагмента, который я собираю из ОС (используя malloc или системные вызовы) - person BumbleBee; 09.11.2009

Вы также можете объявить dummy_align_var как union header* prev, чтобы поместить свободные блоки памяти в двусвязный список.

Это очень помогает производительности, когда вы хотите объединить освобожденный блок с предыдущим и следующим черным цветом, если они тоже свободны.

Наконец, вы не упоминаете об этом, сохранение списка, отсортированного по размеру, позволяет быстрее найти лучший блок для выделения для данного запроса, в то время как сортировка по адресу упрощает объединение освобожденных блоков. Если вы хотите сделать и то, и другое, сделайте пользовательскую часть как минимум 3 header* большой, она будет соответствовать указателям, необходимым при освобождении блока.

В дополнение к границам, упомянутым Аароном, перезапишите освободившиеся буферы тем же шаблоном. Это облегчает распознавание кода, использующего уже освобожденные буферы.

person rsp    schedule 09.11.2009

Я предполагаю, что это было бы полезно: несколько лет назад мне нужно было сделать резервную копию средства malloc() для целей отладки (трассировщик распределения и т. Д.) ... И было довольно просто взять реализацию FreeBSD из их libstdc. Насколько я помню поздние выпуски FreeBSSD 5.0 ​​или даже 4.x, но забавно было то, что их средства были изолированы в простом библиотечном модуле malloc.o, поэтому перегрузка этого уровня была очень простой копией и вставкой, а реализация была действительно хорошей.

Вы действительно делаете всю эту работу? Да, это только для проверки, я не претендую на то, что это решение лучшее.

person Roman Nikitchenko    schedule 09.11.2009

Вы можете использовать свой исходный союз, если хотите, например:

union header *hdr = malloc(total_size);
void *user_ptr = hdr + 1;
char *trailer_ptr = (char *)user_ptr + user_size;

Это установит user_ptr там, где начнется следующий union header, если блок malloced будет рассматриваться как массив этих объединений. Так что это значение, которое вы возвращаете пользователю.

Он также устанавливает trailer_ptr для указания на первый байт после выделения пользователем, где вы можете поместить свою контрольную сумму.

person caf    schedule 09.11.2009
comment
Не могли бы вы объяснить: void user_ptr = hdr + 1; Я подумал, что могу получить это следующим образом: #define HEADERSIZE (align(sizeof(union header))) void unser_ptr = (void*)((char*)hdr + HEADERSIZE); Я ужасно ошибаюсь? - person BumbleBee; 10.11.2009
comment
align() определяется как size_t align(size_t size){return ((size + ALIGNMENT - 1) & ~(ALIGNMENT - 1)); } - person BumbleBee; 10.11.2009
comment
Это тоже может сработать, но весь смысл вашего double dummy_align_var; состоит в том, чтобы сделать ваш союз нужного размера для правильного выравнивания, без этих трюков (предполагается, что double - это тип с самыми строгими требованиями к выравниванию) . Таким образом, вы можете использовать более простой код. - person caf; 10.11.2009

Если вы не хотите использовать malloc(), посмотрите на sbrk

person Aif    schedule 09.11.2009