Pregunta

En el software integrado multiproceso (escrito en C o C ++), un hilo debe tener suficiente espacio de pila para permitirle completar sus operaciones sin desbordarse. El tamaño correcto de la pila es crítico en algunos entornos integrados en tiempo real, porque (al menos en algunos sistemas con los que he trabajado), el sistema operativo no detectará esto para usted.

Por lo general, el tamaño de la pila para un nuevo hilo (que no sea el hilo principal) se designa en el momento en que se crea el hilo (es decir, en un argumento a pthread_create () o similar). A menudo, estos tamaños de pila están codificados por los valores que se sabe que son buenos en el momento en que el código se escribió o probó originalmente.

Sin embargo, los cambios futuros en el código a menudo rompen los supuestos sobre los cuales se basaron los tamaños de pila codificados, y un día fatídico, su hilo ingresa a una de las ramas más profundas de su gráfico de llamadas y desborda la pila, derribando todo el sistema o corrompiendo silenciosamente la memoria.

Personalmente, he visto este problema en el caso de que el código ejecutado en el hilo declara instancias de estructura en la pila. Cuando la estructura se aumenta para contener datos adicionales, el tamaño de la pila se infla en consecuencia, lo que potencialmente permite que ocurran desbordamientos de pila. Me imagino que este podría ser un gran problema para las bases de código establecidas donde los efectos completos de agregar campos a una estructura no se pueden conocer de inmediato (demasiados hilos/funciones para encontrar todos los lugares donde se usa esa estructura).

Dado que la respuesta habitual a las preguntas de "tamaño de pila" es "no son portátiles", supongamos que el compilador, el sistema operativo y el procesador son cantidades conocidas para esta investigación. También supongamos que no se usa la recursión, por lo que no estamos lidiando con la posibilidad de un escenario de "recursión infinita".

¿Cuáles son algunas formas confiables de estimar el tamaño de la pila necesario para un hilo? Preferiría métodos fuera de línea (análisis estático) y automático, pero todas las ideas son bienvenidas.

¿Fue útil?

Solución

Evaluación de tiempo de ejecución

Un método en línea es pintar la pila completa con un cierto valor, como 0xaaaaa (o 0xaa, sea cual sea su ancho). Luego puede verificar cuán grande ha crecido al máximo en el pasado al verificar cuánto de la pintura queda intacta.

Mira esto este Enlace para una explicación con ilustración.

La ventaja es que es simple. Una desventaja es que no puede estar seguro de que su tamaño de pila no excederá eventualmente la cantidad de pila usada durante sus pruebas.

Evaluación estática

Hay algunos controles estáticos y creo que incluso existe una versión hackada de GCC que intenta hacer esto. Lo único que puedo decirle es que la verificación estática es muy difícil de hacer en el caso general.

También eche un vistazo a este pregunta.

Otros consejos

Puede usar una herramienta de análisis estático como Stackanalyzer, si su objetivo se ajusta a los requisitos.

Si desea gastar dinero significativo, puede usar una herramienta de análisis estático comercial como KLOCWork. Aunque KLOCWork está destinado principalmente a detectar defectos de software y vulnerabilidades de seguridad. Sin embargo, también tiene una herramienta llamada 'kwstackoverflow' que puede usarse para detectar el desbordamiento de la pila dentro de una tarea o hilo. Estoy usando para el proyecto integrado en el que trabajo, y he tenido resultados positivos. No creo que ninguna herramienta como esta sea perfecta, pero creo que estas herramientas comerciales son muy buenas. La mayoría de las herramientas que he encontrado luchan con los punteros de la función. También sé que muchos proveedores de compiladores como Green Hills ahora incorporan una funcionalidad similar directamente en sus compiladores. Esta es probablemente la mejor solución porque el compilador tiene un conocimiento íntimo de todos los detalles necesarios para tomar decisiones precisas sobre el tamaño de la pila.

