2010-11-18 6 views
6

Soy un programador incrustado que intenta simular un planificador preventivo en tiempo real en un entorno Win32 utilizando Visual Studio 2010 y MingW (como dos entornos de compilación separados). Estoy muy verde en el entorno de programación de Win32 y he golpeado una pared de ladrillo con lo que estoy tratando de hacer. No estoy tratando de lograr un comportamiento en tiempo real, solo para que las tareas simuladas se ejecuten en el mismo orden y secuencia que lo harían en el hardware objetivo real.Forzar la programación de hilos de Win32 a una secuencia definida según la prioridad

El programador en tiempo real que se simula tiene un objetivo simple: siempre ejecute la tarea de mayor prioridad (subproceso) que se puede ejecutar. Tan pronto una tarea puede ejecutarse, debe adelantarse a la tarea que se está ejecutando actualmente si tiene una prioridad más alta que la tarea que se está ejecutando. Una tarea puede volverse capaz de ejecutarse debido a un evento externo que estaba esperando, o un tiempo de espera/tiempo de bloqueo/tiempo de espera que expira, con una interrupción de marcación que genera la base de tiempo.

Además de este comportamiento preventivo, una tarea puede ceder o ser voluntaria para renunciar a su segmento de tiempo porque está ejecutando una función de tipo espera o espera.

Estoy simulando esto creando un hilo Win32 de baja prioridad para cada tarea creada por el programador en tiempo real simulado (el hilo efectivamente cambia el contexto que el planificador haría en un objetivo incrustado real), una prioridad media Win32 thread como un controlador de pseudointerrupción (maneja interrupciones simuladas de marcación y solicitudes de rendimiento que se le señalan utilizando un objeto de evento Win32), y una secuencia de Win32 de mayor prioridad para simular el periférico que genera las interrupciones de marcación.

Cuando el manejador de pseudointerrupción establece que debe producirse un cambio de tarea, suspende el subproceso que se está ejecutando utilizando SuspendThread() y reanuda el subproceso que ejecuta la tarea recién seleccionada utilizando ResumeThread(). De las muchas tareas y sus subprocesos Win32 asociados que se pueden crear, solo un subproceso que administre la tarea estará fuera del estado suspendido en cualquier momento.

Es importante que un hilo suspendido suspenda inmediatamente que se invoca SuspendThread(), y que el hilo de manejo de pseudointerrumpida se ejecute tan pronto como se señalice el evento que indica que una interrupción está pendiente, pero este no es el comportamiento I estoy viendo. Como un problema de ejemplo que ya tengo para: Cuando una tarea/hilo produce el evento de rendimiento se enclava en una variable y el hilo de manejo de interrupción se señala como hay una pseudointerrupción (el rendimiento) que necesita tratamiento. Ahora, en un sistema de tiempo real, como estoy acostumbrado a programar, esperaría que el hilo de manejo de interrupciones se ejecute inmediatamente que se señaliza porque tiene una prioridad más alta que el hilo que lo señala. Lo que veo en el entorno de Win32 es que el hilo que señala el hilo de mayor prioridad continúa durante algún tiempo antes de suspenderse, ya sea porque toma algún tiempo antes de que el hilo de mayor prioridad señalizado comience a ejecutarse o porque lleva algo de tiempo suspenderlo tarea para realmente dejar de funcionar, no estoy seguro de cuál. En cualquier caso, esto puede ser correcto haciendo el bloque de hilos Win32 con señal en un semáforo después de señalar el hilo de manejo de interrupción Win32, y hacer que la interrupción que maneja el hilo Win32 desbloquee el hilo cuando haya terminado su función (handshake). Usar de manera efectiva la sincronización de hilos para forzar el patrón de programación a lo que necesito. Estoy usando SignalObjectAndWait() para este propósito.

Usando esta técnica, la simulación funciona perfectamente cuando el programador en tiempo real que se está simulando está funcionando en modo cooperativo, pero no (como se necesita) en modo preventivo. El problema con la conmutación de tareas preventivas es que supongo que la tarea continúa ejecutándose durante un tiempo después de que se le haya indicado que suspenda antes de que realmente se ejecute, por lo que no se puede garantizar que el sistema se mantenga en estado constante. el hilo que ejecuta la tarea se suspende. Sin embargo, en el caso de preferencia, dado que la tarea no sabe cuándo va a suceder, no se puede utilizar la misma técnica de usar un semáforo para evitar que el ataque de Win32 continúe hasta que se reanude.

