Professional Documents
Culture Documents
ESCUELA SUPERIOR DE
INGENIERÍA MECÁNICA Y ELÉCTRICA
UNIDAD CULHUACÁN
Ingeniería en computación
Sistemas Operativos
Profesor:
Hernández Espinosa Juan Manuel
Alumno:
Vásquez Romano Ranferi Marduk
Grupo: 6CM23
Introducción
Ya hemos abordado dos aspectos de la compartición de recursos desde dos
perspectivas, la compartición de la memoria y compartición del procesador, pero
el procesador compartido descrito está muy lejos del escenario del mejor caso,
libre de conflictos y complicaciones. En este capítulo analizamos los problemas
ocasionados cuando muchos procesos compiten por relativamente pocos
recursos y el sistema es incapaz de atender todos los procesos que hay en el
sistema.
Justificación
Ya vimos cómo la falta de sincronización de procesos puede resultar en dos
situaciones extremas: bloqueo mutuo o inanición.
En los primeros sistemas operativos, el bloqueo mutuo era conocido por la frase
más descriptiva “abrazo de la muerte” y esto es lo que sucede exactamente
2
cuando el sistema se congela y por esto, debemos conocer el funcionamiento
del administrador de procesos.
Objetivos
Varias causas del bloqueo mutuo y bloqueo activo del sistema.
La diferencia entre prevenir y evitar bloqueos mutuos.
Cómo detectar bloqueos mutuos y recuperarse de ellos.
El concepto de inanición de un proceso, cómo detectarlo y recuperarse de él.
El concepto de competencia y cómo prevenirlo.
La diferencia entre bloqueo mutuo, inanición y competencia.
Desarrollo
El problema surge cuando los recursos necesarios para ese trabajo son los
mismos en posesión de otros trabajos que están esperando su ejecución, pero
no es posible porque están esperando otros recursos que no están disponibles.
El bloqueo mutuo se completa si el resto del sistema también llega a una
situación de detención. Cuando el sistema operativo no puede resolver esta
situación, se requiere la intervención externa.
Bloqueo mutuo
3
Siete casos de bloque mutuo
Un bloqueo mutuo suele ocurrir cuando recursos no compartibles y no
expropiativos, como archivos, impresoras o escáneres, se asignan a trabajos
que finalmente requieren otros recursos compartibles no expropiativos: recursos
que han sido bloqueados por otros trabajos. Sin embargo, los bloqueos mutuos
no están restringidos a archivos, impresoras y escáneres.
También pueden ocurrir en recursos compartibles, como discos y bases de
datos. Las gráficas representan directamente de manera visual los recursos y
procesos del sistema, y muestran cómo se bloquean mutuamente.
4
que buscan o modifican partes de una base de datos. Las solicitudes suelen
llegar en forma aleatoria y pueden intercalarse de manera arbitraria.
Bloquear es una técnica usada para garantizar la integridad de los datos, por
medio de la cual el usuario bloquea a todos los otros usuarios mientras está
trabajando con esa base de datos. Bloquear puede hacerse en tres niveles
diferentes: puede bloquearse toda la base de datos mientras dura la solicitud;
puede bloquearse una subsección de la base de datos; o puede bloquearse sólo
el archivo individual hasta que se completa el proceso. Bloquear toda la base de
datos (la solución más extrema y exitosa) previene la ocurrencia de un bloqueo
mutuo, pero restringe el acceso a la base de datos a un usuario a la vez y, en un
entorno multiusuario, los tiempos de respuesta aminoran significativamente; esta
solución es normalmente inaceptable.
Una alternativa, por supuesto, es no usar bloqueos, aunque esto lleva a otras
dificultades. Si no se usan bloqueos para preservar la integridad de los registros
actualizados, éstos en la base de datos podrían incluir sólo algunos de los datos,
y su contenido dependería del orden en que cada proceso termina su ejecución.
5
Caso 4: Bloqueos mutuos en asignación de dispositivos
múltiples
Considere el caso de una empresa de diseño industrial con tres procesos (P1,
P2 y P3) y tres dispositivos dedicados: escáner, impresora y plotter. La siguiente
secuencia de eventos da por resultado un bloqueo mutuo:
1. P1 solicita y obtiene el escáner.
2. P2 solicita y obtiene la impresora.
3. P3 solicita y obtiene el plotter.
4. P1 solicita la impresora, pero está bloqueado.
5. P2 solicita el plotter, pero está bloqueado.
6. P3 solicita el escáner, pero está bloqueado.
Así como ocurre en los primeros ejemplos, ninguno de los trabajos puede
continuar porque cada uno está esperando un recurso que tienen los otros.
Caso 5: Bloqueos en spooling
El sistema o spooler acepta resultados de varios usuarios y actúa como un área
de almacenamiento temporal para todos los resultados hasta que la impresora
está lista para aceptarlos. Este procesamiento se denomina spooling. Si la
impresora necesita todos los resultados de un trabajo antes de empezar a
imprimir, pero el sistema de spooling llena el espacio disponible con un resultado
parcialmente completo, entonces puede ocurrir un bloqueo mutuo. Esto ocurre
como se describe a continuación.
Considere que falta una hora para entregar el gran proyecto en un curso de
computación. Veintiséis programadores desesperados están ocupados
escribiendo los últimos detalles y, con sólo unos minutos disponibles, todos
emiten comandos de impresión. El spooler recibe las páginas una por una de
cada uno de los estudiantes, pero las recibe por separado; varias páginas uno,
páginas dos, etc. La impresora está lista para imprimir el primer documento
completo que obtiene, pero a medida que el spooler consulta sus archivos, tiene
la primera página, pero no la última de ninguno de ellos. ¡Lástima!, el spooler
está lleno de resultados parcialmente completados, de modo que ya no es
posible aceptar otras páginas, pero ninguno de los trabajos puede imprimirse
(con lo cual se liberaría el espacio de su disco) porque la impresora sólo acepta
archivos de salida completos.
6
Caso 6: Bloqueos en una red
7
Condiciones para bloqueos
En cada uno de los siete casos, el bloqueo mutuo (o bloqueo activo) implicó la
interacción de varios procesos y recursos, pero cada bloqueo mutuo estaba
precedido por la ocurrencia simultánea de cuatro condiciones que el sistema
operativo (u otros sistemas) puede reconocer: exclusión mutua, retención de
recursos, inexistencia de expropiabilidad y espera circular. Es importante
recordar que cada una de estas cuatro condiciones es necesaria para que el
sistema operativo funcione sin contratiempos. Ninguna de ellas puede
removerse fácilmente sin ocasionar problemas al funcionamiento global del
sistema. Por consiguiente, el sistema necesita reconocer la combinación de
condiciones antes que ocurran y amenacen con provocar un bloqueo del
sistema.
Escenario 1
8
Escenario 2
Escenario 3
El tercer escenario se muestra en la tabla 5.3. Como se muestra en la fi gura
5.10, los recursos se liberan antes que ocurra un bloqueo mutuo.
9
Estrategias para manejo de bloqueos
Como muestran estos ejemplos, las solicitudes y las liberaciones se reciben en
orden impredecible, lo cual hace bastante difícil diseñar una política a prueba de
errores. En general, los sistemas operativos usan una de tres estrategias para
ocuparse de los bloqueos mutuos:
• Prevenir la ocurrencia de una de las cuatro condiciones (prevención).
• Evitar el bloqueo mutuo si se vuelve probable (evasión).
• Detectar el bloqueo mutuo cuando ocurre y recuperarse de éste con elegancia
(detección).
Prevención
Para prevenir un bloqueo mutuo, el sistema operativo debe eliminar una de las
cuatro condiciones necesarias, una tarea complicada por el hecho de que la
misma condición no puede eliminarse en cada recurso.
La exclusión mutua es necesaria en cualquier sistema de cómputo porque
algunos recursos como memoria, CPU o dispositivos dedicados deben asignarse
de manera exclusiva a un usuario a la vez. En el caso de los dispositivos de E/S,
como impresoras, la exclusión mutua puede evitarse por medio de spooling, que
permite que el resultado de muchos trabajos se almacene en archivos spool
temporales al mismo tiempo, y luego cada archivo de salida completo se
selecciona para impresión cuando el dispositivo está listo.
La retención de recursos, donde un trabajo detiene un recurso mientras espera
por otro que aún no está disponible, puede soslayarse forzando a cada trabajo a
solicitar, en el momento de su creación, todos los recursos que necesita para su
ejecución hasta el final.
Es posible evitar la no expropiación al permitir que el sistema operativo libere
recursos de trabajos. Esto puede lograrse si es posible guardar y restituir
fácilmente el estado del trabajo, como cuando el trabajo es expropiado en un
entorno round Robin o una página se intercambia a almacenamiento secundario
en un sistema con memoria virtual.
Este esquema por “orden jerárquico” elimina la posibilidad de una espera circular
y garantiza así la remoción de bloqueos mutuos. No requiere que los trabajos
planteen sus necesidades de antemano, pero sí que anticipen el orden en que
solicitarán recursos.
Evasión
Aun si el sistema operativo no puede remover una de las condiciones para
bloqueo mutuo, puede evitar una si el sistema conoce de antemano la secuencia
de solicitudes asociadas con cada uno de los procesos activos. Hay por lo menos
una secuencia de asignación de recursos que permite continuar al trabajo sin
que éste llegue a bloquearse.
Uno de tales algoritmos fue propuesto por Dijkstra en 1965 para regular la
asignación a fin de evitar bloqueos mutuos. El algoritmo del banquero se basa
en un banco con una cantidad de capital fija que opera según los siguientes
principios:
• A ningún cliente se le otorga un préstamo que exceda el capital total del banco.
• A todos los clientes se les otorga un límite de crédito máximo cuando abran una
cuenta.
• A ningún cliente se le permite un crédito superior al límite.
• La suma de todos los préstamos no debe exceder el capital total del banco.
10
El sistema operativo debe estar seguro de nunca satisfacer una demanda que lo
mueva de un estado seguro a uno inseguro. En consecuencia, a medida que se
cumplen las solicitudes de un usuario, el sistema operativo debe identificar el
trabajo con el menor número de recursos restantes y asegurarse que el número
de recursos restantes siempre sea igual, o mayor que, al número necesario para
que este trabajo se ejecute hasta su terminación. El sistema operativo debe
bloquear todas las solicitudes que pueden colocar el estado seguro en peligro
hasta que puedan acomodarse con seguridad.
Aunque el algoritmo del banquero se ha usado para evitar bloqueos mutuos en
sistemas con pocos recursos, no siempre es práctico para la mayor parte de los
sistemas por las siguientes razones:
• A medida que los trabajos entran al sistema, deben pronosticar el número
máximo de recursos necesarios. Como ya se mencionó, esto no es práctico con
sistemas interactivos.
• El número de recursos totales para cada clase debe permanecer constante. Si
un dispositivo se descompone y de repente queda indisponible, el algoritmo no
funciona (el sistema ya puede estar en un estado inseguro).
• El número de trabajos debe permanecer fijo, algo imposible en sistemas
interactivos donde el número de trabajos activos cambia constantemente.
• El procesamiento extra en que se incurre por la ejecución del algoritmo de
evasión pueden ser bastante altos cuando hay muchos trabajos activos y
muchos dispositivos porque debe ser invocado por cada una de las solicitudes.
• Los recursos no se utilizan bien porque el algoritmo supone el peor caso y,
como resultado, mantiene indisponibles recursos vitales a fi n de protegerse
contra estados inseguros.
• Como resultado de la utilización deficiente, la planificación sufre y los trabajos
se mantienen en espera de asignación de recursos. Un flujo estable de trabajos
solicitando nuevos recursos puede ocasionar el aplazamiento indefinido de un
trabajo más complicado que requiere muchos recursos.
Detección
Las gráficas dirigidas presentadas antes en este capítulo mostraron cómo la
existencia de una espera circular indicaba un bloqueo mutuo, por lo que resulta
razonable concluir que los bloqueos mutuos de recursos pueden detectarse al
construir gráficas dirigidas de recursos y buscar ciclos. A diferencia del algoritmo
de evasión, que debe ejecutarse cada que hay una solicitud, el algoritmo usado
para detectar circularidad puede ejecutarse siempre que sea conveniente; cada
hora, una vez al día, sólo cuando el operador observa que se ha deteriorado la
salida, o cuando un usuario enojado se queja.
El algoritmo de detección puede explicarse usando gráficas de recursos dirigidas
y “reduciéndolas”. Se empieza con un sistema en uso, como se muestra en la fi
gura 5.12a. Los pasos para reducir la gráfica son los siguientes:
1. Encontrar un proceso que esté usando un recurso en ese momento y no esté
esperando por ninguno. Este proceso puede removerse de la gráfica (al
desconectar el vínculo que va del recurso al proceso, como P3 en la fi gura 5.12b)
y el recurso puede devolverse a la “lista disponible”. Esto es posible porque el
proceso terminará finalmente y regresará el recurso.
2. Encontrar un proceso que esté esperando sólo por clases de recursos que no
hayan sido asignadas por completo (como P2 en la fi gura 5.12c). Este proceso
no contribuye a ningún bloqueo mutuo porque termina por obtener el recurso que
11
está esperando, termina su trabajo y regresa el recurso a la “lista disponible”,
como se muestra en la fi gura 5.12c.
3. Regresar al paso 1 y continuar con los pasos 1 y 2 hasta que se hayan
eliminado todas las líneas que conectan los recursos con los procesos, llegando
finalmente a la etapa que se muestra en la fi gura 5.12d.
Si quedan algunas líneas, esto indica que la solicitud del proceso en cuestión no
puede cumplirse y que existe un bloqueo mutuo. La fi gura 5.12 ilustra un sistema
en que hay tres procesos: P1, P2 y P3, y tres recursos: R1, R2 y R3 que no están
bloqueados mutuamente.
La figura 5.12 muestra las etapas de la reducción de una gráfica desde a), el
estado original. En b), el vínculo entre P3 y R3 puede eliminarse porque P3 no
está esperando que termine ninguno de los otros recursos, de modo que R3 se
libera y asigna a P2 (paso 1). En c) es posible eliminar los vínculos entre P2 y
R3 y entre P2 y R2 porque P2 tiene todos sus recursos asignados y puede
continuar su ejecución hasta terminar, y luego R2 puede asignarse a P1.
Finalmente, en d), los vínculos entre P1 y R2 y entre P1 y R1 pueden eliminarse
debido a que P1 tiene todos sus recursos solicitados y puede terminar
exitosamente. En consecuencia, la gráfica se ha resuelto completamente. Sin
embargo, la fi gura 5.13 muestra una situación muy semejante que está
bloqueada debido a una diferencia fundamental: P2 está vinculado con R1.
12
Recuperación
Una vez que se ha detectado un bloqueo mutuo, es necesario desentrañarlo y el
sistema devuelto a la normalidad lo más rápido posible. Hay varios algoritmos de
recuperación, aunque todos tienen un rasgo en común: todos requieren por lo
menos una víctima, un trabajo prescindible que, al ser eliminado del bloqueo
mutuo, libera el sistema. Desafortunadamente para la víctima, la eliminación
suele requerir que el trabajo se reinicie desde el principio o desde un punto medio
conveniente.
El primer y más simple método de recuperación, así como el más drástico,
consiste en terminar todos los trabajos activos en el sistema y reiniciarlos desde
el principio.
El segundo método consiste en terminar sólo los trabajos implicados en el
bloqueo mutuo y pedir a sus usuarios que los vuelvan a presentar.
El tercer método consiste en identificar cuáles trabajos están implicados en el
bloqueo mutuo y terminarlos uno por uno, comprobando si el bloqueo mutuo se
ha eliminado después de cada eliminación, hasta que se haya resuelto el
bloqueo mutuo. Una vez que se ha liberado el sistema, se permite que los
trabajos restantes completen su procesamiento y más tarde los trabajos
detenidos vuelven a iniciarse desde el principio.
El cuarto método sólo puede ponerse en efecto si el trabajo mantiene un registro,
una instantánea, de su avance de modo que sea posible interrumpirlo y luego
continuar sin iniciar de nuevo desde el principio de su ejecución.
El quinto método de la lista selecciona un trabajo sin bloqueo mutuo, expropiando
los recursos que mantiene, y asignándolos a un proceso con bloqueo mutuo de
manera que éste pueda reanudar su ejecución, rompiéndose el bloqueo mutuo.
El sexto método, impide la entrada de nuevos trabajos al sistema, lo cual permite
que los trabajos no bloqueados terminen su ejecución, de modo que liberan sus
recursos. Por último, con menos trabajos en el sistema, la competencia por
recursos está restringida, de modo que los procesos bloqueados obtienen los
recursos que necesitan para completar su ejecución. Este método es el único
presentado aquí que no depende de una víctima, y no se garantiza que trabaje
a menos que el número de recursos disponibles exceda los necesarios para que
por lo menos tenga uno de los trabajos bloqueados para su ejecución.
Es necesario considerar varios factores para escoger a la víctima que tenga el
efecto menos negativo sobre el sistema. Los más comunes son:
• La prioridad del trabajo en consideración: los trabajos con alta prioridad suelen
ser intocables.
• Tiempo de CPU usado por el trabajo: los trabajos próximos a su
completamiento suelen dejarse solos.
• El número de otros trabajos que pueden resultar afectados si este trabajo se
escoge como la víctima.
Además, los trabajos que funcionan con bases de datos también merecen un
tratamiento especial porque una base de datos que está actualizada sólo en
parte es sólo parcialmente correcta. En consecuencia, los trabajos que están
modificando datos no deben escogerse para su terminación porque podría
ponerse en peligro la consistencia y validez de los datos.
13
Inanición
Hasta el momento nos hemos centrado en bloqueos mutuos, el resultado de la
asignación liberal de recursos. En el extremo opuesto de la inanición, el resultado
de la asignación conservadora de recursos donde a un solo trabajo se le impide
su ejecución porque se mantiene en espera de recursos que nunca se vuelven
disponibles. Para ilustrar esto, Dijkstra introdujo el caso de “los filósofos
cenando” en 1968. Cinco filósofos están sentados alrededor de una mesa
redonda, cada uno en profunda reflexión, y en el centro de la mesa hay un tazón
con espagueti accesible para todos. Sobre la mesa hay tenedores: uno entre
cada par de filósofos,
Cuando se sientan a cenar, el filósofo 1 (P1) es el primero en tomar los dos
tenedores (F1 y F5) a cada lado del plato y empieza a comer. Inspirado por su
colega, el filósofo 3 (P3) hace lo mismo, usando F2 y F3. Ahora, el filósofo 2 (P2)
decide empezar a cenar, pero no puede hacerlo porque no hay tenedores
disponibles: F1 ha sido asignado a P1 y F2 ha sido asignado a P3, y el único
tenedor que queda sólo pueden usarlo P4 o P5. Así (P2), debe esperar. Pronto,
P3 termina de cenar, coloca sus dos tenedores sobre la mesa y reanuda su
reflexión. El tenedor que está a su lado (F2), que ahora está libre, ¿debe
asignarse al filósofo hambriento (P2)? Aunque es tentador, hacerlo sentaría un
mal precedente porque si a los filósofos se les permite atar recursos con la única
esperanza de que el otro recurso requerido esté disponible, la cena podría llegar
fácilmente a un estado inseguro; sólo sería cuestión de tiempo antes que cada
filósofo cogiera un simple tenedor y nadie podría cenar. Así, los recursos se
asignan a los filósofos sólo cuando ambos tenedores estén disponibles al mismo
tiempo.
14
implementar un algoritmo diseñado para detectar trabajos privados de recursos
que sigue la pista de cuánto tiempo ha esperado recursos un trabajo.
Este algoritmo debe supervisarse estrechamente: si se aplica muy a menudo,
entonces nuevos trabajos serán bloqueados con demasiada frecuencia y
entonces disminuye el resultado. Si no se aplica a menudo, entonces suficientes
trabajos en inanición permanecen en el sistema durante un periodo inaceptable.
Conclusión
Todo sistema operativo debe asignar dinámicamente un número limitado de
recursos, a la vez que evitar los dos extremos de bloqueo mutuo e inanición. En
este capítulo analizamos varios métodos para tratar con los bloqueos mutuos:
prevención, evasión, detección y recuperación. Los bloqueos mutuos pueden
prevenirse al no permitir que las cuatro condiciones de un bloqueo ocurran en un
sistema al mismo tiempo. Al eliminar por lo menos una de las cuatro condiciones
(exclusión mutua, retención de recursos, ausencia de expropiación y espera
circular), el sistema puede permanecer libre de bloqueos mutuos. Como hemos
visto, la desventaja de una política preventiva es que cada una de estas
condiciones es vital para diferentes partes del sistema por lo menos parte del
tiempo, de modo que los algoritmos de prevención son complicados y
rutinariamente su ejecución implica procesamiento extra elevado. Los bloqueos
mutuos pueden evitarse al identificar claramente estados seguros y estados
inseguros, y requiriendo al sistema que mantenga suficientes recursos en
reserva para garantizar que todos los trabajos activos en el sistema puedan
ejecutarse hasta su terminación. La desventaja de una política de evasión es que
los recursos del sistema no se asignan a todo su potencial. Si un sistema no
atiende prevención o evasión, entonces debe prepararse para detectar y
recuperarse de los bloqueos que puedan ocurrir. Desafortunadamente, esta
opción suele depender de la selección de por lo menos una “víctima”: un trabajo
que debe terminarse antes de completar su ejecución y reiniciarse desde el
principio.
Glosario
Bloquear: Técnica usada para garantizar la integridad de los datos en una base
de datos a través de la cual el usuario bloquea a todos los otros usuarios mientras
trabaja con la base de datos.
Bloqueo mutuo: Problema que ocurre cuando los recursos necesarios por
algunos trabajos para terminar su ejecución están retenidos por otros trabajos
que, a su vez, esperan que otros recursos estén disponibles. También se
denomina abrazo de la muerte.
Bloqueo activo: Sistema bloqueado donde dos (o más) procesos bloquean
continuamente el avance del orden sin hacer ningún avance en sí. Es semejante
a un bloqueo mutuo excepto que ninguno de los procesos está bloqueado o en
espera; ambos se encuentran en un estado de cambio continuo.
Competencia: Problema de sincronización entre dos procesos que compiten por
el mismo recurso.
Detección: Proceso de examinar un estado de un sistema operativo para
determinar si existe un bloqueo.
Espera circular: Una de cuatro condiciones para que ocurran bloqueos, a través
de la cual cada proceso implicado está esperando por un recurso retenido por
15
otro proceso; cada proceso está bloqueado y no puede continuar, resultando en
un bloqueo mutuo.
Estado inseguro: Situación en que el sistema tiene muy pocos recursos
disponibles para garantizar el completamiento de por lo menos un trabajo en
ejecución en el sistema. Puede conducir a bloqueo mutuo.
Estado seguro: Situación en que el sistema cuenta con suficientes recursos
para garantizar la terminación de por lo menos un trabajo en ejecución en el
sistema.
Evasión: Estrategia dinámica para evitar bloqueos mutuos que intenta asegurar
que los recursos nunca se asignen de manera que pongan un sistema en un
estado inseguro.
Exclusión mutua: Una de cuatro condiciones para bloqueo mutuo donde sólo a
un proceso se le permite tener acceso a un recurso.
Gráfica dirigida: Modelo gráfico que representa varios estados de asignación
de recursos.
Inanición: Resultado de la asignación conservadora de recursos donde se
previene la ejecución de un solo trabajo porque se mantiene esperando recursos
que nunca están disponibles.
Prevención: Estrategia de diseño para un sistema operativo donde los recursos
son controlados de modo que algunas de las condiciones necesarias para el
bloqueo no se cumplan.
Recuperación: Pasos que deben emprenderse, cuando se detecta un bloqueo
mutuo, al romper el círculo de procesos en espera.
Retención de recursos: Una de cuatro condiciones para bloqueo mutuo donde
cada proceso rehúsa liberar los recursos que posee hasta que se completa su
ejecución, aun cuando no los esté usando porque está esperando otros recursos.
Sin expropiación: Una de cuatro condiciones para bloqueo mutuo donde a un
proceso se le permite retener los recursos mientras está esperando otros
recursos para completar su ejecución.
Sincronización de procesos: 1) La necesidad de algoritmos para resolver
conflictos entre procesadores en un entorno multiprocesamiento; o 2) la
necesidad de asegurar que los eventos ocurran en el orden idóneo si han sido
efectuados por varios procesos.
Spooling: Técnica desarrollada para acelerar la E/S al recolectar en un archivo
de disco ya sea entrada recibida desde dispositivos de entrada lentos o salida
dirigida a dispositivos de salida lentos, como impresoras.
Víctima: Trabajo prescindible que se escoge en un sistema bloqueado a fi n de
proveer más recursos a los trabajos en espera y resolver el bloqueo mutuo.
Bibliografía
Ann McIver McHoes, Ida M. Flynn. (2011). Sistemas Operativos.
Mexico, D.F.: CENGAGE Learning.
16