Pregunta

Estoy programando en C para microcontroladores integrados con RAM con RTOS.

Regularmente rompo mi código en funciones cortas, pero cada función que requiere requiere más memoria de pila. Cada tarea necesita su pila, y este es uno de los consumidores de memoria más importantes en el proyecto.

¿Existe una alternativa para mantener el código bien organizado y legible, aún así conservar la memoria?

¿Fue útil?

Solución

Intente hacer que la pila de llamadas sea más plana, así que en lugar de a () llamando a b () que llama a c () que llama a código> d () , tiene a () llamada b () , c () y d ( ) en sí.

Si solo se hace referencia a una función, márquela inline (suponiendo que su compilador lo admita).

Otros consejos

El uso de la pila tiene 3 componentes:

  • Direcciones de retorno de llamada de función
  • Parámetros de llamada de función
  • variables automáticas (locales)

La clave para minimizar el uso de la pila es minimizar el paso de parámetros y las variables automáticas. El consumo de espacio de la llamada a la función real en sí es bastante mínimo.

Parámetros

Una forma de abordar el problema de los parámetros es pasar una estructura (a través del puntero) en lugar de una gran cantidad de parámetros.


foo(int a, int b, int c, int d)
{
...
   bar(int a, int b);
}

haz esto en su lugar:


struct my_params {
   int a;
   int b;
   int c;
   int d;
};
foo(struct my_params* p)
{
   ...
   bar(p);
};

Esta estrategia es buena si pasas muchos parámetros. Si los parámetros son todos diferentes, es posible que no funcione bien para usted. Usted terminaría con una gran estructura que contiene muchos parámetros diferentes.

Variables automáticas (locales)

Este tiende a ser el mayor consumidor de espacio de pila.

  • Las matrices son el asesino. ¡No defina matrices en sus funciones locales!
  • Minimice el número de variables locales.
  • Use el tipo más pequeño necesario.
  • Si la re-entrada no es un problema, puede usar las variables estáticas del módulo.

Tenga en cuenta que si simplemente está moviendo todas sus variables locales del ámbito local al ámbito del módulo, NO ha guardado ningún espacio. Cambiaste espacio de pila por espacio de segmento de datos.

Algunos RTOS admiten el almacenamiento local de subprocesos, que asigna " global " almacenamiento por subproceso. Esto podría permitirle tener múltiples variables globales independientes por tarea, pero esto hará que su código no sea tan sencillo.

En el caso de que puedas ahorrar una gran cantidad de memoria principal, pero solo tienes un poco de pila, sugiero evaluar las asignaciones estáticas.

En C, todas las variables declaradas dentro de una función se " gestionan automáticamente " lo que significa que están asignados en la pila.

Calificando las declaraciones como " static " Los almacena en la memoria principal en lugar de en la pila. Básicamente, se comportan como variables globales, pero aún así te permiten evitar los malos hábitos que vienen con el uso excesivo de las variables globales. Puede ser un buen caso para declarar que las variables / buffers grandes y de larga duración son estáticas para reducir la presión en la pila.

Tenga en cuenta que esto no funciona bien o no funciona si su aplicación tiene varios subprocesos o si utiliza la recursión.

Encienda la optimización, específicamente en línea agresiva. El compilador debería poder incluir métodos en línea para minimizar las llamadas. Dependiendo del compilador y los modificadores de optimización que use, marcar algunos métodos como inline puede ayudar (o puede ignorarse).

Con GCC, intente agregar las " -finline-functions " (o -O3) y posiblemente la " -finline-limit = n " bandera.

Un truco que leí en algún lugar para evaluar los requisitos de apilamiento del código en una configuración incrustada es llenar el espacio de apilamiento al inicio con un patrón conocido (DEAD en hexadecimal es mi favorito) y dejar que el sistema funcione durante un mientras.

Después de una ejecución normal, lea el espacio de pila y vea cuánto espacio de pila no se ha reemplazado durante el curso de la operación. Diseñe para dejar al menos el 150% de eso para abordar todas las rutas de código de obsesión que no se hayan ejercitado.

¿Puedes reemplazar algunas de tus variables locales por variables globales? Las matrices en particular pueden devorar la pila.

Si la situación te permite compartir algunas globales entre algunas de esas funciones, existe la posibilidad de que pueda reducir su huella de memoria.

El costo de la compensación es una mayor complejidad y un mayor riesgo de efectos secundarios no deseados entre las funciones y una huella de memoria posiblemente más pequeña.

¿Qué tipo de variables tienes en tus funciones? ¿De qué tamaño y límites estamos hablando?

Dependiendo de su compilador y de lo agresivas que sean sus opciones de optimización, tendrá uso de la pila para cada función que realice. Por lo tanto, para comenzar, probablemente deba limitar la profundidad de sus llamadas a funciones. Algunos compiladores usan saltos en lugar de ramas para funciones simples que reducirán el uso de la pila. Obviamente, puede hacer lo mismo utilizando, por ejemplo, una macro de ensamblador para saltar a sus funciones en lugar de una llamada a función directa.

Como se mencionó en otras respuestas, la inclusión en línea es una opción disponible, aunque eso conlleva el costo de un mayor tamaño de código.

La otra área que come pila es los parámetros locales. Esta área tiene cierto control sobre. El uso de estadísticas (nivel de archivo) evitará la asignación de la pila al costo de su asignación de RAM estática. Globos de la misma manera.

En casos (verdaderamente) extremos, puede crear una convención para funciones que utiliza un número fijo de variables globales como almacenamiento temporal en lugar de locales en la pila. Lo difícil es asegurarse de que ninguna de las funciones que usan los mismos elementos globales sea llamada al mismo tiempo. (de ahí la convención)

Si necesita comenzar a conservar el espacio de pila, debería obtener un mejor compilador o más memoria.

Normalmente, su software crecerá (nuevas funciones, ...), así que si tiene que comenzar un proyecto pensando en cómo conservar el espacio de pila, está condenado desde el principio.

Sí, un RTOS realmente puede consumir RAM para el uso de la pila de tareas. Mi experiencia es que como nuevo usuario de un RTOS, hay una tendencia a usar más tareas de las necesarias.

Para un sistema embebido que usa un RTOS, la RAM puede ser un bien valioso. Para preservar la memoria RAM, para las funciones simples, aún puede ser efectivo implementar varias funciones dentro de una tarea, ejecutándose de manera rotatoria, con un diseño multitarea cooperativo. Por lo tanto, reducir el número total de tareas.

Creo que puedes estar imaginando un problema que no existe aquí. La mayoría de los compiladores no hacen nada cuando asignan " " Variables automáticas en la pila.

La pila se asigna antes de " main () " es ejecutado. Cuando llama a la función b () desde la función a (), la dirección del área de almacenamiento inmediatamente después de la última variable utilizada por a se pasa a b (). Esto se convierte en el inicio de la pila de b () si b () llama a la función c () y la pila de c comienza después de la última variable automática definida por b ().

Tenga en cuenta que la memoria de la pila ya está allí y asignada, que no se realiza la inicialización y que el único procesamiento involucrado es pasar el puntero de la pila.

La única vez que esto se convierta en un problema sería cuando las tres funciones usan grandes cantidades de almacenamiento que la pila tiene que acomodar en la memoria de las tres funciones. Intente mantener las funciones que asignan grandes cantidades de almacenamiento en la parte inferior de la pila de llamadas, es decir, no llame a otra función desde ellas.

Otro truco para los sistemas con memoria limitada es dividir las partes de una función que ocupan la memoria en funciones independientes independientes.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top