tipos - comportamiento malloc en un sistema integrado
sistemas integrados de gestion ppt (3)
Actualmente estoy trabajando en un proyecto integrado (STM32F103RB, CooCox CoIDE v.1.7.6 con arm-none-eabi-gcc 4.8 2013q4) y estoy tratando de entender cómo se comporta malloc()
en la C
simple cuando la RAM está llena .
Mi STM32 tiene 20kB = 0x5000Bytes de RAM, 0x200 se usan para la pila.
#include <stdlib.h>
#include "stm32f10x.h"
struct list_el {
char weight[1024];
};
typedef struct list_el item;
int main(void)
{
item * curr;
// allocate until RAM is full
do {
curr = (item *)malloc(sizeof(item));
} while (curr != NULL);
// I know, free() is missing. Program is supposed to crash
return 0;
}
Esperaría que malloc()
devuelva NULL
tan pronto como el montón sea demasiado pequeño para asignar:
0x5000
(RAM) - 0x83C
(bss) - 0x200
(pila) = 0x45C4
(pila)
Entonces al ejecutar el malloc()
por decimoctava vez. Un artículo es 1024 = 0x400
Bytes de gran tamaño.
Pero en su lugar el uC llama al HardFault_Handler(void)
después de la 18ª vez (ni siquiera al MemManager_Handler(void)
)
¿Alguien tiene un consejo sobre cómo pronosticar un error de malloc()
? Ya que esperar un retorno NULL
no parece funcionar.
Gracias.
Es muy probable que su programa falle debido a un acceso ilegal a la memoria , que casi siempre es un resultado indirecto (posterior) de un acceso legal a la memoria , pero que no tuvo la intención de realizar.
Por ejemplo (que también es mi suposición sobre lo que está sucediendo en su sistema):
Tu montón probablemente comienza justo después de la pila. Ahora, supongamos que tiene un desbordamiento de pila en main
. Entonces, una de las operaciones que realiza en main
, que es naturalmente una operación legal en lo que a usted respecta, anula el comienzo del montón con algunos datos "basura".
Como resultado posterior, la próxima vez que intente asignar memoria desde el montón, el puntero al siguiente fragmento disponible de memoria ya no es válido, lo que eventualmente dará lugar a una violación de acceso a la memoria.
Entonces, para empezar, le recomiendo encarecidamente que aumente el tamaño de la pila de 0x200 bytes a 0x400 bytes. Esto se define típicamente dentro del archivo de comando del enlazador, o a través del IDE, en la configuración del enlazador del proyecto.
Si su proyecto está en IAR, puede cambiarlo en el archivo icf
:
define symbol __ICFEDIT_size_cstack__ = 0x400
Aparte de eso, le sugiero que agregue código en su HardFault_Handler
, para reconstruir la pila de llamadas y registrar valores antes del bloqueo. Esto podría permitirle rastrear el error de tiempo de ejecución y averiguar exactamente dónde sucedió.
En el archivo ''startup_stm32f03xx.s'', asegúrese de tener el siguiente fragmento de código:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler
Luego, en el mismo archivo, agregue el siguiente controlador de interrupciones (donde se encuentran todos los demás controladores):
PUBWEAK HardFault_Handler
SECTION .text:CODE:REORDER(1)
HardFault_Handler
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B HardFault_Handler_C
Luego, en el archivo ''stm32f03xx.c'', agregue el siguiente ISR:
void HardFault_Handler_C(unsigned int* hardfault_args)
{
printf("R0 = 0x%.8X/r/n",hardfault_args[0]);
printf("R1 = 0x%.8X/r/n",hardfault_args[1]);
printf("R2 = 0x%.8X/r/n",hardfault_args[2]);
printf("R3 = 0x%.8X/r/n",hardfault_args[3]);
printf("R12 = 0x%.8X/r/n",hardfault_args[4]);
printf("LR = 0x%.8X/r/n",hardfault_args[5]);
printf("PC = 0x%.8X/r/n",hardfault_args[6]);
printf("PSR = 0x%.8X/r/n",hardfault_args[7]);
printf("BFAR = 0x%.8X/r/n",*(unsigned int*)0xE000ED38);
printf("CFSR = 0x%.8X/r/n",*(unsigned int*)0xE000ED28);
printf("HFSR = 0x%.8X/r/n",*(unsigned int*)0xE000ED2C);
printf("DFSR = 0x%.8X/r/n",*(unsigned int*)0xE000ED30);
printf("AFSR = 0x%.8X/r/n",*(unsigned int*)0xE000ED3C);
printf("SHCSR = 0x%.8X/r/n",SCB->SHCSR);
while (1);
}
Si no puede usar printf
en el punto de la ejecución cuando se produce esta interrupción específica de Hard-Fault, entonces guarde todos los datos anteriores en un búfer global, para que pueda verlos después de alcanzar el while (1)
.
Luego, consulte la sección ''Excepciones y Registros de Fallas Cortex-M'' en http://www.keil.com/appnotes/files/apnt209.pdf para comprender el problema, o publique el resultado aquí si desea más ayuda. .
ACTUALIZAR:
Además de todo lo anterior, asegúrese de que la dirección base del montón esté definida correctamente. Posiblemente está codificado dentro de la configuración del proyecto (generalmente justo después de la sección de datos y la pila). Pero también se puede determinar durante el tiempo de ejecución, en la fase de inicialización de su programa. En general, debe verificar las direcciones base de la sección de datos y la pila de su programa (en el archivo de mapa creado después de compilar el proyecto), y asegurarse de que el montón no se superponga con ninguno de ellos.
Una vez tuve un caso en el que la dirección base del montón se estableció en una dirección constante, lo que estuvo bien para empezar. Pero luego aumenté gradualmente el tamaño de la sección de datos, agregando variables globales al programa. La pila se ubicó justo después de la sección de datos, y "avanzó" a medida que la sección de datos crecía, por lo que no hubo problemas con ninguno de ellos. Pero, finalmente, el montón se asignó "sobre la parte" de la pila. Entonces, en algún punto, las operaciones de montón comenzaron a anular las variables en la pila, y las operaciones de pila comenzaron a anular los contenidos del montón.
No parece que malloc
esté haciendo ningún control en absoluto. La falla que obtienes proviene del hardware que detecta una escritura en una dirección no válida, que probablemente provenga de malloc
.
Cuando malloc
asigna memoria, toma un fragmento de su grupo interno y se lo devuelve. Sin embargo, necesita almacenar cierta información para la función free
para poder completar la desasignación. Por lo general, esa es la longitud real del fragmento. Para guardar esa información, malloc
toma unos pocos bytes desde el comienzo del fragmento en sí, escribe la información allí y le devuelve la dirección más allá del lugar donde ha escrito su propia información.
Por ejemplo, supongamos que solicitó un fragmento de 10 bytes. malloc
tomaría un fragmento disponible de 16 bytes, por ejemplo, en las direcciones 0x3200..0x320F
, escriba la longitud (es decir, 16) en los bytes 1 y 2, y devuelva 0x3202
a usted. Ahora su programa puede usar diez bytes de 0x3202
a 0x320B
. Los otros cuatro bytes también están disponibles: si llama a realloc
y solicita 14 bytes, no habrá reasignación.
El punto crucial viene cuando malloc
escribe la longitud en la porción de memoria que está a punto de devolverle: la dirección a la que escribe debe ser válida. Parece que después de la 18ª iteración, la dirección del siguiente fragmento es negativa (lo que se traduce en un gran positivo), por lo que la CPU atrapa la escritura y desencadena la falla grave.
En situaciones en las que el montón y la pila crecen uno hacia el otro, no existe una manera confiable de detectar una memoria insuficiente y, a la vez, de utilizar cada último byte de memoria, lo que a menudo es algo muy deseable. malloc
no puede predecir cuánta pila va a utilizar después de la asignación, por lo que ni siquiera intenta. Es por eso que el conteo de bytes en la mayoría de los casos depende de ti.
En general, en hardware incrustado cuando el espacio está limitado a unas pocas docenas de kilobytes, se evitan llamadas malloc
en lugares "arbitrarios". En su lugar, asigna previamente toda su memoria usando algunos límites precalculados, y la distribuye a estructuras que lo necesitan, y nunca vuelve a llamar a malloc
.
Usar c malloc
estándar es muy difícil de distinguir y malloc
parece fallar desde mi punto de vista. Para que pueda administrar la memoria implementando algunos malloc
personalizados usando su dirección RAM.
No estoy seguro de que esto te ayude, pero he hecho algo de malloc
personalizado en mi proyecto relacionado con el controlador, es el siguiente
#define LENGTH_36_NUM (44)
#define LENGTH_52_NUM (26)
#define LENGTH_64_NUM (4)
#define LENGTH_128_NUM (5)
#define LENGTH_132_NUM (8)
#define LENGTH_256_NUM (8)
#define LENGTH_512_NUM (18)
#define LENGTH_640_NUM (8)
#define LENGTH_1536_NUM (6)
#define CUS_MEM_USED (1)
#define CUS_MEM_NO_USED (0)
#define CALC_CNT (0)
#define CALC_MAX (1)
#define __Ram_Loc__ (0x20000000) ///This is my RAM address
#define __TOP_Ram_Loc__ (0x20000000 + 0x8000 -0x10) //Total 32K RAM and last 16 bytes reserved for some data storage
typedef struct _CUS_MEM_BLOCK_S {
char used;
int block_size;
char *ptr;
char *next;
} cus_mem_block_s;
static struct _MEM_INFO_TBL_S {
int block_size;
int num_max;
cus_mem_block_s *wm_head;
int calc[2];
} memInfoTbl[] = {
{36, LENGTH_36_NUM , 0, {0,0} },
{52, LENGTH_52_NUM , 0, {0,0} },
{64, LENGTH_64_NUM , 0, {0,0} },
{128, LENGTH_128_NUM , 0, {0,0} },
{132, LENGTH_132_NUM , 0, {0,0} },
{256, LENGTH_256_NUM , 0, {0,0} },
{512, LENGTH_512_NUM , 0, {0,0} },
{640, LENGTH_640_NUM , 0, {0,0} },
{1536,LENGTH_1536_NUM, 0, {0,0} },
};
#define MEM_TBL_MAX (sizeof(memInfoTbl)/sizeof(struct _MEM_INFO_TBL_S))
BOOL MemHeapHasBeenInitialised = FALSE;
Esto básicamente define macro para la dirección de la RAM y ha elegido manualmente más número de bloque para el tamaño del bloque que frecuentemente requiere asignar, Al igual que 36 bytes me requieren más, así que tomo más número para ello.
Esta es la función init para mem init
void cus_MemInit(void)
{
int i,j;
cus_mem_block_s *head=NULL;
unsigned int addr;
addr = __Ram_Loc__;
for(i=0; i<MEM_TBL_MAX; i++)
{
head = (char *)addr;
memInfoTbl[i].wm_head = head;
for(j=0;j<memInfoTbl[i].num_max; j++)
{
head->used =CUS_MEM_NO_USED;
head->block_size = memInfoTbl[i].block_size;
head->ptr = (char *)(addr + sizeof(cus_mem_block_s));
addr += (memInfoTbl[i].block_size + sizeof(cus_mem_block_s));
head->next =(char *)addr;
head = head->next;
if(head > __TOP_Ram_Loc__)
{
printf("%s:error./n",__FUNCTION__);
return;
}
}
}
head->ptr = 0;
head->block_size = 0;
head->next = __Ram_Loc__;
MemHeapHasBeenInitialised=TRUE;
}
Este para la asignación
void* CUS_Malloc( int wantedSize )
{
void *pwtReturn = NULL;
int i;
cus_mem_block_s *head;
if(MemHeapHasBeenInitialised == FALSE)
goto done_exit;
for(i=0; i<MEM_TBL_MAX; i++)
{
if(wantedSize <= memInfoTbl[i].block_size)
{
head = memInfoTbl[i].wm_head;
while(head->ptr)
{
if(head->used == CUS_MEM_NO_USED)
{
head->used = CUS_MEM_USED;
pwtReturn = head->ptr;
goto done;
}
head = head->next;
}
goto done;
}
}
done:
if(pwtReturn)
{
for(i=0; i<MEM_TBL_MAX; i++)
{
if(memInfoTbl[i].block_size == head->block_size)
{
memInfoTbl[i].calc[CALC_CNT]++;
if(memInfoTbl[i].calc[CALC_CNT] > memInfoTbl[i].calc[CALC_MAX] )
memInfoTbl[i].calc[CALC_MAX]=memInfoTbl[i].calc[CALC_CNT];
break;
}
}
}
done_exit:
return pwtReturn;
}
Este gratis
void CUS_Free(void *pm)
{
cus_mem_block_s *head;
char fault=0;
if( (pm == NULL) || (MemHeapHasBeenInitialised == FALSE) )
goto done;
if( (pm < __RamAHB32__) && (pm > __TOP_Ram_Loc__) )
{
printf("%s:over memory range/n",__FUNCTION__);
goto done;
}
head = pm-sizeof(cus_mem_block_s);
if(head->used)
head->used = CUS_MEM_NO_USED;
else
{
printf("%s:free error/n",__FUNCTION__);
fault=1;
}
if(fault)
goto done;
int i;
for(i=0;i<MEM_TBL_MAX;i++)
{
if(memInfoTbl[i].block_size == head->block_size)
{
memInfoTbl[i].calc[CALC_CNT]--;
goto done;
}
}
done:;
}
Después de todo, puedes usar la función anterior como
void *mem=NULL;
mem=CUS_Malloc(wantedsize);
Luego también puede ver su memoria usada de la siguiente manera
void CUS_MemShow(void)
{
int i;
int block_size;
int block_cnt[MEM_TBL_MAX];
int usedSize=0, totalSize=0;
cus_mem_block_s *head;
if(MemHeapHasBeenInitialised == FALSE)
return;
memset(block_cnt, 0, sizeof(block_cnt));
head = memInfoTbl[0].wm_head;
i=0;
block_size = head->block_size;
vTaskSuspendAll();
while( head->ptr !=0)
{
if(head->used == CUS_MEM_USED )
{
block_cnt[i]++;
usedSize +=head->block_size;
}
usedSize += sizeof(cus_mem_block_s);
totalSize += (head->block_size+ sizeof(cus_mem_block_s));
/* change next memory block */
head = head->next;
if( block_size != head->block_size)
{
block_size = head->block_size;
i++;
}
}
xTaskResumeAll();
usedSize += sizeof(cus_mem_block_s);
totalSize+= sizeof(cus_mem_block_s);
dprintf("----Memory Information----/n");
for(i=0; i<MEM_TBL_MAX; i++) {
printf("block %d used=%d/%d (max %d)/n",
memInfoTbl[i].block_size, block_cnt[i],
memInfoTbl[i].num_max,
memInfoTbl[i].calc[CALC_MAX]);
}
printf("used memory=%d/n",usedSize);
printf("free memory=%d/n",totalSize-usedSize);
printf("total memory=%d/n",totalSize);
printf("--------------------------/n");
}
En general, he precalculado la memoria primero y luego doy lo que tengo.