Práctica 3

Anuncio
Sistemas Operativos
Ingenierı́a de telecomunicaciones
Sesión 3: Concurrencia
Calendario
Comienzo: Lunes 2 de noviembre y miércoles 4 de noviembre.
Entrega: 16 de noviembre y 18 de noviembre.
1.
Requisitos de teorı́a
Comprender los problemas de la concurrencia.
Comprender los mecanismos de gestión de la concurrencia como semáforos y exclusiones mutuas.
2.
Objetivos
Al finalizar esta sesión:
Identificarás correctamente situaciones conflictivas de concurrencia.
Conocerás las llamadas al sistema que manejan los semáforos.
Diseñarás correctamente esquemas de sincronización para solucionar situaciones
conflictivas.
3.
Motivación: paralelismo y sus problemas
Cuando trabajamos de forma paralela, y esto es algo constante en sistemas multitarea
como los actuales, es bueno pensar que “nadie nos garantiza nada”. Ya hemos aprendido
que nadie garantiza el orden de ejecución de los procesos, ni siquiera que todos ellos
1
se ejecuten el mismo número de veces. Por lo tanto, para no dar nada por supuesto,
como programadores tenemos que ocuparnos de garantizar que se cumple lo que nosotros
queremos que se cumpla.
En esta práctica vamos a ver qué problemas surgen cuando varios procesos acceden a
un mismo recurso y cómo podemos resolverlos.
4.
4.1.
Conceptos
Sincronización
Por sincronización entendemos un orden o una dependencia temporal de los hechos.
Por ejemplo, no puedes retirar el dinero de un cajero antes de que él te lo dé. Existe una
sincronización entre las acciones del usuario y las del cajero, ya que ese orden se garantiza
siempre.
En los sistemas paralelos muchas veces necesitaremos garantizar el orden de las acciones que realicen nuestros procesos. Para ello utilizaremos mecanismos que el sistema
operativo nos ofrece.
4.2.
Comunicación entre procesos
Existen una serie de mecanismos llamados IPC (Inter Process Communication) que
nos van a ayudar en la tarea de comunicar y coordinar procesos. En esta práctica vamos
a utilizar sólo dos de los que existen: memoria compartida y semáforos.
El comando ipcs nos muestra información sobre los objetos IPC (Inter Process Communication) que existen actualmente en el sistema. En particular nos va a interesar la
parte destinada a los semáforos. Si lo ejecutais en una terminal podéis ver algo como (el
formato exacto es dependiente del sistema):
Semaphores:
T
ID
s
65536
KEY MODE
OWNER
1297581835 --rw------- fherrero
GROUP
fherrero
El comando ipcrm permite borrar un conjunto de semáforos desde la lı́nea de comandos
(con la opción -s).
4.3.
Semáforos
Un semáforo es un objeto IPC, con una clave y un identificador asociados. Las operaciones que podremos realizar sobre él son signal o post y wait. Os remito a la parte
teórica de la asignatura para entender qué hace cada operación.
2
Veamos con un pequeño ejemplo cómo crear un conjunto de semáforos y cómo operar
con él:
#include
#include
#include
#include
<s y s / t y p e s . h>
<s y s / i p c . h>
<s y s /sem . h>
<s t d i o . h>
int main ( )
{
k e y t key = 3 4 ;
int semid = semget ( key , 1 , IPC CREAT | 0 6 0 0 ) ;
s e m c t l ( semid , 0 , SETVAL, 0 ) ;
struct sembuf o p e r a t i o n ;
o p e r a t i o n . sem num = 0 ;
o p e r a t i o n . sem op = −1;
operation . sem flg = 0;
semop ( semid , &o p e r a t i o n , 1 ) ;
p r i n t f ( ‘ ‘ Hasta a q u i l l e g a m o s \n ’ ’ ) ;
return 0 ;
}
La función semget() nos devuelve el identificador de un objeto de tipo “conjunto de
semáforos”. La opción IPC CREAT le dice que lo cree si no existe ya (probad a quitarle la
opción y utilizad una clave nueva). Los otros parámetros son una clave asociada al objeto
IPC y el número de semáforos que contendrá este conjunto que queremos compartir.
Con semctl() (como siempre, usad el man), podemos realizar varias operaciones,
como inicializar el valor de un semáforo del conjunto (como en este caso) o eliminar el
conjunto con IPC RMID. Consultad la página man para saber cómo.
Compilad y ejecutad este ejemplo. ¿Qué pasa? ¿Que pasarı́a si en lugar de 0 pusiéramos
1 en el último argumento de semctl()? Probad lo mismo con el siguiente ejemplo:
#include
#include
#include
#include
#include
<s y s / t y p e s . h>
<s y s / i p c . h>
<s y s /sem . h>
<s t d i o . h>
< s t d l i b . h>
void h i j o ( int semid )
{
struct sembuf o p e r a t i o n ;
3
o p e r a t i o n . sem num = 0 ;
o p e r a t i o n . sem op = 1 ;
operation . sem flg = 0;
p r i n t f ( ” Soy e l h i j o , e s p e r a n d o t r e s s e g un d o s \n” ) ;
sleep (3) ;
semop ( semid , &o p e r a t i o n , 1 ) ;
exit (0) ;
}
int main ( )
{
k e y t key = 3 4 ;
int semid = semget ( key , 1 , IPC CREAT | 0 6 0 0 ) ;
s e m c t l ( semid , 0 , SETVAL, 1 ) ;
struct sembuf o p e r a t i o n ;
o p e r a t i o n . sem num = 0 ;
o p e r a t i o n . sem op = −1;
operation . sem flg = 0;
p i d t pid = f o r k ( ) ;
i f ( p i d == 0 ) {
h i j o ( semid ) ;
}
semop ( semid , &o p e r a t i o n , 1 ) ;
p r i n t f ( ” Padre , h a s t a a q u i l l e g a m o s \n” ) ;
return 0 ;
}
4.4.
Unicidad
¿Qué pasarı́a si ya existiese un conjunto de semáforos con la clave que nosotros queremos usar? Puede ser que, por casualidad, hayamos escogido una clave que ya está en
uso. Con un poco de imaginación y la función ftok() podemos conseguir claves únicas
que harán a nuestros programas más robustos.
4
4.5.
Sincronización en hilos
Los hilos siguen teniendo los mismos problemas de sincronización que los procesos, eso
es algo inherente al paralelismo.
Existen multitud de mecanismos de sincronización para hilos. Combinándolos se pueden resolver multitud de problemas de paralelismo.
Uno de ellos son los objetos de exclusión mutua. Son similares a los semáforos en
ciertos aspectos, aunque tienen algunas diferencias:
1. Un objeto de exclusión mutua sólo puede tener dos valores: libre o bloqueado.
2. Sólo puede hacer una operación de desbloqueo un hilo que ya lo haya bloqueado.
Las exclusiones mutuas o mutexes se manejan con pthread mutex init, pthread mutex lock,
pthread mutex unlock y pthread mutex destroy.
Otro mecanismo que nos será muy útil para esta práctica son las condiciones. Los
objetos de condición son un mecanismo útil para que un hilo indique a los demás que algo
ha ocurrido. Como en el caso de los semáforos, un hilo puede quedarse a la escucha de
lo que pasa con esa condición (pthread cond wait) y otro puede avisarle de que se ha
cumplido la condición (pthread cond signal). Adicionalmente, con objetos de condición
se puede avisar a varios hilos a la vez, usando pthread cond broadcast.
Normalmente los objetos de condición se utilizan como complemento a una comprobación de alguna variable. Imagina que un hilo quiere comprobar si se ha cumplido algo,
y si todavı́a no ha ocurrido, que alguien le avise cuando eso pase (pseudocódigo):
if(variable == algun_valor){
variable = otro_valor
}
else{
espera a que variable alcance algun_valor
}
Como variable será una variable compartida, tenemos que garantizar que los accesos
no generan problemas. Para ello hay que utilizar siempre mutexes que protejan el acceso
a variable:
Bloquea mutex de variable
if(variable == algun_valor){
variable = otro_valor
libera mutex
}
5
else{
libera mutex y espera a que variable alcance algun_valor
}
5.
Comprobación de errores
Aunque sea redundante, vuelvo a insistir en la necesidad de comprobar errores: si
queremos reservar algo y luego lo utilizamos, tenemos que asegurarnos de que lo hemos
reservado bien, etc. Para ello comprobad en las páginas man los valores devueltos de las
llamadas al sistema.
Si ahora miráis la salida de ipcs veréis que todos los recursos IPC que hayáis creado
ejecutando vuestras prácticas siguen ahı́, no se liberan automáticamente. Por eso es
muy importante esta vez que los libere vuestro proceso (siempre lo es, pero esta vez con
razón). Tenéis que tener cuidado, eso sı́, de no borrarlos antes de que el resto de procesos
hayan terminado de usarlos.
Más páginas man interesantes relacionadas con el control de errores: errno(3) y
perror(3), en la sección 3 del manual.
6.
Ejercicios
6.1.
Conocimiento
Cada pregunta vale 1 punto. Serán valoradas como correctas o incorrectas.
¿Por qué pthread cond wait libera un mutex antes de bloquear al hilo? ¿Se podrı́a
hacer también usando pthread mutex unlock?
¿Qué es un esquema de sincronización productor/consumidor ?
¿Qué llamada al sistema y con qué parametros utilizarı́as para obtener el valor que
tiene un semáforo IPC?
¿Cómo se puede averiguar en linux cuántas CPUs tiene la máquina? Argumenta tu
respuesta con un ejemplo real.
6.2.
Experimentación
Las máquinas del laboratorio tienen dos CPUs. ¿Por qué? ¿Por qué es mejor tener dos
que una? Teniendo en cuenta los dolores de cabeza que da asegurarse de que las cosas
se ejecutan en su orden, ¿realmente merece la pena tener más de una? Vamos a intentar
comprobarlo.
6
(2 puntos) Vamos a escribir un programa que cree varios hilos y vaya repartiendo
una tarea entre ellos. En concreto, queremos calcular la función f (x) = exp(x) −
1/ exp(x) entre −3 y 3.
El programa deberá recibir como argumentos por lı́nea de comandos el número de
hilos a crear y el número de puntos a evaluar de la función. Es decir, la siguiente
lı́nea:
$> program 4 1000000
creará cuatro hilos y evaluará la función f (x) en un millón de puntos en el intervalo
[−3, 3).
La manera de repartir las tareas entre los hilos es fácil: cada hilo deberá calcular un
tramo de la función, por ejemplo el hilo 0 calculará [−3, −1,5), el hilo 1 [−1,5, 0),
etc. Cada hilo imprimirá por pantalla el valor de x y el valor de f (x), de tal forma
que al final se pueda comprobar que se está calculando correctamente la función.
El ejercicio consiste en estudiar el comportamiento del programa en función del
número de hilos y la complejidad del problema. Vamos a usar el comando time para
ver cuánto tiempo tarda en ejecutarse en cada caso. Puedes escribir un script de
shell como el siguiente:
for t h r e a d s i n 1 2 4 8
do
i t e r a t i o n s =1000
f o r n i n 1 2 3 4 5 6 7 8 9 10 11 12 13 14
do
i t e r a t i o n s=$ ( ( $ i t e r a t i o n s ∗ 2 ) )
echo −n $ t h r e a d s $ i t e r a t i o n s ; time prog0 $ t h r e a d s
$ i t e r a t i o n s > out
done
echo
done
Representa gráficamente los tiempos de usuario y tiempos de sistema y contesta
razonadamente a las siguientes preguntas:
• ¿Compensa el uso de hilos frente a un proceso monohilo? (0,5 puntos)
• ¿Qué significa el tiempo de usuario? (0,25 puntos)
• ¿Qué significa el tiempo de sistema? (0,25 puntos)
• ¿Cómo explicarı́as el comportamiento del sistema? (0,5 puntos)
6.3.
Programación
Vamos a experimentar con los semáforos y las exclusiones mutuas. Las utilizaremos
para resolver algunos problemas comunes de sincronización.
7
Para este ejercicio utilizarás hilos. Escribe un programa que cree varios hilos. Cada
uno de ellos escribirá su identificador. Deberás diseñar un mecanismo de sincronización de tal forma que cada hilo pueda ejecutarse cuando quiera, pero deberán
esperar al resto de los hilos antes de volver a imprimir su identificador. (3 puntos)
8
Descargar