Маллок из Newlib "игнорирует" целую кучу свободной памяти
Поэтому я использую голый металл malloc в сочетании с самописным _sbrk. Я запускаю все на панели запуска Stellaris. Эта доска содержит кору M4. Он также содержит 32 КБ ОЗУ, начиная с memaddr 0x20000000 и до 0x20007FFF. В самом начале программы ситуация выглядит следующим образом:
- _ebss - это начало назначаемой памяти, все с более низким адресом, чем _ebss, является consts, программными инструкциями и т. д. Это определяется в сценарии компоновщика. Значение в начале 0x200008b0
- На этапе загрузки программного обеспечения указатель основного стека устанавливается в 0x20007FFF, и стек увеличивается. Это означает, что на начальном этапе для назначения выделено около 30,5 КБ ОЗУ.
_sbrk хочет раздать эту оперативную память и подтверждает правильное количество свободной оперативной памяти: после фазы инициализации (что означает, что стек немного увеличился, поэтому объем свободной оперативной памяти меньше), существуют следующие распечатки памяти:
(S) MSP: 0x20007f4c, heap_end 0x200008b0, diff 30364
(F) MSP: 0x20007f4c, heap_end 0x200008e8, diff 30308
(S) MSP: 0x20007f4c, heap_end 0x200008e8, diff 30308
(F) MSP: 0x20007f4c, heap_end 0x20000fc8, diff 28548
arena: 1816
ordblks: 1
smblks: 0
hblks: 0
hblkhd 0
usmblks: 0
fsmblks: 0
uordblks: -1056
fordblks: 2872
keepcost: 2872
Первые четыре строки взяты из _sbrk, последние две - из mallinfo(). (S) означает в начале _sbrk (до перемещения указателя кучи) и (F) означает после перемещения указателя кучи. Если я могу правильно прочитать документацию mallinfo (боюсь, это сложная часть), то арена означает количество кучи, запрошенное у _sbrk. Это имеет смысл, потому что разница между самым первым напечатанным heap_end и самым последним напечатанным heap_end действительно равна 1816. И fordblks должен означать общее количество свободной кучи, поэтому максимальный объем кучи, который все еще можно запросить у _sbrk. И это неверно. Как вы можете видеть из последнего различия, разница между heap_end и MSP составляет 28K. Конечно, мы хотим сохранить буфер между ними, чтобы MSP мог расти, не повреждая все, но мы хотим выделить более 2,8 КБ оперативной памяти.
Это становится проблемой, когда я позволяю программе работать немного дольше: в конце концов fordblks приблизится к нулю, и malloc начнет возвращать NULL, в то время как heap_end не достиг MSP длинным выстрелом. Таким образом, malloc отказывается выдавать больше памяти, прежде чем это необходимо. Как я могу исправить это behviour? На чем основано значение fordblks?
(S) MSP: 0x20007f34, heap_end 0x20000fc8, diff 28524
(F) MSP: 0x20007f34, heap_end 0x20001fc8, diff 24428
(S) MSP: 0x20007f34, heap_end 0x20001fc8, diff 24428
(F) MSP: 0x20007f34, heap_end 0x20002000, diff 24372
Could not create process timeoutProc30s
Test prepared
arena: 8304
ordblks: 2
smblks: 0
hblks: 0
hblkhd 0
usmblks: 0
fsmblks: 0
uordblks: 7648
fordblks: 656
keepcost: 112
Изменить: Подробнее! Выше вы видите фактический момент, когда Маллок говорит нет: его на Could not create process..
линия. Как вы можете ясно видеть, чуть выше Malloc сделал последнюю попытку получения кучи от _sbrk, и _sbrk обязался (указатель кучи изменен). Также осталось 24 КБ ОЗУ. Я попытался распределить 1024 байта оперативной памяти, когда malloc сказал мне NULL
,
Изменить: файл linker_script.ld:
_stack_buffer = 128; /*128 byte buffer between HEAP and STACK*/
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}
SECTIONS
{
.text :
{
_text = .;
KEEP(*(.isr_vector))
*(.text*)
*(.rodata*)
_etext = .;
} > FLASH
.data : /*AT(ADDR(.text) + SIZEOF(.text))*/
{
_data = .;
*(vtable)
*(.data*)
_edata = .;
} > SRAM AT > FLASH
.bss : AT (ADDR(.data) + SIZEOF(.data))
{
_bss = .;
*(.bss*)
*(COMMON)
_ebss = .;
_end = .;
} > SRAM
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM) - 1; /*The starting point of the stack, at the very bottom of the RAM*/
}
Редактировать: я провел еще несколько исследований по этому вопросу, и я нашел некоторые интересные вещи. Прежде всего: fordblks не связан с реальной пустой кучей, и я не знаю, на чем она основана. Потому что, если вы используете malloc много 100 байтов в цикле true, то неправильная блокировка будет продолжаться до тех пор, пока _sbrk вернет -1, что является ожидаемым поведением. Существуют определенные обстоятельства, когда malloc возвращает NULL без заполнения кучи. Один пример - это когда malloc(1024) возвращает NULL, но допустимо пять malloc(555). Так что, похоже, это как-то связано с внутренностями Маллока.
Отказ от ответственности: в прошлый раз, когда я задал голый металлический вопрос, я был осмеян за тусклость: я не говорю, что newlib Malloc делает что-то не так, и я подозреваю, что мне нужно определить что-то в сценарии компоновщика или что-то, чтобы это исправить. Я осознаю тот факт, что все это моя вина, и я здесь, чтобы спросить, почему эта проблема здесь и как мне нужно исправить свой код, чтобы исправить это поведение. Нельзя сказать, что ребята из Newlib понятия не имеют, что они делают.
1 ответ
Хирон,
- Обычно, если система имеет небольшой объем памяти или высокую нагрузку, любое динамическое распределение максимально избегается. Я бы порекомендовал посмотреть на методы, такие как пулы памяти.
- Менеджеры памяти всегда имеют проблему фрагментации в долго работающей системе. Блоки имеют выравнивания. Например, я видел диспетчеры памяти, которые выделяют как минимум 48 байтов для чего-либо. И ни один менеджер памяти не может честно учитывать вашу предметную область и, в свою очередь, схему использования памяти.
Поэтому я рекомендую избегать динамического выделения памяти и использовать пулы объектов, соответствующих вашим требованиям. Это почти 100% случай для встраиваемых систем, где почти у всех есть "пулы", "киркулярные буферы" и так далее. Надеюсь, что этот совет поможет вам в некотором роде.