2008-09-25 5 views
12

Estoy programando en C para el microcontrolador embebido RAM limitado con RTOS.¿Cómo conservar el espacio de la pila con un buen diseño?

Rompo con regularidad mi código a funciones cortas, pero cada llamada de función requiere más memoria de la pila. Cada tarea necesita su pila, y este es uno de los consumidores de memoria más importantes en el proyecto.

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

Respuesta

11

tratar de hacer el plano pila de llamadas, por lo que en lugar de llamar a a()b() la que se pide que se pide c()d(), tienen a() llamada b(), c() y d() sí.

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

+0

+1 inline .... preguntándose si los compiladores modernos lo hacen automáticamente? – kenny

5

Active la optimización, específicamente la creación de líneas agresivas. El compilador debería poder alinear métodos para minimizar llamadas. Dependiendo del compilador y los conmutadores de optimización que utilice, marcar algunos métodos como inline puede ayudar (o puede ignorarse).

Con GCC, intente agregar el indicador "-finline-functions" (u -O3) y posiblemente el indicador "-finline-limit = n".

6

En caso de que pueda ahorrar una gran cantidad de memoria principal pero tenga solo una pequeña porción de pila, le sugiero que evalúe las asignaciones estáticas.

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

Al calificar las declaraciones como "estáticas" las almacena en la memoria principal en lugar de en la pila. Básicamente se comportan como variables globales, pero aún le permiten evitar los malos hábitos que vienen con el uso excesivo de productos globales. Puede justificar la declaración de búferes/variables grandes y de larga vida como estáticos para reducir la presión en la pila.

Tenga en cuenta que esto no funciona bien/en absoluto si su aplicación es multiproceso o si utiliza recursividad.

+0

No suele haber una diferencia cualitativa entre la RAM para la pila y la RAM para la asignación estática. Debe tener el control de las asignaciones a través de algo como un archivo de control del enlazador. A menos que tenga un procesador complejo con múltiples bancos de RAM, como RAM en el chip y RAM externa separada. –

0

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

Si la situación le permite compartir algunos valores globales entre algunos entre las funciones, existe la posibilidad de que pueda reducir la huella de memoria.

El costo de compensación es una mayor complejidad y un mayor riesgo de efectos secundarios no deseados entre las funciones frente a una posible pérdida de memoria.

¿Qué tipo de variables tiene en sus funciones? ¿De qué tamaños y límites estamos hablando?

0

Dependiendo de su compilador, y cuán agresivas son sus opciones de optimización, tendrá uso de pila para cada llamada de función que realice. Así que, para empezar, 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 de función directa.

Como se menciona en otras respuestas, la alineación es una opción disponible, aunque eso tiene el costo de un mayor tamaño de código.

La otra área que come pila son los parámetros locales. Esta área sobre la que tienes cierto control. El uso de estática (nivel de archivo) evitará la asignación de la pila a costa de la asignación estática de ram. Globales también.

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 utilizan los mismos valores globales se llame al mismo tiempo. (De ahí la convención)

10

Hay 3 componentes para su uso de pila:

  • direcciones de retorno Función de llamada
  • parámetros de llamada
  • función
  • variables automáticas (locales)

La clave 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 manera de abordar la cuestión parámetro es pasar una estructura (a través de puntero) en lugar de un gran número de parámetros.


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

hacer 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 se pasa por una gran cantidad de parámetros. Si los parámetros son todos diferentes, entonces podría no funcionarle bien. Usted terminaría con una estructura grande que se pasa y que contiene muchos parámetros diferentes.

variables automáticas (locales)

Esta tienden a ser el mayor consumidor de espacio de pila.

  • Las matrices son el asesino. ¡No defina arrays en sus funciones locales!
  • Minimice el número de variables locales.
  • Utilice el tipo más pequeño necesario.
  • Si la reentrada 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. Has cambiado el espacio de la pila por el espacio del segmento de datos.

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

1

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

Después de una ejecución normal, lea el espacio de la pila y vea cuánto del espacio de la pila no se ha reemplazado durante el curso de la operación. Diseñe para dejar al menos el 150% de eso para abordar todos los caminos obscuros del código que pudieron no haberse ejercitado.

+0

No, no lo es. Mi punto es que es posible que no obtenga una cobertura de código del 100% y puede que falten algunas rutas de código. Solo una regla general que sigo. –

0

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

Su software generalmente crecerá (nuevas características, ...), por lo que si tiene que comenzar un proyecto pensando en cómo conservar el espacio de la pila, está condenado desde el principio.

0

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 integrado que utiliza un RTOS, la RAM puede ser un bien preciado. Para preservar la memoria RAM, para funciones simples, puede ser efectivo implementar varias funciones dentro de una misma tarea, ejecutarse en forma conjunta, con un diseño cooperativo multitarea. Por lo tanto, reduzca el número total de tareas.

0

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

La pila se asigna antes de que se ejecute "main()". Cuando llama a la función b() desde la función a() la dirección del área de almacenamiento inmediatamente después de pasar la última variable utilizada por a a b(). Esto se convierte en el inicio de la pila de b() si b() luego llama a la función c(), entonces 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 tiene lugar ninguna inicialización y que el único procesamiento involucrado es pasar un puntero de la pila.

La única vez que esto se convierte en un problema sería cuando las tres funciones usan grandes cantidades de almacenamiento, la pila debe acomodarse a 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 constantes de memoria es dividir las partes de memoria que acaparan una función en funciones separadas independientes.

Cuestiones relacionadas