Домашняя страница библиотеки_c AVR

Синтаксис языка C

Библиотека языка C GNU glibc

 Страницы развития библиотеки_с AVR

Главная страница

Инструкция пользователя

Содержание библиотеки_c

Часто задаваемые вопросы

Содержание по алфавиту

Демонстрационные проекты


Области памяти и использование malloc()

Введение

Многие устройства, являющиеся возможными адресатами avr-libc, имеют недостаточное количество оперативной памяти (RAM). Младшие из устройств, поддерживаемые средой C имеют 128 байт RAM. Их следует разделить на устройства с инициализированными и неинициализированными переменными (sections .data и .bss), динамическая программа распределения памяти, и стек, который используется для вызовов подпрограмм и сохранения локальных (автоматических) переменных.

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

При стандартном разбиении внутренней RAM, в её начале должны разместиться переменные .data, а затем .bss. Стек растёт вниз, начиная с вершины внутренней RAM. Динамическая память (heap), доступная для программы динамического распределения памяти (malloc) будет помещена после .bss. Таким образом, нет риска того, что heap когда-либо столкнется с переменными стека (если не было ошибок при выполнении программы malloc). Всё же такой риск остаётся, если имеются повышенные требования к heap, или к стеку. Может случиться так, что новые запросы могут не вписаться в "отверстия" предварительно подготовленные программой malloc. Повышенные требования к пространству стека могут возникнуть в функциях C, содержащих большие и/или многочисленные местные переменные, или при рекурсивном запросе функции.

Примечание:

Изображения, приведённые в этом документе относятся к ATmega128, они представлены в нелинейном масштабе.

malloc-std.png

Карта устройства с внутренней RAM

Для такого устройства, как микроконтроллер, необходимо осуществление динамического распределения памяти, которое является достаточно простым, так что размеры кода останутся низкими, и все же достаточно мощным для того, чтобы избежать ненужной фрагментации памяти и получить весё то, что будет выполнено за несколько циклов центрального процессора. Микроконтроллеры часто бедны на память, а также работают на намного более низких частотах, чем современные типовые PC. Программа распределения памяти, осуществленная в avr-libc пытается справляться со всеми этими ограничениями, и предлагает некоторые варианты настройки, которые могут использоваться, если есть больше доступных ресурсов, чем в заданной по умолчанию конфигурации.

Внутренняя против внешней RAM

Очевидно, ограничения намного жестче при выполнении в заданной по умолчанию конфигурации - если доступна только внутренняя RAM. Особая осторожность должна быть предпринята во избежание столкновения динамической памяти стека, и удостовериться, что функции не вкладываются слишком глубоко, и не требуют слишком большого количества пространства стека для местных переменных, так же нужна осторожность при распределении слишком большого количества динамической памяти.

Если имеется внешняя оперативная память, рекомендуется переместить динамическую память во внешнюю оперативную память, независимо от того, в которой памяти располагаются разделы .data и .bss. Стек должен всегда сохраняться во внутренней оперативной памяти. Некоторые устройства даже требуют этого, и вообще, к внутренней оперативной памяти быстрее доступ, так как не требуются никакие дополнительные ожидания. При использовании динамического распределения памяти стека и heap, если они расположены в разных областях памяти, то это - самый безопасный способ избежать столкновения heap и стека.

Настройки для malloc()

Есть множество переменных, которые могут быть настроены так, чтобы приспособить поведение malloc() к ожидаемым результатам и ограничениям приложения. Любые изменения с этими настройками должны быть сделаны перед первым обращением к malloc(). Обратите внимание, что некоторые библиотечные функции также могут использовать heap (особенно <stdio.h>: Стандартные средства ввода / вывода), так что удостоверьтесь, что изменения были сделаны достаточно рано в процессе запуска.

Переменные __malloc_heap_start и __malloc_heap_end могут использоваться для ограничения действия функции malloc () в некоторой области памяти. Эти переменные статически инициализируют __ heap_start и __ heap_end, соответственно.

Если heap перемещается во внешнюю RAM, __malloc_heap_end должен быть соответственно откорректирован. Это может быть сделано во время выполнения записи переменной, или автоматически во время компоновки, регулируя значение __ heap_end.

Следующий пример указывает команде компоновщика - переместить .data, .bss, и heap  в 0x1100 внешней оперативной памяти, heap будет простираться до адреса 0xffff.

avr-gcc ... -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff ...

Примечание:

См. объяснение для смещения 0x800000. См. главу о использование gcc для -Wl варианта.

malloc-x1.png

Внутренняя RAM: только стек, внешняя RAM: переменные и heap