¿Alguien ha llegado hasta aquí en este post? ¡Disculpe por su longitud!

Mis preguntas son entonces:

  • ¿Cómo puedo forzar Win32 programación (XP) para iniciar y detener tareas de inmediato que las funciones de suspensión y reanudación de rosca se llaman - o - ¿Cómo puedo forzar una mayor prioridad Win32 hilo para comenzar a ejecutar inmediatamente que es capaz de hacerlo (el objeto que está bloqueado se señaliza). Forzar efectivamente a Win32 a reprogramar sus procesos en ejecución.

  • Hay alguna forma de detener asincrónicamente una tarea para esperar un evento cuando no está en la ruta de ejecución secuencial tarea/hilos.

  • El simulador funciona bien en un entorno Linux donde las señales POSIX se utilizan para interrumpir efectivamente los hilos. ¿Hay un equivalente en Win32?

Gracias a cualquiera que haya tomado el tiempo de leer este post largo, y sobre todo gracias por adelantado a las personas que puedan sostener la mano de mi 'ingenieros' en tiempo real a través de este laberinto de Win32.

Respuesta

5

Si necesita hacer su propia programación, entonces puede considerar el uso de fibers en lugar de hilos. Las fibras son como hilos, en el sentido de que son bloques separados de código ejecutable, sin embargo, las fibras se pueden programar en código de usuario, mientras que los hilos solo están programados por el sistema operativo. Un solo hilo puede alojar y gestionar la programación de múltiples fibras, y las fibras incluso pueden programarse entre sí.

+0

Pero las fibras no se ejecutarán simultáneamente en varios núcleos. –

+0

Gracias por la sugerencia.Buscaré algún material de referencia sobre las fibras mañana. – Richard

+0

@ Zan: múltiples fibras se pueden ejecutar en un solo subproceso, y puede ejecutar varios subprocesos, uno para cada núcleo. –

1

En primer lugar, ¿qué valores de prioridad está utilizando para sus hilos?

Si establece el subproceso de alta prioridad en THREAD_PRIORITY_TIME_CRITICAL, debería ejecutarse casi de inmediato; solo aquellos subprocesos asociados con un proceso en tiempo real tendrán una prioridad mayor.

En segundo lugar, ¿cómo sabe que la suspensión y el currículum no están sucediendo inmediatamente? ¿Estás seguro de que este es el problema?

No puede obligar a un hilo a esperar algo desde afuera sin suspender el hilo para inyectar el código de espera; si SuspendThread no funciona para usted, entonces esto no va a ayudar.

Lo más cercano a una señal es probablemente QueueUserAPC, que programará una devolución de llamada para ejecutarse la próxima vez que el hilo entre en un "estado de espera alertable", p. llamando al SleepEx o WaitForSingleObjectEx o similar.

1

@Anthony W - gracias por el consejo. Estaba ejecutando los hilos de Win32 que simulaban las tareas en tiempo real en THREAD_PRIORITY_ABOVE_NORMAL, y los hilos que ejecutaban el manejador de pseudointerrupción y el generador de interrupción de marca en THREAD_PRIORITY_HIGHEST. Los hilos que se suspendieron Iba cambiando a THREAD_PRIORITY_IDLE en caso de que hiciera alguna diferencia. Acabo de probar tu sugerencia de usar THREAD_PRIORITY_TIME_CRITICAL pero desafortunadamente no hizo ninguna diferencia.

Con respecto a su pregunta, estoy seguro de que la suspensión y la reanudación no suceden de inmediato es el problema, pues no, no lo estoy. Es mi mejor suposición en un entorno con el que no estoy familiarizado. Mi pensamiento sobre el fracaso de suspender y reanudar el trabajo de inmediato se deriva de mi observación cuando se produce una tarea. Si realizo la llamada para ceder (señal [usando un evento Win32] una hebra Win32 de mayor prioridad para cambiar a la siguiente tarea en tiempo real) Puedo colocar un punto de ruptura después del rendimiento y eso se golpea antes de un punto de quiebre en la prioridad más alta hilo. No está claro si un retraso en la señalización del evento y la ejecución de la tarea de mayor prioridad, o una demora en suspender el hilo y el hilo que realmente deja de ejecutarse estaba causando esto, pero el comportamiento definitivamente se observó. Esto se solucionó usando un protocolo de enlace de semáforo, pero eso no se puede hacer por preventa causada por interrupciones de marca.

