viernes, 7 de febrero de 2014

Ejercicio #4 - Planificación y variables compartidas


Crea dos procesos A y B que modifican una variable compartida. El proceso A itera 40 veces, incrementando la variable compartida de 100 en 100. El proceso B repite la misma acción pero incrementa el valor de de la variable compartida de 1 en 1.

1. Asigna un esquema de planificación fifo y round robin. Analiza el comportamiento.
2. Asigna prioridades (por ejemplo,
A con prioridad 24 y B con prioridad 26) y un esquema
de planificación round robin. Analiza el comportamiento.

Nota 1: se puede añadir una espera activa de 1 sg en la iteración de cada proceso para
observar mejor el comportamiento del planificador.
Nota 2: para observar el comportamiento del planificador, es necesario que el programa se ejecute en una máquina con una única CPU. 

Planificación de procesos

El Planificador

El elemento fundamental de un SOTR es el planificador. Este, básicamente, controla el estado de las tareas y decide qué tarea pasará a ejecutarse. 

Tenemos que tener en cuenta que las tareas en el sistema no pueden bloquear otras tareas, además una tarea bloqueada nunca tomará el procesador


Para conseguir la portabilidad de las aplicaciones puede ser necesario especificar la política de planificación.


  • SCHED_FIFO: Política de planificación expulsiva basada en prioridades fijas y un comportamiento FIFO para las tareas con la misma prioridad.
  • SCHED_RR: Igual que en el caso anterior pero se emplea la política Round-Robin para procesos de igual prioridad.
  • SCHED_OTHER: Definida por la implementación.

#include <sched.h>

int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);



int sched_getscheduler(pid_t pid);

int sched_setparam(pid_t pid, const struct sched_param *param); int sched_getparam(pid_t pid, struct sched_param *param);

int sched_get_priority_max(int policy); 
int sched_get_priority_min(int policy);

int sched_yield(void);



Ejercicio #3 - Ejecutivo cíclico


Supongamos el siguiente conjunto de tareas. Implementar un ejecutivo cíclico, donde los tiempos son expresados en segundos.
El programa principal puede tener el siguiente formato, donde cada tarea mostrará un mensaje mostrando la hora de inicio de ejecución. Además, dormirá por el tiempo que sea necesario hasta completar su tiempo de cómputo especificado, en cuyo caso mostrará un mensaje indicando la finalización de su ejecución. 


En este caso, el periodo principal, o hiperperiodo, será el mcm de todos los periodos, es decir 20. El periodo mínimo es 10. Por tanto el número de ciclos de este ejecutivo cíclico será de 2 (periodo principal / periodo mínimo).

jueves, 6 de febrero de 2014

Ejercicio #2 - Señales en POSIX. Tareas periódicas

Diseñe e implemente un proceso que genere una señal SIGUSR1 de forma periódica cada 1 segundo (utilizando un temporizador). Así mismo, instale un manejador de la señal SIGUSR1 tal que cada vez que reciba dicha señal incremente el valor de un contador de tiempo (inicialmente a cero) que cuente los segundos transcurridos desde el inicio del programa.

Además, cuando reciba una señal SIGUSR2, debe terminar el proceso mostrando un mensaje de finalización con el valor final del contador del tiempo.

El programa debe mostrar al principio de la ejecución su identificador de proceso (pid), así como el número de la señal en la que ha instalado un manejador de señales y el número de la señal por la que espera por su terminación.

Nota 1: puede enviar señales al proceso desde un Terminal mediante el comando: kill -USR2 pid-del-proceso.
Nota 2: puede conocer los identificadores de los procesos desde un Terminal mediante el comando: ps -e


Autor de los ejercicios (Profesorado UMA)

Temporizadores y tareas periódicas

Un temporizador se trata de un registro contador asociado a un reloj. Una vez creado se inicializa con el tiempo a contar y cada pulso de reloj decrementa el contador del temporizador. Al llegar a 0 se envía una señal al proceso que lo creó e inicializa de nuevo su valor.

Creación de un temporizador

#includes <sys/time.h>

int timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)

int timer_settime (timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue);

Devuelve 0 en caso de éxito y -1 en caso de error. Es importante definir un identificador para el temporizador y el tipo de notificación que se produce cuando expira el temporizador. NULL eleva SIGALARM.

struct sigevent {  
   int sigev_notify /* notification type */ 
   int sigev_signo; /* signal number */  
   union sigval sigev_value; /* signal value */ 
};

sigev_notify
   SIGEV_SIGNAL : Se asocia una señal.
   SIGEV_NONE : No se notifica nada.

El valor de espera se especifica mediante una estructura de tipo itimerspec.
  • it_value: valor inicial del temporizador (0 lo desactiva).
  • it_interval: nuevo valor tras expiración (0 un solo evento).
struct itimerspec {    
   struct timespec it_interval; /* periodo */    
   struct timespec it_value; /* valor inicial*/ 
};  

