• No se han encontrado resultados

Problemática del modelado temporal básico

6 Modelado del SW de aplicación 6.1 Modelado del subsistema SW

C object # cycles

6.4 Modelado del avance de tiempo de la ejecución SW

6.4.1.1 Problemática del modelado temporal básico

En la técnica de modelado temporal básico propuesta, el estado final del proceso al concluir un segmento es equivalente a una ejecución ciclo a ciclo. Sin embargo, la técnica no es capaz de modelar el efecto de eventos intermedios. Por tanto, esta técnica es bastante precisa siempre y cuando no se produzcan dos circunstancias: que se acceda a variables compartidas o que la ejecución del segmento sea interrumpida por un evento externo. Desgraciadamente, en código SW, el uso de variables globales compartidas por los hilos de un proceso y la aparición de interrupciones, son elementos muy comunes, que violan estas restricciones. Veamos un ejemplo.

Supongamos que tenemos un ejemplo con dos tareas y un procesador. La tarea 1 tiene prioridad baja, como corresponde a una tarea de cómputo, mientras que la tarea 2, encargada de responder a eventos externos, tiene una prioridad mayor. Al comienzo del ejemplo, la tarea 1 tiene que ejecutar un determinado código hasta que acaba. La tarea 2 comienza esperando a un evento externo o a la finalización de un tiempo de espera. El tiempo de espera expira a los 20us. Una vez expirado el tiempo de espera se ejecuta un código y se queda esperando un nuevo evento HW. Dicho evento llega en T=55 us, con lo que ejecuta un código para responder a la interrupción y termina.

Time (us) Task 1 Priority 1 Task 2 Priority 2 T= 0 T=20 T=30 T=40 T=50 T=10 ΔT= 0 ΔT= 60 ΔT= 60

Simul. time Estimation Events:

Timeout expiration T=20 us T=70 T=80 T=60 T=90 ΔT= 30 Hardware interrupt T=55 Delayed preemption Code execution Time annotation ΔT= 20 ΔT= 10 Delayed preemption Blocked

Figura 6-8: Ejemplo de simulación temporal.

Aplicando la técnica anterior, la tarea 1 comienza, mientras que la anterior esta bloqueada. Esto significa que ejecuta su código hasta la finalización del segmento. Dado que no tiene llamadas al sistema intermedias, esto significa que ejecuta completamente su código de una vez. Una vez ejecutado en código en tiempo 0 se bloquea durante el tiempo estimado (60us), bloqueando el procesador.

A los 20 us, el tiempo de espera de la tarea 2 finaliza, con lo que ha de ejecutar. Sin embargo, el procesador está ocupado hasta T=60us. Dado que la tarea 2 tiene mayor prioridad que T1, esto implica un modelado erróneo del sistema. Para complicar aun más las cosas, supongamos que la tarea 1 lee una variable que se modifica en la tarea 2. Si la variable se lee después de T=20us, el valor con el que ha computado la tarea 1 es erróneo.

Lo mismo ocurre en T=55. La tarea 2 debe ejecutar, pero el procesador sigue ocupado. Es esta caso la tarea 1 ha vuelto a leer la variable antes de ser modificada por la tarea 2. Sin embargo, si consideramos la expulsión que debería haber producido la primera ejecución de la tarea 2, la modificación de la variable debería haber sido anterior a su uso en la tarea 1. La situación es incluso más preocupante, porque la espera de la tarea 1 ha finalizado antes de que la tarea 2 modifique la variable. Esto implica no solo un error, sino que además ni con un análisis de modificación de variables hay forma de saber que se produce dicho error. Además, no hay forma de simular que la tarea T1 recibe dos valores distintos al leer la variable

compartida. Dado que se ejecuta toda la tarea en tiempo 0, no es posible modelar dicha modificación de ninguna forma.

Para resolver el problema, hemos de considerar que elementos pueden causar estos problemas. Como veremos en el grafo de ejecución de las tareas en el sistema operativo, tan solo una interrupción externa puede forzar una expulsión, requiriendo una modificación tanto del orden de ejecución como del valor de variables compartidas. En nuestro ejemplo, tanto el tiempo de espera, que se produce como consecuencia de un evento del temporizador HW del sistema, como el evento HW se deben a interrupciones.

En sistemas multiprocesador, el cambio de valor de las variables compartidas se puede producir en cualquier momento. En resumen, podemos decir que hay que gestionar tanto los cambios en variables compartidas como el efecto de las interrupciones en la gestión del orden de ejecución de las tareas.

6.4.2 Modelado temporal basado en rodajas temporales

La primera solución que vamos a considerar es definir un tiempo máximo de ejecución para cada segmento. Esto significa que cuando el tiempo acumulado de ejecución del segmento supera un valor predefinido, el segmento es finalizado automáticamente. De esta forma el proceso se bloquea para esperar el tiempo máximo, y una vez pasa ese tiempo continúa la ejecución como si se tratase de un segmento nuevo.

Esta técnica permite limitar el error producido por la anotación completa de segmentos. Nunca el error es mayor que el tiempo máximo permitido. Esto permite una gran flexibilidad, ya que es posible manejar fácilmente el binomio precisión – sobrecarga de simulación. Si definimos un tiempo límite grande, el efecto en el tiempo de simulación será mínimo, pero resolverá pocos problemas. Si definimos un tiempo máximo pequeño, la precisión será alta, pero aumentaremos el tiempo de simulación. Hay que tener en cuenta que el mayor coste de tiempo de simulación se produce cuando se bloquea un proceso, ya que entra en ejecución el kernel de simulación de SystemC analizando todas las tareas del sistema. Por tanto, si limitamos el tiempo máximo, dividiremos todos los segmentos del código en múltiples fragmentos, generando muchos bloqueos temporales, y por tanto mucha sobrecarga en la simulación.