Sé que la simulación no se está ejecutando como esperaba porque falla un conjunto de pruebas que verifican la secuencia de programación de las tareas en tiempo real. Siempre es posible que el planificador tenga un problema o que la prueba tenga un problema, pero la prueba se ejecutará durante semanas sin fallar en un objetivo real en tiempo real, por lo que me inclino a pensar que la prueba y el planificador son correctos. Una gran diferencia es en el objetivo en tiempo real, la frecuencia de tic-tac es de 1 ms, mientras que en el objetivo simulado de Win32 es 15 ms con bastante variación incluso entonces.

@Remy - He leído bastante sobre las fibras en la actualidad, y mi conclusión es que para simular el programador en modo cooperativo serían perfectas. Sin embargo, hasta donde puedo ver, solo pueden ser programados por las propias fibras llamando a la función SwitchToFiber(). ¿Se puede hacer que un hilo se bloquee con un temporizador o que duerma para que se ejecute periódicamente, lo que previene eficazmente la fibra que se estaba ejecutando en ese momento? Según lo que he leído, la respuesta es no porque bloquear una fibra bloqueará todas las fibras que se ejecutan en la secuencia. Si se pudiera hacer funcionar, ¿podría la fibra que se ejecuta periódicamente llamar a la función SwitchToFiber() para seleccionar la siguiente fibra que se ejecutará antes de volver a dormir durante un período fijo? De nuevo, creo que la respuesta es negativa, porque una vez que cambia a otra fibra, ya no se ejecutará y, por lo tanto, no llamará a la función Sleep() hasta la próxima vez que la fibra de ejecución vuelva a activarse. Por favor, corrija mi lógica aquí si tengo una idea equivocada de cómo funcionan las fibras.

Creo que podría funcionar si la funcionalidad periódica podría permanecer en su propio hilo, separado del hilo que ejecutó las fibras, pero (de nuevo por lo que he leído) No creo que un hilo pueda influir en la ejecución de fibras que se ejecutan en un hilo diferente. De nuevo, le agradecería si pudiera corregir mis conclusiones aquí si están equivocadas.

1

[EDIT] - más simple que el hack de abajo - parece que solo asegurar que todos los hilos se ejecuten en el mismo núcleo de la CPU también soluciona el problema: o) Después de todo eso. El único problema es que la CPU funciona a casi el 100% y no estoy seguro de si el calor la está dañando. [/ EDITAR]

Ahaa! Creo que tengo un trabajo para esto, pero es feo. Sin embargo, la fealdad se mantiene en la capa del puerto.

Lo que hago ahora es almacenar el ID del hilo cada vez que se crea un hilo para ejecutar una tarea (se crea un hilo Win32 para cada tarea en tiempo real que se crea). Luego agregué la función a continuación, que se llama usar macros de rastreo. Las macros de rastreo se pueden definir para que hagan lo que quieran, y han demostrado ser muy útiles en este caso. Los comentarios en el siguiente código explican. La simulación no es perfecta, y todo lo que hace es corregir la programación del hilo cuando ya se ha desviado de la programación en tiempo real, mientras que yo preferiría que no fuera mal en primer lugar, pero el posicionamiento de las macros de rastreo hace que el código contenga esta solución pasa todas las pruebas:

void vPortCheckCorrectThreadIsRunning(void) 
{ 
xThreadState *pxThreadState; 

    /* When switching threads, Windows does not always seem to run the selected 
    thread immediately. This function can be called to check if the thread 
    that is currently running is the thread that is responsible for executing 
    the task selected by the real time scheduler. The demo project for the Win32 
    port calls this function from the trace macros which are seeded throughout 
    the real time kernel code at points where something significant occurs. 
    Adding this functionality allows all the standard tests to pass, but users 
    should still be aware that extra calls to this function could be required 
    if their application requires absolute fixes and predictable sequencing (as 
    the port tests do). This is still a simulation - not the real thing! */ 
    if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) 
    { 
     /* Obtain the real time task to Win32 mapping state information. */ 
     pxThreadState = (xThreadState *) *((unsigned long *) pxCurrentTCB); 

     if(GetCurrentThreadId() != pxThreadState->ulThreadId) 
     { 
      SwitchToThread(); 
     } 
    } 
} 
Cuestiones relacionadas