struct timespec {   
   time_t tv_sec; /* seconds */    
   long tv_nsec; /* and nanoseconds */ 
}; 


Tarea periódica

Los temporizadores por sí solos no tienen mucha función, por ello añadimos las tareas periódicas.


void periodic (void *arg) {  
   int signum; /* señal recibida */  
   sigset_t set; /* señales a las que se espera */  
   struct sigevent sig; /* información de señal */  
   timer_t timer;  
   struct itimerspec required, old;  
   struct timespec first, period;  

   sig.sigev_notify = SIGEV_SIGNAL;  
   sig.sigev_signo = SIGRTMIN;  
   
   if (clock_gettime (CLOCK_MONOTONIC, &first) != 0) error();   

   first.tv_sec = first.tv_sec + 1;  
   period.tv_sec = 0;  
   period.tv_nsec = 10.0E6; /* 10 ms */  
   required.it_value = first;  
   required.it_interval = period;


   if (timer_create(CLOCK_MONOTONIC,&sig,&timer) != 0) error();  
   if (sigemptyset(&set) != 0) error ();  
   if (sigaddset(&set, SIGRTMIN) != 0) error();  
   if (timer_settime(timer,0, &required, &old) != 0)    error ();  
   
   while (1) {  
      if (sigwait(&set, &signum) != 0) error();   // Comportamiento de la tarea 
   }
}


Ejecutivo cíclico

Si todas las tareas son periódicas, se puede confeccionar un plan fijo de ejecución. No se utiliza la concurrencia, por lo que las distintas tareas se simulan en un programa secuencial, sin soporte del lenguaje o del sistema operativo.

Ejercicio #1 - Señales en POSIX

Vamos a empezar a realizar una serie de ejercicios para asimilar los conocimientos adquiridos en entradas anteriores. Las soluciones de los mismos las añadiré en un comentario de las correspondientes entradas a los ejercicios.

Diseñe e implemente un proceso que cada vez que reciba una señal SIGUSR1 incremente el valor de un contador (inicialmente a cero). Para ello, instale un manejador de la señal SIGUSR1 tal que cada vez que reciba dicha señal incremente el valor de un contador que cuente la cantidad de señales SIGUSR1 recibidas desde el inicio del programa. 

Además, cuando reciba una señal SIGUSR2, debe terminar el proceso mostrando un mensaje de finalización con el valor final del contador. 

El programa debe mostrar al principio de la ejecución su identificador de proceso (pid), así como el número de la señal en la que ha instalado un manejador de señales y el número de la señal por la que espera por su terminación. 

Nota 1: puede enviar señales al proceso desde un Terminal mediante el comando: kill -USR2 pid-del-proceso. 
Nota 2: puede conocer los identificadores de los procesos desde un Terminal mediante el comando: ps -ef

Autor de los ejercicios (Profesorado UMA)

Extensiones de Tiempo Real

El mecanismo de manejo de señales de POSIX.4 cuenta con la posibilidad de encolar las señales de tiempo real. Las señales pendientes están ordenadas por prioridad y se permite el intercambio de datos.

El rango de señales de Tiempo Real van desde SIGRTMIN a SIGRTMAX (el número de estas señales viene definido en RTSIG_MAX, constante de "rt_limits.h", se puede saber usando sysconf(_SC_RTSIG_MAX))

Señales POSIX

Configuración

Bloquear una señal significa que, en el caso de que sea generada, la señal no se pierde y se posterga su atención. El bloque se realiza mediante la máscara de señal

int sigemptyset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);

El bloqueo se genera por:

int pthread_sigmas(int how, const singset_t *restrict set, sigset_t *restrict oset);

Le pasamos como primer parámetro el bloqueo/desbloqueo "SIG_BLOQ" y "SIG_UNBLOQ". Acto seguido le pasamos el conjunto de señales.

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

Esta función afecta a todas las hebras del proceso. Si el "oset" no es NULL, almacena el conjunto de señales antiguo.

La asignación de un manejador para la señal se realiza mediante la función sigaction. Nos permitirá, entre otras cosas, ignorar una señal, asignar un manejador por defecto y asignar un manejador propio.

int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void (*sa_handler) (int);
   sigset_t_t sa_mask;
   int sa_flagas;
};

SIG_DFL: Manejador por defecto.
SIG_IGN: Ignora la señal

Manejador

Los manejadores de señales deben ser muy cortos y simples. Si una hebra necesita esperar hasta que se produzca una señal:

int sigwait (cont sigset_t *set, int *sig);
int sigwaitinfo(const sigset_t *set, siginfo_t *info);

Esta función realiza tres operaciones de forma atómica. Desbloquea el conjunto de señales, queda a la espera de que se produzca alguna señal no bloqueada y cuando se produce, se restablecen los bloqueos y se devuelve la señal producida.