Para incrementar la precisión sin aumentar la sobrecarga se ha propuesto una optimización de esta técnica. Recordemos que las limitaciones de la técnica de modelado dependen fuertemente de las interrupciones. Analizando los generadores de interrupciones HW podemos ver que uno de los elementos que está siempre presente en el sistema y que más interrupciones genera es el temporizador del sistema. Este temporizador tiene la cualidad de generar interrupciones periódicas. Cada cierto tiempo se genera una interrupción.

Como el periodo del temporizador es conocido, podemos decir que sus interrupciones son predecibles. Si ajustamos los tiempos máximos para que coincidan con las ejecuciones del temporizador, podemos minimizar los errores, eliminando todos los que dependen de elementos temporales: esperas, alarmas, temporizadores… Para ello, cuando un segmento empieza se comprueba cuanto tiempo resta para la siguiente interrupción del temporizador, y si este es menor que el tiempo máximo, se pone como límite el evento del temporizador. Teniendo en cuenta que en un sistema operativo convencional, tipo Linux, el temporizador del sistema tiene un periodo de 10ms, podemos decir que el aumento de segmentos que produce es limitado. Si además consideramos que la activación del temporizador va a provocar la ejecución del kernel de simulación de SystemC por si mismo, la finalización de un segmento en ese momento no implica una ejecución extra del kernel de simulación, ya que se ejecutaría de todas formas. Por tanto la sobrecarga que implica sobre la simulación es aceptable.

Figura 6-9: Ejemplo considerando Tic-Temporizador.

Si aplicamos esta técnica al ejemplo anterior con un tiempo máximo de 20, vemos que la ejecución de la tarea 2 debida a la finalización del tiempo de espera es correcta. De esta forma tanto el orden de ejecución de las tareas, como el valor de la variable compartida implicada son correctos. Sin embargo, la ejecución de la respuesta a la interrupción HW sigue siendo errónea. Tanto la planificación de la ejecución de las tareas, como el valor de la variable son erróneos.

ΔT= 20 Time (us) Task 1 Priority 1 Task 2 Priority 2 T= 0 T=20 T=30 T=40 T=50 T=10 ΔT= 0 ΔT= 20 ΔT= 20 Simulation time Estimation Expected preemption Events Predictable: Timeout expiration T=20 Prediction T=70 T=80 T=60 T=90 ΔT= 10 ΔT= 20 Unpredictable: Hardware interrupt T=65 Delayed preemption Code execution Time annotation Unexpected preemption ΔT= 20 ΔT= 10 Delayed preemption ΔT= 20

Por tanto podemos decir que esta técnica, aunque es capaz de minimizar los errores de modelado, no garantiza un correcto funcionamiento de la simulación. Además implica una cierta sobrecarga en la simulación que ha de ser tenida en cuenta. Esto es especialmente importante cuando se aplica con la técnica de estimación mixta explicada anteriormente. En este caso, dado que la técnica de estimación es extremadamente rápida, el aumento del tiempo de simulación puede ser considerable en cuanto se empiece a reducir el tiempo máximo de ejecución de los segmentos.

6.4.3 Transformación de variables en canales

Tras considerar una posible solución para minimizar todos los problemas del modelado temporal básico, vamos a considerar técnicas que resuelven los problemas por separado.

La segunda solución que se ha considerado en la tesis es la transformación de las variables compartidas en canales. Para ello la técnica consiste en detectar las variables globales del código y transformarlas en una clase con los operadores de acceso, asignación y conversión sobrecargados. Con ello se consigue que cada vez que se accede a una variable global se ejecuten las funciones de dichos operadores.

Una vez podemos detectar esos puntos de comunicación con el exterior, pasamos a aplicar la definición de segmento propuesta al principio de la sección de manera estricta. Dado que un segmento es el código ejecutado consecutivamente entre dos interacciones con el exterior, los accesos a variable global se han de convertir en delimitadores de segmento. Cuando se acceda a una variable global se realizará la espera correspondiente al tiempo de segmento ejecutado anteriormente y se comenzará un nuevo segmento.

Esta técnica no resuelve el problema de re-ordenación en el orden de ejecución de las tareas, pero evita problemas con las variables compartidas.

La técnica es recomendable para su aplicación con la técnica de sobrecarga. De hecho puede ser fácilmente integrada con el uso de tipos sobrecargados para realizar la estimación de tiempo. La detección de si una variable es local o global puede detectarse fácilmente. Las variables globales se generan en estático, antes del comienzo de la simulación, mientras que las locales se crean al entrar en las funciones, durante la simulación. Por tanto, mirando si la variable se construye antes o después del comienzo de la simulación podemos detectar si es global o no, y si se debe forzar la terminación de los segmentos o no.

Con la técnica mixta también es aplicable, ya que el análisis estático puede detectar si las variables son globales o locales y hacer las transformaciones necesarias. Sin embargo, considerando que algunos programadores SW tienen tendencia al uso de variables globales, y que la transformación de una variable en un objeto sobrecargado implica una carga para la simulación, el aumento del tiempo de simulación puede hacer no recomendable su uso en determinados casos. Un análisis que compruebe si cada variable global se comparte realmente o no, puede limitar la sobrecarga, pero implica un análisis estático más complejo.