Sistemas informáticos en tiempo real Práctica 5 Hilos (Thread) Objetivos generales • • Aprender la programación multihebras o multihilos del S.O. Profundizar en el entorno de depuración de C++ y Tslite del S.O. Práctica En esta práctica se partirá del programa ejemplo suministrado por el S.O. por lo que no se necesitará escribirlo. El programa arranca cinco hilos, cuya tarea es imprimir tres veces un mensaje. La práctica tiene los siguientes objetivos: 1. Comprender el programa así como las distintas funciones del S.O. 2. Utilizando el entorno de depuración determinar las prioridades asignadas por defecto a las cinco hebras. 3. Cambiar las prioridades asignadas por defecto asignando por orden creciente a la primera hebra arrancada la prioridad más baja y a la última la más alta. 4. Utilizando el entorno de depuración comprobar las nuevas prioridades. El programa se muestra en las hojas siguientes así como la explicación la definición de las funciones. Indicaciones • • Para comprobar las prioridades asignadas a las hebras será necesario detener el depurador en una línea en la que ya esté creada la tarea, o en una línea en la que ya estén todas creadas. En este momento se elige el menú de información de target system y aquí se puede observar toda la información relativa a las hebras. El ETS Kernel tiene definido un rango de prioridades desde -15 (la más baja) a 15 (la más alta). La función que permite cambiar la prioridad de una hebra definida en el fichero <winbase.h> es: BOOL SetThreadPriority( HANDLE hThread, int nPriority ); Donde hThread nPriority es el HANDLE de la hebra a la que se quiere cambiar la prioridad es la nueva prioridad que se le asigna a la hebra // RTHELLO.C -- Multithreaded (real-time) hello world program // Copyright (c) 1998 Phar Lap Software, Inc. All rights reserved. #include <windows.h> #include <stdio.h> #include <process.h> #include <embkern.h> #define STACK_SIZE (16 * 1024) void OneHello(void *id); volatile DWORD alive; int num_threads = 5; void main() { int i; int id = 0; HANDLE hThread; char ThreadName[32]; // start threads for (i = 0 ; i < num_threads ; i++) { hThread = (HANDLE) _beginthread(OneHello, STACK_SIZE, (void *) id); if (hThread != (HANDLE)-1) { sprintf(ThreadName, "OneHello %c", 'A' + id); EtsSetThreadDebugName(hThread, ThreadName); ++id; InterlockedIncrement((LPLONG)&alive); } } while (alive) Sleep(50); printf("main thread terminates\n"); exit(0); } void OneHello(void *id) { int i; for(i = 0; i < 3; ++i) printf("Hello From Thread %c\n", 'A' + (int)id); InterlockedDecrement((LPLONG)&alive); _endthread(); } Aclaraciones: HANDLE HANDLE es un valor entero único, utilizado por windows para identificar un objeto. El ETS kernel presenta en algunas funciones el mismo interfaz de windows. Este caso es una de ellas. Volatile Esta cláusula añadida a una variable indica al compilador que esta variable puede ser modificada por otra entidad u otra función distinta a la que está procesando por lo que no debe optimizar. Por ejemplo en el siguiente trozo de código: int a, valor1; : : a=valor1; if (a !=0) : : El compilador podría optimizar y en la sentencia if chequear el valor de la variable valor1 y no el valor de A que es el que realmente cambia; EtsSetThreadDebugName: Asigna al objeto HANDLE un nombre simbólico que aparece cuando utilicemos el depurador. sprintf: Escribe datos formateados en un string. Sleep(): Suspende la ejecución por el tiempo especificado. _beginthread: Permite crear una hebra. Pertenece a la librería de run-time de Visual C++ _endthread: Finaliza una hebra. Pertenece a la librería de run-time de Visual C++ Sistemas informáticos en tiempo real Práctica 6 Suspender y reanudar (dormir y despertar) Objetivos generales • • • Aprender la programación multihebras o multihilos del S.O. Aprender a Suspender y reanudar hilos. Profundizar en el entorno de depuración de C++ y Tslite del S.O. Práctica Se desea hacer una aplicación multihilos, con tres hilos H1,H2, H3. La función de cada hilo será imprimir un mensaje indicando que hilo es, esperá un tiempo, reanudará el siguiente hilo y se suspenderá. Es decir: 1. H1 imprimirá un mensaje esperará un segundo, despertará a H2 y se dormirá. 2. H2 imprimirá un mensaje esperará dos segundos, despertará a H3 y se dormirá. 3. H3 imprimirá un mensaje esperará tres segundos, despertará a H1 y se dormirá. El programa realizará este ciclo cinco veces, y finalizará. Indicaciones • Para suspender y reanudar un hilo se utilizarán las siguientes funciones: Para suspender un hilo: DWORD SuspendThread(HANDLE hThread); Para reanudar un hilo: DWORD ResumeThread(HANDLE hThread); • • Para esperar el tiempo requerido utilícese la función Sleep empleada en la práctica anterior. Para controlar el número de veces que se realizará el ciclo debe utilizarse una variable compartida. Sistemas informáticos en tiempo real Práctica 7 Exclusión mutua Objetivos generales • • • Aprender la programación multihilos del S.O. Comprender la exclusión mutua. Profundizar en el entorno de depuración de C++ y Tslite del S.O. Práctica Con esta práctica se pretende observar la importancia de conseguir la exclusión mutua cuando varios hilos comparten variables. La práctica consistirá de las siguientes partes: 1. Estudiar el código que acompaña a la práctica, localizando los problemas de exclusión mutua. 2. Ejecutar el código para observar que estos problemas se producen fácilmente. 3. Añadir el código necesario para implementar la solución de Peterson y conseguir así exclusión mutua. #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winbase.h> #include <stdio.h> #include <process.h> #include <embkern.h> #define STACK_SIZE (16 * 1024) void hilo1(void *id); void hilo2(void *id); volatile DWORD variable_compartida ; BOOL salir = FALSE; HANDLE hThread1,hThread2; void main() { int id = 0; // start threads hThread1 = (HANDLE) _beginthread(hilo1, STACK_SIZE,(void *) id); if (hThread1 != (HANDLE)-1) EtsSetThreadDebugName(hThread1, "hilo1"); hThread2 = (HANDLE) _beginthread(hilo2, STACK_SIZE,(void *) id); if (hThread2 != (HANDLE)-1) EtsSetThreadDebugName(hThread2, "hilo2"); for(;salir!=TRUE;); Sleep(100); printf("main thread terminates\n"); exit(0); } void hilo1(void *id) { for(;salir!=TRUE;) { // sección crítica variable_compartida=1; if (variable_compartida!=1) salir=TRUE; } printf("hilo1 finalizado\n"); _endthread(); } void hilo2(void *id) { for(;salir!=TRUE;) { // sección crítica variable_compartida=2; if (variable_compartida!=2) salir=TRUE; } printf("hilo2 finalizado\n"); _endthread(); } Sistemas informáticos en tiempo real Práctica 8 Productor-consumidor Objetivos generales • • • • • Aprender la programación multihilos del S.O. Comprender la exclusión mutua. Profundizar en el entorno de depuración de C++ y Tslite del S.O. Regiones críticas. Implementar el modelo productor consumidor (reducido). Práctica En esta práctica se va a implementar el modelo del productor-consumidor en una versión reducida, es decir en lugar de tener distintos buffers, tendremos una variable donde el productor aleatoriamente pondrá un dato y lo recogerá el consumidor. Cada vez que el productor ponga un dato en esta variable activará un flag para indicar que el dato es válido. Cada vez que el consumidor lea el dato y este dispuesto a recibir uno nuevo pondrá el flag inactivo, momento en el que el productor podrá poner un nuevo dato. El dato que recoja el consumidor se mostrará por pantalla Indicaciones El dato que va a poner el productor puede ser cualquiera, por ejemplo un número entero que se incrementará. El momento en el que el productor ponga el dato o el consumidor lo recoja debe ser aleatorio, para lo cual se utilizará la función Sleep con un valor de tiempo aleatorio. Para comprobar que el funcionamiento del programa es correcto se deberá mostrar los datos que pone el productor y lee el consumidor por pantalla para comparar que son iguales. El kernel ETS mantiene compatibilidad con la API Win32. La API WIN32 define dos objetos distintos que se pueden utilizar como semáforos: mutex y critical section. Ambos objetos dan el mismo servicio. La diferencia es que el objeto critical section se utiliza para hilos del mismo proceso y el objeto mutex se puede utilizar además, para hilos de distintos procesos. El objeto critical section es menos costoso que el objeto mutex. En el caso del kernel ETS vamos a utilizar el objeto critical section ya que es un kernel multihilo pero con un solo proceso. Para el caso de objetos critical section se debe definir como un tipo de dato CRITICAL_SECTION. Se tiene las siguientes funciones, donde CritSec es el objeto CRTICAL_SECTION: • • • InitializeCriticalSection(&CritSec); Inicializa el objeto critical Section EnterCriticalSection(&CritSec); Se llama a esta función para entrar en la sección crítica seria lo equivalente a la función DOWN para semáforos binarios explicada en clase de teoría. LeaveCriticalSection(&CritSec); Se llama a esta función para salir de la sección crítica. Sería equivalente a la función UP para un semáforo binario.