• No se han encontrado resultados

Transacciones atómicas

In document Conceptos de Sistemas Operativos (página 78-83)

La exclusión mutua de secciones criticas asegura que las secciones criticas son ejecutadas atómicamente. Esto es, si dos secciones criticas son ejecutadas concurrentemente, el resultado es equivalente a sus ejecuciones en cualquier orden. Aunque ésta propiedad es muy útil en muchas aplicaciones, existen otras donde queremos que la sección critica forme una única unidad lógica de trabajo donde las operaciones son realizadas o todas o no se hace ninguna de ellas. Un ejemplo es en una transferencia, en donde una cantidad es debitada y otra es acreditada. Claramente, es esencial para la consistencia de los datos, o que se haga la acreditación y el débito, o ninguno de los dos.

Esta sección está relacionada con los sistemas de base de datos. Las base de datos están involucradas con el almacenamiento y recuperación de datos, y con la consistencia de los datos.

Modelo del sistema: una colección de instrucciones (operaciones) que realizan una única función lógica es llamada transacción. El mayor problema en el procesamiento de transacciones es preservar la atomicidad a pesar de fallas en la computadora. En esta sección se verán varios métodos que aseguran la atomicidad de la transacción.

Una transacción es una secuencia de operaciones read y write, terminadas por su operación commit o abort. Una operación commit significa que la transacción ha terminado su ejecución exitosamente, mientras que una operación abort significa que la transacción a parado su normal ejecución debido a algún error. El efecto de una transacción finalizada exitosamente no puede ser desasida por la cancelación de la misma.

Una transacción puede parar su normal ejecución debido a una falla del sistema. En ambos casos, ya que una transacción abortada puede haber cambiado algunos de los datos antes de que sea parada, el estado de estos datos puede no ser el mismo que hubiera quedado en caso de que la transacción haya terminado exitosamente. Si se asegura la propiedad de atomicidad de la transacción, una transacción abortada no debe tener efecto sobre los datos que ya ha modificado. Así, el estado de los datos accedidos por una transacción abortada debe ser restablecido al estado que estaban los datos antes de que la transacción comenzara su ejecución. Si esto ocurre, se dirá que una transacción ha sido roll back.

Para determinar como se puede asegurar la propiedad de atomicidad, primero se deben identificar las propiedades de los dispositivos usados para almacenar los datos accedidos por las transacciones. Varios tipos de medios de almacenamiento se distinguen por sus velocidades, capacidad, y por su elasticidad en caso de fallos.

Almacenamiento volátil: la información residente en el almacenamiento volátil no sobrevive a caídas

del sistema. Ejemplos de tales almacenamientos son la memoria principal y la memoria cache. El acceso a la memoria volátil es muy rápido, ya que es una memoria de alta velocidad y porque es posible acceder a cualquier dato directamente.

Almacenamiento no volátil: la información residente en el almacenamiento no volátil usualmente

sobrevive a caídas del sistema. Ejemplos de tales medios de almacenamientos son discos y cintas magnéticas. Actualmente, el almacenamiento no volátil es mucho más lento que el almacenamiento volátil ya que los discos y las cintas son electrónicas y requieren movimientos físicos.

Almacenamiento estable: la información residente en almacenamiento estable nunca se pierde. Para

implementar una aproximación a tales almacenamientos, se necesita reproducir la información en varios medios de almacenamientos no volátiles (usualmente discos) con modos de fallas diferentes, y cambiar la información de una manera controlada.

Recuperación basada en registros: una forma de asegurar la atomicidad es registrar, en almacenamiento estable, información describiendo todas las modificaciones realizadas por la transacción. El sistema mantiene, en un almacenamiento estable, una estructura de datos llamada log. Cada registro del log describe una única operación de una transacción write, y tiene los siguientes campos:

Nombre de la transacción: el nombre de la transacción que realiza la operación write.

Nombre del ítem de dato.

Valor viejo: el valor del dato antes de la escritura.

Valor nuevo: el valor que el dato tendrá después de la escritura.

Antes de que una transacción Ti comience su ejecución, el registro <Ti starts> es escrito en el log. Durante su ejecución, cualquier operación de escritura realizada por la transacción Ti es precedida por la escritura de un nuevo registro apropiado en el log. Cuando Ti commits, el registro <Ti commits> es escrito en el log. Ya que la información en el log es usada para reconstruir el estado del dato, no podemos permitir que los actuales cambios realizados en el dato tomen lugar antes de que los correspondientes registros del log estén escrito en el almacenamiento estable. Ante esto, antes de que se ejecute, por ejemplo, la operación write(X), el correspondiente registro log debe ser escrito en el almacenamiento estable.

Note que se requieren dos escrituras físicas para cada pedido de escritura lógica. Además, se necesita más almacenamiento: para los datos mismos y para los registros del log. Usando el log, el sistema puede manejar cualquier fallo. El algoritmo de recuperación usa dos procedimientos:

