malloc

Сделав несколько проектов успешно работающих на 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 байта.