Estoy escribiendo un programador de tareas simple y usando la asignación de memoria dinámica en mi cc430F5137. Estoy de acuerdo en que no es una buena práctica, pero por el momento supongamos que es un requisito de mi aplicación usar la asignación de memoria dinámica.
En mi archivo OS.c
tengo dos estructuras
typedef struct
{
task_t task;
uint8_t next;
uint8_t prev;
uint8_t priority;
} info_t;
typedef struct
{
task_t task;
uint8_t index;
} index_t;
el tamaño de info_t
es de 8 bytes y el tamaño de index_t
es de 6 bytes.
también lo hago
index_t* m_index;
info_t* m_info;
Entonces tengo la función de inicialización en la que hago
m_info = NULL;
m_index = NULL;
Ahora tengo una función registerTask(& task)
que toma la dirección de la función para programar. En esta función hago
m_info = realloc(m_info,(num_registered_tasks + 1) * sizeof(*m_info));
y luego establezca los valores de .priority, next,task y prev.
Entonces lo hago
m_index = realloc(m_index,(num_registered_tasks + 1) * sizeof(*m_index));
y establezca los valores de task e index. y hacernum_registered_tasks++;
Mi pregunta es que como se está realloc()
comportando en este sentido.
Supongamos que mi espacio de memoria, First Task está registrado, por lo que tendrá los primeros 8 bytes para m_info[0]
y los siguientes 6 bytes para m_index[0]
. Ahora, cuando mi segunda tarea llame a esta función, ¿qué sucederá? Lo que supongo es que para m_info primero buscará 16 bytes de datos continuos y solo los encontrará después de los primeros 14 bytes, cambiará la dirección m_info[0]
y copiará el contenido y luego agregará m_info[1]
. Y cuando m_index
se llama, solo encontrará 12 bytes después de este (14 + 16) bytes y lugar m_index[0]
y m_index[1]
aquí.
Si esto es cierto, ¿se está desperdiciando todo mi espacio anterior?
Si me equivoco, ¿cómo funcionará esta función?
¿Cómo puedo utilizar el espacio anterior también?
Necesito index_t
una estructura para implementar algún tipo de algoritmo de búsqueda, por lo que también es necesario
El uso de la asignación de memoria dinámica en una MCU de 16 bits con 4 kb de RAM es una ingeniería muy pobre.
No tanto por los habituales problemas de fugas de memoria. No tanto por la fragmentación de la memoria del montón. Ni siquiera por el elevado tiempo de ejecución necesario para las rutinas de asignación. Sino porque es completamente inútil y no tiene sentido.
Tiene algunos requisitos del mundo real que indican lo que debe hacer su programa y, según estos, su programa necesitará usar exactamente x bytes de RAM para manejar el peor de los casos . No necesitará menos, no necesitará más, necesitará la cantidad exacta de RAM, que se puede determinar en el momento de la compilación.
No tiene sentido guardar parte de los 4kb, dejándolos sin usar. ¿Quién los va a usar? Del mismo modo, no tiene sentido asignar más memoria de la necesaria para el peor de los casos. Simplemente asigne estáticamente tanta memoria como sea necesario, punto.
Además, tiene el peor de los casos con el uso máximo de la pila, en el que se encuentra en lo más profundo de una pila de llamadas y todas las interrupciones que están habilitadas se han disparado. Este peor de los casos puede calcularse en tiempo de compilación o medirse en tiempo de ejecución. Una buena práctica es asignar más RAM de la que se necesita en el peor de los casos, para evitar desbordamientos de la pila (la pila también es en realidad un área de memoria dinámica). O más bien: use cada byte de RAM no utilizado por su programa para la pila.
Si su aplicación necesita exactamente 3 kb de RAM, debe usar el 1 kb restante para la pila.
La asignación dinámica/el montón de la computadora está diseñado para usarse en aplicaciones alojadas como Windows o Linux, donde cada proceso tiene una cantidad fija de RAM y, además, puede usar la memoria del montón para usar tanta RAM como haya en el hardware. Solo tiene sentido utilizar la asignación dinámica en sistemas multiproceso tan complejos, con grandes cantidades de RAM disponibles.
En un programa MCU, ya sea bare metal o basado en RTOS, debe darse cuenta de que las implementaciones de montón funcionan así: reserve una cantidad fija, x kb de RAM para el montón. Cada vez que usa la asignación dinámica, se le entrega una parte de esta cantidad fija de memoria. Entonces, ¿por qué no simplemente tomar esos x kb que usaría para asignar el montón y usarlos para almacenar sus variables?
He ampliado esto con una explicación más detallada y más problemas posibles, aquí:
¿Por qué no debería usar la asignación de memoria dinámica en sistemas integrados?
Esto se llama fragmentación de la memoria.
Hay todo tipo de algoritmos sofisticados para manejarlo, pero la sobrecarga de contabilidad los hace inadecuados para una MCU pequeña.
En su lugar, puede escribir sus propias rutinas de asignación de memoria en función de su patrón de uso esperado. Puede tener una rutina separada para cada subproceso de ejecución, por ejemplo. Si puede garantizar que free() ocurra en el orden opuesto a alloc(), entonces puede usar un puntero simple al final de la memoria.
O podría usar un nivel de direccionamiento indirecto: acceda a la memoria solo a través de un puntero a un puntero. Luego, puede recolectar basura libremente en la memoria y ajustar la tabla de punteros para que apunte a la memoria donde sea que termine.
La mejor respuesta para los sistemas integrados suele ser evitar por completo la asignación de memoria dinámica.
Después de muchos comentarios y respuestas de no usar memoria dinámica, todavía encontré una manera que en ese momento resolvió mi problema.
Cambié esto en mi código
typedef struct
{
task_t task;
uint8_t next;
uint8_t prev;
uint8_t priority;
} info_t;
typedef struct
{
task_t task;
uint8_t index;
} index_t;
typedef struct
{
info_t m_info;
info_t m_index;
} combined_t;
combined_t* m_combined;
Con esto, ahora mi realloc() asigna memoria contigua a m_combined y se resuelve mi problema de fragmentación. (Bueno, supongo que porque después de analizar la memoria paso a paso).
typedef struct { int a; } foo_t; foo_t bar;
es un mal diseño, mientras que struct foo_s { int a; }; struct foo_s bar;
es un buen diseño? Si es así, ¿por qué?
diente filoso
brahans
Pablo A. Clayton