Undo(Ti), el cual reestablece el valor de todos los datos cambiados por la transacción Ti al los viejos valores.

Redo(Ti), el cual setea el valor de todos los datos modificados por la transacción Ti a los nuevos valores.

Si una transacción Ti aborta (es decir, tiene el registro <Ti starts> al inicio, pero no el registro <Ti commits> al final), entonces se puede restaurar el estado de los datos que se han cambiado ejecutando simplemente undo(Ti).

Checkpoints: cuando ocurre una falla de sistema, se debe consultar el log para determinar aquellas transacciones que se necesitan volver a hacer y aquellas que se necesitan des hacer. En principio, se necesita recorrer en el log completo para hacer estas determinaciones. Existen dos inconvenientes para esto:

1. El proceso de búsqueda esta consumiendo tiempo.

2. La mayoría de las transacciones que, acorde a sus algoritmos, necesitan ser re hechas, ya han cambiado los datos que actualmente el log dice que necesita modificar. Aunque re hacer las modificaciones de los datos no causara daño, esta acción tomara tiempo.

Para reducir este tipo de overhead, se introduce el concepto de punto de chequeos. Durante la ejecución, el sistema mantiene el log y la idea de escribir primero el registro en el log en caso de una escritura y luego ejecutar la escritura. Además, el sistema realiza periódicos puntos de chequeo, el cual requiere la siguiente secuencia de acciones:

1. Copiar todos los registros del log actualmente residentes en almacenamiento volátil (usualmente en memoria principal) a almacenamiento estable.

2. Copiar todos los datos modificados residentes en almacenamiento volátil a almacenamiento estable. 3. Copiar un registro en el log <checkpoint> en almacenamiento estable.

La presencia de un registro <checkpoint> en el log permite al sistema llevar pista para usarlo en el procedimiento de recuperación. Consideremos una transacción Ti que commited antes de punto de chequeo. El registro <Ti commits> aparecerá en el log antes del registro <checkpoint>. Cualquier modificación realizada por Ti debe haber sido escrita en el almacenamiento estable o antes del punto de chequeo, o como parte del punto de chequeo mismo (es decir, cuando se produjo el punto de chequeo, por la rutina misma del chequeo se copia todo en almacenamiento estable). Así, en el momento de recuperación, no existe necesidad de realizar la operación redo sobre Ti.

Ante esto, el nuevo algoritmo de recuperación es: luego de que ocurre una falla, la rutina de recuperación examina el log para determinar la transacción Ti más reciente que comenzó su ejecución antes del punto de chequeo más reciente. Este encuentra tal transacción buscando en el log hacia atrás para encontrar el primer registro <checkpoint>, y luego encuentra el subsecuente registro <Ti start> (es decir, la transacción empezó pero no llego a terminar).

Una vez que la transacción Ti a sido identificada, las operaciones redo y undo necesitan ser aplicadas solo a la transacción Ti y toda transacción Tj que comenzó la ejecución después de la transacción Ti. Denotaremos este conjunto de transacciones por medio del conjunto T. El resto del log puede entonces ser ignorado. Las operaciones de recuperación que se requieren son las siguientes:

 Para toda transacción Tk en T tal que el registro <Tk commits> aparece en el log, ejecutar redo(Tk).  Para toda transacción Tk en T tal que el registro <Tk commits> no aparece en el log, ejecutar

Transacciones atómicas concurrentes: ya que cada transacción es atómica, la ejecución concurrente de transacciones debe ser equivalente al caso donde estas transacciones se ejecutan serialmente en algún orden arbitrario. Esta propiedad, llamada seriabilidad, se puede mantener simplemente ejecutando cada transacción en una sección critica. Esto es, todas las transacciones comparten un semáforo común mutex, el cual se inicializa en 1. Cuando una transacción comienza la ejecución, su primer acción es ejecutar wait(mutex). Luego de que la transacción hace un commit o un abort, ejecuta signal(mutex).

Aunque este esquema asegura la atomicidad de las transacciones ejecutándose concurrentemente, es demasiado restricto. Como veremos, existen muchos casos donde se permitirá solapar la ejecución de transacciones, mientras se mantenga la seriabilidad. Hay un número diferente de algoritmos de control de concurrencia para asegurar la seriabilidad.

Serializabilidad: Considere un sistema con dos ítems A y B, ambos son leídos y escritos por dos transacciones T0 y T1. Supongamos que estas transacciones son ejecutadas atómicamente en el orden T0 seguida de T1. Esta secuencia de ejecución, el cual es llamada un schedule, se ve en la figura 6.23.

Un schedule donde cada transacción es ejecutada atómicamente es llamado schedule serial. Cada schedule serial consiste de una secuencia de instrucciones de varias transacciones donde las instrucciones pertenecientes a una transacción aparecen juntas en el scheduler. Así, para un conjunto de n transacciones, existen n! diferentes schedulers seriales validos.