Если heap был перемещён во внешнюю RAM, при удержании внутренних переменных RAM, могло использоваться что-то, подобное следующему. Обратите внимание на это для целей демонстрации, назначение различных областей не было сделано смежным в этом примере, так что есть "дыры" ниже и выше heap во внешнем RAM они остается полностью недоступными правильными переменными или динамическими распределениями памяти.

avr-gcc ... -Wl,--defsym=__heap_start=0x802000,--defsym=__heap_end=0x803fff ...

malloc-x2.png

Внутренняя RAM: переменные и стек, внешняя RAM: heap
(динамическая память)

Если __ malloc_heap_end равен 0, программа распределения пытается обнаружить дно стека, чтобы предотвратить столкновение heap и стека, и получить больше пространства для heap. Это не будет пробовать идти вне текущего предела стека, уменьшенного на байты __ malloc_margin. Таким образом, все возможные записи активации подпрограмм прерывания, которые могли прервать текущую функцию, плюс все дальнейшие вложенные запросы функций, не должны требовать большего количества пространства стека, иначе они рискуют столкнуться с сегментом данных.

 Значение по умолчанию __malloc_ margin равно 32.

Подробности выполнения

Динамическое распределение памяти запрашивает, будет возвращен с двух-байтовым заголовком, предбыл на рассмотрении, который делает запись размера распределения (ассигнования). Позже это используется free(). Возвращенный адрес направляет кроме того заголовок. Таким образом, если приложение случайно пишет прежде, чем возвращенная область памяти, внутренняя последовательность программы распределения памяти скомпрометирована.

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

Сам freelist не поддержан как отдельная структура данных, а скорее изменяя содержание освобожденной памяти, чтобы содержать формирование цепочки указных частей вместе. Тем самым, никакая дополнительная память не reqired, чтобы поддержать этот список если бы не переменная, которая следит за самой низкой долей памяти, доступной для перераспределения. Так как оба, цепной указатель и размер куска должны быть зарегистрированы в каждом куске, минимальный размер куска на freelist - четыре байта.

При распределении памяти, сначала freelist шелся бы, чтобы видеть, могло ли бы это удовлетворить запрос. Если есть кусок, доступный на freelist, который будет соответствовать запросу точно, это будет взято, разъединено от freelist, и возвращено к вызывающей программе. Если бы никакое точное соответствие не могло бы быть найдено, самое близкое соответствие, которое только удовлетворило бы, запрос будет использоваться. Кусок будет обычно разбиваться в один, чтобы быть возвращенным к вызывающей программе, и другому (меньшему), который останется на freelist. В случае, если этот кусок был только до на два байта большего чем запрос, запрос будет просто изменен внутренне, чтобы также объяснить эти дополнительные байты, так как(с тех пор как) никакой отдельный freelist вход не мог быть отколот в том случае.

Если ничто не могло бы быть найдено на freelist, расширение heap предпринято. Это - то, где __malloc_margin will рассмотрите, работает ли динамическая память ниже стека, или где __malloc_heap_end будет проверен иначе.

Если остающаяся память недостаточна, чтобы удовлетворить запрос, NULL will в конечном счете будет возвращен к вызывающей программе.

При запросе free(), новый freelist вход будет готов. Попытка тогда сделана, чтобы объединить новый вход с возможными смежными входами, приводя к одному большему входу, доступному для дальнейших распределений(ассигнований). Тем путем, потенциал для фрагментации динамической памяти с надеждой уменьшен.

Запрос к realloc() сначала определяет, собирается ли операция наращивать или сокращать текущее распределение(ассигнование). При сокращении, случай прост: существующий кусок разбит, и хвост области, которая больше не должна использоваться, передается к стандартной функции free()для вставки в freelist. Проверки сначала сделаны, является ли кусок хвоста достаточно большим, чтобы держать собственный кусок вообще, иначе realloc() просто не сделает ничего, и возвратит первоначальную область.

При росте области, это сначала проверено, может ли существующее распределение(ассигнование) быть расширено оперативно. Если так, это сделано, и первоначальный указатель возвращен, не копируя любое содержание данных. Как побочный эффект, эта проверка будет также делать запись размера наибольшего куска на freelist.

Если область не может быть расширена оперативно, но старый кусок - наверху динамической памяти, и вышеупомянутый freelist обход не показывал достаточно большой кусок на freelist, чтобы удовлетворить новый запрос, попытка сделана, чтобы быстро расширить этот самый верхний кусок (и таким образом heap), так что никакая потребность не возникает, чтобы копировать по существующим данным. Если нет больше пространства, доступного в динамической памяти (та же самая проверка сделана как в malloc()), полный запрос будет терпеть неудачу.

Иначе, malloc() будет вызываться с новым размером запроса, существующие данные будут скопированы, и free() будет вызываться старую область.

Hosted by uCoz