Práctica Lectores-Escritores con tuberías

Anuncio
Unistmo
Ing. en Computación
Sistemas Operativos
Sistemas Operativos
Problemas de comunicación y sincronización (Lectores-Escritores).
En el problema de los lectores-escritores existe un determinado objeto que
va a ser utilizado y compartido por una serie de procesos concurrentes.
Algunos procesos solo acceden al objeto sin modificarlo (lectura) y
algunos acceden para modificarlo (escritura), lo que lleva a algunas
restricciones:
1.- Solo se permite que un escritor tenga acceso al mismo tiempo.
2.- Se permite cualquier número de lectores accediendo al mismo tiempo.
En el presente documento se explica como crear una aplicación gráfica que utiliza una tubería sin
nombre como mecanismo de sincronización para simular el problema de los Lectores-Escritores.
Primero crearemos la ventana principal utilizando la aplicación Glade-2 mediante los siguientes pasos:
1.- Generamos un Nuevo Proyecto GTK +
2.- De la Paleta de Widgets seleccionamos Window
3.- En la ventana de propiedades del widget window configuramos lo siguiente:
a) Nombre: window
b) Título: Lectores-Escritores
c) Ancho y largo de 250
d) Añadimos la señal destroy
4.- Adicionamos y configuramos widgets de la siguiente forma:
a) Adicionamos una caja vertical con dos renglones a window
b) En el primer renglón adicionamos una barra de herramientas con dos botones
c) El primer botón será para el cuadro Acerca de (usar botón de stock y añadir la señal clicked)
d) El segundo botón será para salir (usar botón de stock y añadir la señal clicked)
e) En el segundo renglón adicionamos una área de dibujo (Drawing Area), cambiar el nombre a
drawingarea y añadir las señales de configure_event y expose_event a este widget
5.- Guardar como LectoresEscritores y construir
6.- Entramos a la terminal y nos cambiamos a la ruta del proyecto
cd Projects/LectoresEscritores/
7.- Ejecutamos la autogeneración y compilamos
./autogen.sh
make
8.- Nos cambiamos al directorio de los fuentes y editamos el archivo callbacks.c
cd src/
gedit callbacks.c
9.- El código de la retrollamada on_window_destroy deberá quedar como sigue:
void
on_window_destroy
{
(GtkObject
gpointer
*object,
user_data)
gtk_main_quit();
}
Esto hará que se salga del bucle de procesamiento de las señales y se termine la aplicación.
M. en C. J. Jesús Arellano Pimentel
Abril 2011
Unistmo
Ing. en Computación
Sistemas Operativos
10.- El código para la retrollamada on_toolbuttonSalir_clicked deberá quedar como sigue:
void
on_toolbuttonSalir_clicked
{
(GtkToolButton
gpointer
}
*toolbutton,
user_data)
on_window_destroy((GtkObject *)toolbutton, user_data);
Simplemente se trata de llamar a la función editada en el paso anterior (nueve).
11.- Codificamos las retrollamadas configure_event y expose_event para el área de dibujo de la
siguiente manera:
gboolean
on_drawingarea_configure_event
{
(GtkWidget
*widget,
GdkEventConfigure *event,
gpointer
user_data)
if(pixmap != NULL)
gdk_pixmap_unref(pixmap);
pixmap = gdk_pixmap_new(widget->window,
widget->allocation.width,
widget->allocation.height,
-1);
trazar(widget);
}
return TRUE;
gboolean
on_drawingarea_expose_event
{
(GtkWidget
GdkEventExpose
gpointer
*widget,
*event,
user_data)
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
pixmap,
event->area.x, event->area.y,
event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
Observe que en código anterior se utiliza una variable de nombre pixmap y un procedimiento llamado
trazar, la creación de estos dos elementos se detallan en los siguientes pasos.
12.- Declaramos la variable global pixmap después de la inclusión de librerías
GdkDrawable * pixmap
= NULL;
Esta variable es prácticamente el lienzo sobre el cual vamos a dibujar.
13.- Implementamos, abajo de la declaración de la variable anterior, el procedimiento trazar
int trazar(GtkWidget * widget)
{
GdkRectangle update_rect;
GtkWidget *where_to_draw = lookup_widget(widget,"drawingarea");
GdkFont *font = NULL;
M. en C. J. Jesús Arellano Pimentel
Abril 2011
Unistmo
Ing. en Computación
Sistemas Operativos
if(font == NULL)
if((font = gdk_font_load("fixed"))==NULL)
return FALSE;
gdk_draw_rectangle(pixmap, widget->style->white_gc,
TRUE,
0, 0,
where_to_draw->allocation.width,
where_to_draw->allocation.height);
update_rect.x = 0;
update_rect.y = 0;
update_rect.width = where_to_draw->allocation.width;
update_rect.height = where_to_draw->allocation.height;
gtk_widget_draw(where_to_draw, &update_rect);
return TRUE;
}
Este procedimiento solo “limpia” el área de dibujo con un rectángulo relleno de color blanco toda el
área cliente de la ventana.
14.- En este punto podemos guardar los cambios al archivo callbacks.c y compilar para verificar que no
existen errores de sintaxis, desde la terminal se debe ejecutar la siguiente sentencia:
make
15.- Ahora adicionamos el cuadro de dialogo Acerca de. Para esto, presionamos el botón de los widgets
adicionales de la paleta de widgets del Glade2, damos clic en el widget correspondiente al cuadro de
dialogo Acerca de (About Dialog). Configuramos las siguientes propiedades:
a) Lo nombramos aboutdialog
b) Como comentario escribimos “Simulación del problema de lectores-escritores”
c) En autores le ponemos nuestro nombre :).
d) Habilitamos la propiedad de auto-destruir
e) Le adicionamos la señal de destroy
16.- Guardamos, construimos y compilamos
17.- Editamos nuevamente el archivo callbacks.c adicionando la siguiente variable global:
GtkWidget * acercade
= NULL;
18.- El código de la retrollamada on_toolbuttonAcercade_clicked debe quedar de la siguiente forma:
void
on_toolbuttonAcercade_clicked
{
(GtkToolButton
gpointer
*toolbutton,
user_data)
if(acercade == NULL){
acercade = create_aboutdialog();
}
gtk_widget_show(acercade);
}
Esta función simplemente muestra el widget del cuadro de dialogo Acerca de, siempre y cuando exista
un cuadro de dialogo creado.
19.- El código de la retrollamada on_aboutdialog_destroy debe quedar de la siguiente forma:
M. en C. J. Jesús Arellano Pimentel
Abril 2011
Unistmo
Ing. en Computación
void
on_aboutdialog_destroy
{
}
(GtkObject
gpointer
Sistemas Operativos
*object,
user_data)
acercade = NULL;
20.- Guardamos, compilamos y probamos
make
./lectoresescritores
Hecho lo anterior, se tiene lista la interfaz gráfica de usuario para implementar el problema de los
lectores-escritores. Parra este propósito se deberán realizar los siguientes pasos:
1.- Editar el archivo main.c.
gedit main.c
2.- Añadir el siguiente código después de la inclusión de librerías:
#include <stdlib.h>
#define ESCRITOR
#define LECTOR
1
2
extern int trazar(GtkWidget *);
int tuberia[2];
/*mecanismo de sincronización
int numproc = 5;
/*número de procesos a ejecutar
int rol = LECTOR;
/*rol del proceso en ejecución
char c;
/*dato testigo
gchar szRol[64]= "Iniciando ...";
*/
*/
*/
*/
Estamos declarando la tubería (mecanismo de sincronización) a utilizar y cuantos procesos se
ejecutarán simultáneamente, así como el rol inicial que tendrán todos ellos.
3.- Los procesos estarán cambiando de rol para competir por la escritura, con la restricción de que solo
uno de ellos puede escribir a la vez. Para cambiarse de rol y permitir un solo escritor a la vez la
siguiente función puede ayudar:
int cambiarrol(void *w)
{
int ale;
trazar((GtkWidget *)w);
if(rol == ESCRITOR)
{
read(tuberia[0], &c, 1);
sleep(3);
write(tuberia[1], &c, 1);
}
ale = 1+(int) (10.0*rand()/(RAND_MAX+1.0));
if(ale <= 9){
rol = LECTOR;
sprintf(szRol,"Lector Activo %d",ale);
}
else{
rol = ESCRITOR;
M. en C. J. Jesús Arellano Pimentel
Abril 2011
Unistmo
Ing. en Computación
}
Sistemas Operativos
sprintf(szRol,"Escritor %d",ale);
return TRUE;
}
Los lectores siempre estarán activos, pero un escritor deberá obtener el dato testigo primero antes de
escribir. Aleatoriamente se decide si un proceso es escritor o lector, puede observarse en el código que
si el número aleatorio generado es menor a 9 el proceso será lector, pero si el número aleatorio es 10
será escritor.
4.- El siguiente paso es modificar la función main. Habrá que agregar la declaración de las siguientes
variables:
pid_t pid;
int i;
5.- En la misma función main se deberá crear la tubería, escribir el dato testigo y crear la cantidad de
procesos deseados. Esto se logra mediante el siguiente segmento de código después del bloque de
compilación condicional:
if (pipe(tuberia) == -1)
{
perror("[pipe]");
return -1;
}
write(tuberia[1], &c, 1);
for(i = 0; i < numproc; i++){
pid = fork();
if(pid == -1){
printf("Error al crear el hijo");
break;
}
if(pid != 0){
break;
}
}
6.- Ahora debemos programar un temporizador para que los procesos cambien de rol cada cierto
tiempo. Esto también es dentro de la función main, después de haber creado y mostrado la ventana
principal mediante la siguiente línea de código:
g_timeout_add(1000, cambiarrol, (void *)window);
7.- Finalmente se deberá cerrar la tubería antes del return 0; de la función main de la siguiente forma:
close(tuberia[0]);
close(tuberia[1]);
8.- Para poder visualizar el rol del proceso es necesario modificar la función trazar del archivo
callbacks.c adicionando la siguiente línea de código después de dibujar el rectángulo blanco:
gdk_draw_string(pixmap, font, widget->style->black_gc,
10, 15, szRol);
9.- Sin embargo la variable global szRol esta definida en el archivo main.c, por lo que se deberá
declarar como variable externa en el archivo callbacks.c.
extern gchar szRol[64];
10.- Guardar los cambios en los archivos main.c y callbacks.c, compilar y probar desde la terminal.
make
./lectoresescritores &
M. en C. J. Jesús Arellano Pimentel
Abril 2011
Unistmo
M. en C. J. Jesús Arellano Pimentel
Ing. en Computación
Sistemas Operativos
Abril 2011
Descargar