Si permitimos que dos transacciones solapen su ejecucion, entonces el scheduler resultante no es serial. Un scheduler no serial no necesariamnete implica que su resultado sea una ejecución incorrecta. Para ver esto, definamos la noción de operaciones conflictivas. Consideremos un scheduler S en el cual hay dos operaciones consecutivas Oi y Oj de las transacciones Ti y Tj respectivamente. Diremos que Oi y Oj están en conflicto si acceden al mismo dato y, al menos una de estas operaciones es la operación write. Para ilustrar este concepto, consideremos el scheduler no serial que se ve en la figura 6.24. La operación write(A) de T0 esta en conflicto con la operación read(A) de T1. Sin embargo, la operación write(A) de T1 no tiene conflicto con la operación read(B) de T0, ya que las dos operaciones acceden a ítems diferentes.

T0 T1 Read(A) Write(A) Read(B) Write(B) Read(A) Write(A) Read(B) Write(B)

Figura 6.23 Schedule 1: un schedule serial.

T0 T1 Read(A) Write(A) Read(A) Write(A) Read(B) Write(B) Read(B) Write(B)

Figura 6.24 Schedule 2: Un schedule serializable concurrente.

Ilustremos la idea de swapping considerando nuevamente el segundo scheduler. Como la operación write(A) de T1 no tiene conflicto con la operación read(B) de T0, podemos intercambiar estas operaciones para generar un scheduler equivalente. Sin tener en cuenta el estado inicial del sistema, ambos schedulers producen el mismo estado final. Continuando con este procedimiento de cambio de operaciones sin conflictos:

Cambiar la operación read(B) de T0 con la operación read(A) de T1. Cambiar la operación write(B) de T0 con la operación write(A) de T1. Cambiar la operación write(B) de T0 con la operación read(A) de T1.

Protocolo de bloqueo: una forma de asegurar la seriabilidad es la de asociar a cada ítem de dato un bloqueo, y requerir que cada transacción siga un protocolo de bloqueo que gobierna como los bloqueos son adquiridos y liberados. Hay varios modos en el cual el dato puede ser bloqueado. En esta parte, mostraremos dos modos:

Compartido: si una transacción Ti ha obtenido un bloqueo de modo compartido

(denotado por S) sobre el dato Q, entonces Ti puede leer este ítem, pero no puede escribirlo.

Exclusivo: si una transacción Ti ha obtenido un bloqueo de modo exclusivo (denotado

por X) sobre el dato Q, entonces Ti puede tanto leer como escribir el ítem.

Se requiere que cada transacción pida un bloqueo de un modo apropiado sobre el dato Q, dependiendo del tipo de operación que se realizara sobre Q.

Para acceder al dato Q, la transacción Ti debe primero bloquear Q en el modo apropiado. Si Q no esta actualmente bloqueado, entonces se concede el bloqueo, y Ti puede acceder al dato. Sin embargo, si el dato Q esta actualmente bloqueado, por alguna otra transacción, entonces Ti debe esperar. Más precisamente, supongamos que Ti pide un bloqueo exclusivo sobre Q. En este caso, Ti debe esperar hasta que el bloqueo sobre Q se libera. Si Ti pide un bloqueo compartido sobre Q, entonces Ti debe esperar si Q esta bloqueado en modo exclusivo. De otra manera, este obtendrá el bloqueo y acceso a Q.

Un protocolo que asegura seriabilidad es el protocolo de bloqueo de dos fases. Este protocolo requiere que cada transacción emita pedidos de bloqueo y desbloqueo en dos fases:

Fase de aumento: una transacción puede obtener bloqueos, pero no puede liberar

ningún bloqueo.

Fase de disminución: una transacción puede liberar bloqueos, pero no puede obtener

nuevos bloqueos.

Inicialmente, una transacción esta en la fase de crecimiento. La transacción adquiere bloqueos cuando los necesita. Una vez que la transacción libera un bloqueo, entra en la etapa de disminución, y no se pueden emitir más pedidos de bloqueos.

El protocolo de bloqueos de dos fases asegura la seriabilidad de conflictos pero no asegura libertad de deadlock.

7. Deadlocks

En un entorno multiprogramado, varios procesos pueden competir por recursos. Un proceso pide recursos; en caso de que todos los recursos que pida no están todos disponibles a la vez, entonces entra en estado de espera. Pero esto puede provocar que el proceso nunca salga de su estado de espera, ya que los recursos que pidió están siendo utilizados por otros procesos que también están en estado de espera. A ésta situación se la denomina deadlock.

En este capítulo se verán métodos que tratan los problemas de deadlock. Note, sin embargo, que los sistemas operativos más actuales no proveen la prevención de deadlock. Tales métodos se están agregando en los sistemas ya que los problemas de deadlock se están haciendo cada vez más comunes. Esto es causado porque día a día esta aumentando el número de procesos, el aumento de recursos en un sistema (incluyendo CPUs), y la tendencia hacia los sistemas de base de datos en lugar de los sistemas batch.

In document Conceptos de Sistemas Operativos (página 78-83)