Сделав несколько проектов успешно работающих на STM32 , причем довольно не простых ( как мне кажется): USB device,USB Host, RNDIS HTTP server вдруг при скрещивании lwip rndis http server с FreeRTOS решил наконец-то разобраться с динамическим выделением памяти через malloc.
И удивлению не было предела - а динамическое ли это вообще выделение памяти? Мы же не имеем дело с диспетчером памяти типа как во FreeRTOS heap_4 (pvPortMalloc) или в LWIP (mem_malloc), где где-то в программе кто-то следит за выделением памяти в SRAM через добавление доп. информации к выделяемых блокам памяти (ссылки на другие выделенные блоки и т.д.).
Просто попробовав несколько раз вызвать malloc в main.c удивлен был донельзя. Правда не забываем , что мы еще используем FreeRTOS v.1.
main()
{
size_t size = 0xfff;
for(uint32_t kk=0; kk < 200 ; kk++)
{
void * mem = malloc(size);
if(mem == 0)
break;
aaa(mem , size , kk);
printf("mem 1 = %p\n", mem);
for(uint32_t ii=0 ; ii < size; ii++)
printf("x%.2X", *(uint8_t*)(mem + ii));
printf("\n");
//free(mem);
}
// заполняет память до предела SRAM ( 0х20000)
// и даже не вылетает в HardFault.
// нормально продолжается далее, стартуют потоки FreeRTOS и т.д.
// все работает как ни в чем не бывало
}
void aaa(uint8_t* mem , size_t size , uint8_t bb)
{
for(uint32_t ii=0 ; ii < size; ii++)
{
*(uint8_t*)(mem + ii) = bb;
}
}
Если потом в потоке freeRTOS остановиться отладчиком и посмотреть карту heap , то вообще никакого влияния malloc на FreeRTOS heap не проявляется. Почему? А может так и должно быть ? Вот это мой самый любимый вопрос.
До некоторого времени я думал , что за malloc стоит какой-то код внутри... Настало время посмотреть на ассемблерный код .
wp = (struct wordlist *) malloc(sizeof(struct wordlist) + l);
_PTR _EXFUN_NOTHROW(malloc,(size_t __size)); // это макрос
и в результате :
void * malloc(size_t __size) _NOTHROW
Сама malloc как оказывается реализована в закрытой библиотеке C stdlib (как я понял). У меня это примерно здесь C:\Program Files (x86)\Atollic\TrueSTUDIO for STM32 9.3.0\....
Мы всегда может поискать глобальным поиском реализацию malloc или другой функции в файлах из каталога Atollic-а (C:\Program Files (x86)\Atollic\TrueSTUDIO for STM32 9.3.0). Но что искать? Сначала лучше посмотреть ассемблерные инструкции ибо там можно увидеть названия функций , которые вызываются внутри malloc.
Мы всегда можем посмотреть на ассемблерный код а файле list.
[.list]
void * mem = malloc(size);
8002320: f640 70ff movw r0, #4095 ; 0xfff
8002324: f000 f9b6 bl 8002694 /
/ переход на выполнение malloc
и вот она сама malloc :
08002694 :
8002694: 4b02 ldr r3, [pc, #8] ; (80026a0 )
8002696: 4601 mov r1, r0
8002698: 6818 ldr r0, [r3, #0]
800269a: f000 b857 b.w 800274c <_malloc_r>
800269e: bf00 nop
80026a0: 20000008 .word 0x20000008
На самом деле далее внутри malloc вызывается _malloc_r и это уже не две-три строчки кода , и похоже надо разбираться ...
0800274c <_malloc_r>:
800274c: b570 push {r4, r5, r6, lr}
800274e: 1ccd adds r5, r1, #3
8002750: f025 0503 bic.w r5, r5, #3
8002754: 3508 adds r5, #8
8002756: 2d0c cmp r5, #12
8002758: bf38 it cc
800275a: 250c movcc r5, #12
800275c: 2d00 cmp r5, #0
800275e: 4606 mov r6, r0
8002760: db01 blt.n 8002766 <_malloc_r+0x1a>
8002762: 42a9 cmp r1, r5
8002764: d903 bls.n 800276e <_malloc_r+0x22>
8002766: 230c movs r3, #12
8002768: 6033 str r3, [r6, #0]
800276a: 2000 movs r0, #0
800276c: bd70 pop {r4, r5, r6, pc}
800276e: f000 fb8b bl 8002e88 <__malloc_lock>
8002772: 4a23 ldr r2, [pc, #140] ; (8002800 <_malloc_r+0xb4>)
8002774: 6814 ldr r4, [r2, #0]
8002776: 4621 mov r1, r4
8002778: b991 cbnz r1, 80027a0 <_malloc_r+0x54>
800277a: 4c22 ldr r4, [pc, #136] ; (8002804 <_malloc_r+0xb8>)
800277c: 6823 ldr r3, [r4, #0]
800277e: b91b cbnz r3, 8002788 <_malloc_r+0x3c>
8002780: 4630 mov r0, r6
8002782: f000 f8d1 bl 8002928 <_sbrk_r>
8002786: 6020 str r0, [r4, #0]
8002788: 4629 mov r1, r5
800278a: 4630 mov r0, r6
800278c: f000 f8cc bl 8002928 <_sbrk_r>
8002790: 1c43 adds r3, r0, #1
8002792: d126 bne.n 80027e2 <_malloc_r+0x96>
8002794: 230c movs r3, #12
8002796: 4630 mov r0, r6
8002798: 6033 str r3, [r6, #0]
800279a: f000 fb76 bl 8002e8a <__malloc_unlock>
800279e: e7e4 b.n 800276a <_malloc_r+0x1e>
80027a0: 680b ldr r3, [r1, #0]
80027a2: 1b5b subs r3, r3, r5
80027a4: d41a bmi.n 80027dc <_malloc_r+0x90>
80027a6: 2b0b cmp r3, #11
80027a8: d90f bls.n 80027ca <_malloc_r+0x7e>
80027aa: 600b str r3, [r1, #0]
80027ac: 18cc adds r4, r1, r3
80027ae: 50cd str r5, [r1, r3]
80027b0: 4630 mov r0, r6
80027b2: f000 fb6a bl 8002e8a <__malloc_unlock>
80027b6: f104 000b add.w r0, r4, #11
80027ba: 1d23 adds r3, r4, #4
80027bc: f020 0007 bic.w r0, r0, #7
80027c0: 1ac3 subs r3, r0, r3
80027c2: d01b beq.n 80027fc <_malloc_r+0xb0>
80027c4: 425a negs r2, r3
80027c6: 50e2 str r2, [r4, r3]
80027c8: bd70 pop {r4, r5, r6, pc}
80027ca: 428c cmp r4, r1
80027cc: bf0b itete eq
80027ce: 6863 ldreq r3, [r4, #4]
80027d0: 684b ldrne r3, [r1, #4]
80027d2: 6013 streq r3, [r2, #0]
80027d4: 6063 strne r3, [r4, #4]
80027d6: bf18 it ne
80027d8: 460c movne r4, r1
80027da: e7e9 b.n 80027b0 <_malloc_r+0x64>
80027dc: 460c mov r4, r1
80027de: 6849 ldr r1, [r1, #4]
80027e0: e7ca b.n 8002778 <_malloc_r+0x2c>
80027e2: 1cc4 adds r4, r0, #3
80027e4: f024 0403 bic.w r4, r4, #3
80027e8: 42a0 cmp r0, r4
80027ea: d005 beq.n 80027f8 <_malloc_r+0xac>
80027ec: 1a21 subs r1, r4, r0
80027ee: 4630 mov r0, r6
80027f0: f000 f89a bl 8002928 <_sbrk_r>
80027f4: 3001 adds r0, #1
80027f6: d0cd beq.n 8002794 <_malloc_r+0x48>
80027f8: 6025 str r5, [r4, #0]
80027fa: e7d9 b.n 80027b0 <_malloc_r+0x64>
80027fc: bd70 pop {r4, r5, r6, pc}
80027fe: bf00 nop
8002800: 20004830 .word 0x20004830
8002804: 20004834 .word 0x20004834
__malloc_lock ... _sbrk_r (выделение памяти) .... __malloc_unlock примерная логика
08002928 <_sbrk_r>:
8002928: b538 push {r3, r4, r5, lr}
800292a: 2300 movs r3, #0
800292c: 4c05 ldr r4, [pc, #20] ; (8002944 <_sbrk_r+0x1c>)
800292e: 4605 mov r5, r0
8002930: 4608 mov r0, r1
8002932: 6023 str r3, [r4, #0]
8002934: f7ff fe10 bl 8002558 <_sbrk>
8002938: 1c43 adds r3, r0, #1
800293a: d102 bne.n 8002942 <_sbrk_r+0x1a>
800293c: 6823 ldr r3, [r4, #0]
800293e: b103 cbz r3, 8002942 <_sbrk_r+0x1a>
8002940: 602b str r3, [r5, #0]
8002942: bd38 pop {r3, r4, r5, pc}
8002944: 200048c4 .word 0x200048c4
Код функций _sbrk_r найти глобальным поиском не удалось, повидимому его и нет , то есть поставляется в виде закрытых библиотек. А хочется... понять , что происходит вообще внутри malloc , понимает ли malloc что делает вообще. Пока складывается впечатление , что нет....
Поиски _sbrk_r вывели случайно на _sbrk прописанную в syscalls.c нашего проекта. Отладчиком установили , что malloc вызывает именно это с-шную функцию _sbrk из своего ассемблерного кода. И вот она многое уже объясняет :
register char * stack_ptr asm("sp");
caddr_t _sbrk(int incr)
{
extern char end asm("end");
// это просто конец физической памяти SRAM
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0)
heap_end = &end;
prev_heap_end = heap_end;
if (heap_end + incr > stack_ptr)
{
// stack_ptr это адрес текущего стека
// write(1, "Heap and stack collision\n", 25);
// abort();
errno = ENOMEM;
return (caddr_t) -1;
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
Не хочется досконально ковыряться в ассемблеоном коде , но _sbrk это почти точно и есть наш диспетчер памяти malloc .
Тут сразу понятно , что происходит : тупо используется heap_end и он понятно растет с каждым malloc.
Начальное значение heap_end устанавливается сразу за окончанием области bss в RAM (_ebss по данным ld файла) . За областью .bss идет ._user_heap_stack:
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . ); // asm("end")
// end это конец инициализированных данных в RAM
// дальше можно использвать для malloc
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >RAM
Это значит, что после помещения всех данных (статичных) в память SRAM остается только _user_heap_stack , который от начала заполняется через malloc , а с конца в обратном направлении пишутся данные стека (STM32 контроллер).
В malloc проверяется только одно условие по сути , что новая позиция heap_end + sizeofBlock должен быть меньше текущего значения регистра sp (stack_ptr) и все. Вот и весь диспетчер памяти на malloc.
Наверное становится понятно , что вызываемый в нескольких потоках malloc становится источником проблем.
Кстати FreeRTOS можно использовать статически , то есть pvPortMalloc просто не будет вызываться . И это для контроллеров по мнению многих профи реально правильный подход. Нет динамического выделения памяти - нет проблем.
Где malloc запоминает сколько байт он выделил ведь возвращает он только указатель на область памяти ?
Смотрим на 2 черехбайтовых слова перед указателем и все становиться очевидно : перед указателем магическое число 0xFFFFFFFC (длиной 4 байта), а перед ним размер выделенного блока в байтах (тоже четыре байта длиной). Значение размера кратно 4 , похоже потому что выделение памяти ведется 32-разрядными значениями , то есть по 4 байта.