Si tiene tiempo, estoy seguro de que puede usar un lenguaje de secuencias de comandos para hacer su propia herramienta de análisis de desbordamiento de pila. El script necesitaría identificar el punto de entrada de la tarea o hilo, generar un árbol de llamadas de función completa y luego calcular la cantidad de espacio de pila que usa cada función. Sospecho que probablemente hay herramientas gratuitas disponibles que pueden generar un árbol de llamadas de función completa, por lo que debería hacerlo más fácil. Si conoce los detalles de su plataforma que genera el espacio de la pila que usa cada función puede ser muy fácil. Por ejemplo, la primera instrucción de ensamblaje de una función PowerPC a menudo es la palabra de almacenamiento con instrucción de actualización que ajusta el puntero de la pila por la cantidad necesaria para la función. Puede tomar el tamaño en bytes directamente desde la primera instrucción, lo que hace que la determinación del espacio total de pila utilizado sea relativamente fácil.

Todos estos tipos de análisis le darán una aproximación del peor de los casos en el límite superior para el uso de la pila, que es exactamente lo que desea saber. Por supuesto, los expertos (como los que trabajo) pueden quejarse de que está asignando demasiado espacio de pila, pero son dinosaurios que no les importa la buena calidad del software :)

Otra posibilidad, aunque no calcula el uso de la pila, sería usar la unidad de gestión de memoria (MMU) de su procesador (si tiene uno) para detectar el desbordamiento de la pila. He hecho esto en VXWorks 5.4 usando un PowerPC. La idea es simple, solo coloque una página de memoria de escritura protegida en la parte superior de su pila. Si se desborda, se producirá una ejecución del procesador y se alertará rápidamente del problema de desbordamiento de pila. Por supuesto, no le dice cuánto necesita aumentar el tamaño de la pila, pero si su bien con la excepción de depuración/archivos principales puede descubrir al menos la secuencia de llamadas que desbordó la pila. Luego puede usar esta información para aumentar el tamaño de su pila de manera adecuada.

-djhaus

No gratis, pero Cobertura Hace análisis estático de la pila.

La verificación de la pila estática (fuera de línea) no es tan difícil como parece. Lo he implementado para nuestro IDE integrado (Rápido)-Actualmente funciona para ARM7 (NXP LPC2XXX), Cortex-M3 (STM32 y NXP LPC17XX), X86 y nuestro núcleo suave FPGA compatible con MIPS ISA interno.

Esencialmente, utilizamos un análisis simple del código ejecutable para determinar el uso de la pila de cada función. La asignación de pila más significativa se realiza al comienzo de cada función; Solo asegúrese de ver cómo se altera con diferentes niveles de optimización y, si corresponde, conjuntos de instrucciones de brazo/pulgar, etc. ¡Recuerde también que las tareas generalmente tienen sus propias pilas, y los ISR a menudo (pero no siempre) comparten un área de pila separada!

Una vez que tenga el uso de cada función, es bastante fácil construir un árbol de llamadas a partir del análisis y calcular el uso máximo para cada función. Nuestro IDE genera programadores (RTOSS delgadas efectivas) para usted, por lo que sabemos exactamente qué funciones se están designando como 'tareas' y cuáles son ISR, por lo que podemos decir el peor uso de cada área de pila.

Por supuesto, estas cifras casi siempre están sobre el actual máximo. Piense en una función como sprintf que puede usar un lote de espacio de pila, pero varía enormemente según la cadena de formato y los parámetros que proporcione. Para estas situaciones también puede usar análisis dinámico: llene la pila con un valor conocido en su inicio, luego ejecute en el depurador por un tiempo, haga una pausa y vea cuánto de cada pila todavía está llena con su valor (pruebas de estilo de marca de agua) .

Ninguno de los enfoques es perfecto, pero combinar ambos le dará una imagen bastante buena de cómo será el uso del mundo real.

Como se discutió en la respuesta a esta pregunta, una técnica común es inicializar la pila con un valor conocido y luego ejecutar el código durante un tiempo y ver dónde se detiene el patrón.

Este no es un método fuera de línea, pero en el proyecto en el que estoy trabajando tenemos un comando de depuración que lee la marca de agua en todas las pilas de tareas dentro de la aplicación. Esto genera una tabla del uso de la pila para cada tarea y la cantidad de espacio para la cabeza disponible. Verificar estos datos después de una ejecución de 24 horas con mucha interacción del usuario nos da cierta confianza en que las asignaciones de pila definidas son "seguras".

Esto funciona utilizando la técnica bien probada de llenar las pilas con un patrón conocido y suponiendo que la única forma en que esto se puede reescribir es mediante el uso normal de la pila, aunque si está siendo escrito por cualquier otro medio que un desbordamiento de la pila es el ¡Mínimo de tus preocupaciones!

Intentamos resolver este problema en un sistema integrado en mi trabajo. Se volvió loco, hay demasiado código (nuestros marcos de terceros y de terceros) para obtener una respuesta confiable. Afortunadamente, nuestro dispositivo estaba basado en Linux, por lo que volvimos al comportamiento estándar de dar a cada hilo 2 MB y dejar que el Administrador de memoria virtual optimice el uso.

Nuestro único problema con esta solución fue una de las herramientas de terceros que realizó una mlock en todo su espacio de memoria (idealmente para mejorar el rendimiento). Esto hizo que todos los 2 MB de pila para cada hilo de sus hilos (75-150 de ellos) se pelearan. Perdimos la mitad de nuestro espacio de memoria hasta que lo descubrimos y comentamos la línea ofensiva.

Nota al margen: el Administrador de memoria virtual de Linux (VMM) asigna RAM en fragmentos 4K. Cuando un nuevo hilo solicita 2 MB de espacio de direcciones para su pila, el VMM asigna páginas de memoria falsas a todas las mejores páginas, excepto en la mejor página. Cuando la pila se convierte en una página falsa, el núcleo detecta una falla de la página y cambia la página falsa con una real (que consume otros 4k de RAM real). De esta manera, la pila de un hilo puede crecer a cualquier tamaño que necesita (siempre que sea inferior a 2 MB) y el VMM asegurará que solo se use una cantidad mínima de memoria.

Además de algunas de las sugerencias ya realizadas, me gustaría señalar que a menudo en los sistemas integrados debe controlar el uso de la pila con fuerza porque tiene que mantener el tamaño de la pila en un tamaño razonable.

En cierto sentido, el uso del espacio de la pila es un poco como asignar la memoria, pero sin una forma (fácil) de determinar si su asignación tuvo éxito, por lo que no controlar el uso de la pila dará como resultado una lucha para siempre para descubrir por qué su sistema se está bloqueando nuevamente. Entonces, por ejemplo, si el sistema asigna la memoria para variables locales desde la pila, asigne esa memoria con malloc () o, si no puede usar malloc (), escriba su propio manejador de memoria (que es una tarea bastante simple).

No no:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

Sí Sí:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

Parece bastante obvio, pero a menudo no lo es, especialmente cuando no puedes usar Malloc ().

Por supuesto, todavía tendrá problemas, por lo que esto es algo para ayudar, pero no resuelve su problema. Sin embargo, lo ayudará a estimar el tamaño de la pila en el futuro, ya que una vez que haya encontrado un buen tamaño para sus pilas y, si luego, después de algunas modificaciones de código, nuevamente se queda sin espacio de pila, puede detectar una serie de errores o Otros problemas (pilas de llamadas demasiado profundas para uno).

No es 100% seguro, pero creo que esto también se puede hacer. Si tiene un puerto JTAG expuesto, puede conectarse a Trace32 y verificar el uso máximo de la pila. Aunque para esto, tendrá que dar un tamaño inicial de pila arbitraria bastante grande.

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