Bajar - Emilio JA

Anuncio
Diseño e Implementación de un Planificador para un Sistema de
Virtualización basado en Minix
Prinsich Bernz, Emilio
Quaglia, Constanza
Director: Pessolani,Pablo
Universidad Tecnológica Nacional Facultad Regional Santa Fe
Abstract
El sistema operativo de microkernel Minix [1]
brinda un entorno adecuado para desarrollos
experimentales, incluso para incorporarle funciones
de hipervisor y poder gestionar así Máquinas
Virtuales (del inglés VMs). El presente trabajo fue
realizado en el marco de la catedra “Diseño e
Implementación de Sistemas Operativos” del 4°
nivel de la Carrera de Ingeniería en Sistemas de
Información. El mismo busca diseñar y evaluar un
planificador jerárquico que permita seleccionar, en
primera instancia, la máquina virtual en función de
los recursos disponibles en una reserva y luego el
próximo proceso a ejecutar de esa máquina virtual.
El proyecto propone un algoritmo de planificación
de tipo token bucket (cubeta de fichas) [2] para la
selección de las máquinas virtuales y administración
de los tiempos de CPU para cada una.
Palabras Clave
Minix, Sistema Operativo, hipervisor, planificación
de procesos, Máquinas Virtuales, paravirtualización,
Token Bucket.
Introducción
La virtualización [3] consiste en un
software que emula al hardware y puede
ejecutar programas como si fuera una
computadora real. Dicho software se
denomina hipervisor. Esta tecnología
permite que una sola computadora contenga
múltiples máquinas virtuales (VMs),
denominadas huéspedes (en inglés Guests).
En particular, la paravirtualización es un
tipo de virtualización en la cual el código
fuente de los huéspedes es modificado de
manera que en lugar de ejecutar
instrucciones sensibles que provocan
excepciones que son atrapadas por el
hipervisor,
realicen
explícitamente
llamadas al hipervisor (hypervisor calls).
Minix es un OS basado en microkernel y
estratificado, desarrollado por Andrew
Tanenbaum con fines académicos. En
Minix, todos los procesos y tareas son
aislados y se comunican a través de
mensajes. El microkernel se encarga de
planificar procesos, de las transiciones entre
estados,
de
atender
interrupciones,
excepciones y fallos, y de la transferencia
de mensajes. Compartiendo el espacio de
direcciones del microkernel se encuentran
dos procesos con privilegios especiales: la
CLOCK task y la System task (SYSTASK).
La CLOCK task es el proceso que se
encarga de gestionar aquellas operaciones
relacionadas con el reloj y el tiempo del
sistema. La SYSTASK es el proceso
representante del kernel que brinda
servicios a servidores y tareas a través de
una serie de llamadas al kernel (kernel
calls) para que puedan realizarse
operaciones privilegiadas.
En este sentido, el microkernel de Minix
posee un mecanismo similar al de la
paravirtualización. Se puede afirmar que
este actúa como hipervisor paravirtualizado
de una única VM.
El propósito de este artículo es exponer el
trabajo realizado acerca del diseño,
modelado, implementación y evaluación de
un planificador jerárquico para un sistema
de virtualización basado en Minix,
denominado MHyper. MHyper puede dar
soporte a múltiples VMs con Minix como
huesped.
Se trabajó sobre un algoritmo de tipo token
bucket para planificar las VMs y sus
respectivos procesos.
Elementos del Trabajo y metodología
Contexto
Una máquina virtual debe contar con las
siguientes características:



Duplicado: Debería comportarse de
forma idéntica a la máquina real,
excepto por la existencia de menos
recursos disponibles y diferencias de
temporización al tratar con dispositivos;
Aislamiento: Se pueden ejecutar varias
VMs sin interferencias. El aislamiento
debe ser:
o De rendimiento: El rendimiento
de una VM no debe afectar al de
otra VM;
o De fallo: Un fallo de una VM no
debe provocar ningún efecto en
las demás;
o De seguridad: No debe ser
posible acceder a ningún recurso
de una VM desde otra;
Eficiencia: La VM debería ejecutarse a
una velocidad cercana a la del HW real.
Existen varios tipos de virtualización:



Hipervisor tipo I (full virtualization): El
monitor se ejecuta directamente sobre el
hardware en modo kernel y los
huéspedes sobre el monitor en modo
usuario;
Hipervisor tipo II (indirecto): El
monitor se ejecuta sobre un OS en
modo usuario y los huéspedes sobre el
monitor;
Paravirtualización:
Consiste
en
modificar el código fuente del OS
huésped para que en vez de ejecutar
instrucciones
sensibles
realicen
llamadas al hipervisor. De esta forma se
logra un rendimiento cercano a tener
máquinas reales.
Minix es un sistema operativo de tipo
cliente/servidor basado en microkernel. En
su versión 3 los procesos se dividen en 4
capas, como se muestra en la figura 1. Los
procesos de las capas 2, 3 y 4 se ejecutan en
modo usuario y comprenden las siguientes
categorías:



Procesos de usuarios;
Procesos servidores;
Drivers o tasks.
Debido a la similitud mencionada
anteriormente entre Minix y un sistema de
paravirtualización, el proyecto MHyper
busca adaptar a Minix para convertir a la
SYSTASK de Minix en un hipervisor
paravirtualizado de múltiples VMs.
Figura 1. Estructura de Minix 3
MHyper posee una arquitectura similar a la
de los contenedores de Linux [4]. La VM0
es una VM privilegiada que permite
administrar las demás VMs. Al iniciarse,
posee todos los recursos del sistema, por lo
que si no se inicia ninguna VM trabaja de
igual forma que un Minix normal.
Fuera del espacio del microkernel, se
añadió un nuevo servidor para administrar
VMs llamado Virtual Machine Manager
(VMM), exclusivo de la VM0. También se
creó una herramienta de administración
para cargar, iniciar, detener, reanudar y
terminar las VMs utilizando las llamadas al
sistema atendidas por VMM.
Las demás VMs son versiones de Minix
que no ejecutan su propio kernel, pero
poseen todos las demás tareas, drivers y
servidores. Para mantener el aislamiento de
seguridad, recursos y fallos, el espacio de
direcciones de memoria asignado a una VM
no puede ser accedido por ninguna otra,
incluyendo la VM0. El Process Manager
(PM) de la VM0 no tiene bajo su gestión el
rango de direcciones de memoria de una
VM hasta que ésta termina. Además, la
SYSTASK mantiene la rango del espacio
de direcciones para cada VM para poder
controlar que las llamadas al kernel
solicitadas por un proceso operan dentro del
área de memoria asignado a su VM (es
decir, la copia de bloques de memoria entre
procesos). La arquitectura de procesos de
MHyper se muestra en la figura 2.
Figura 2. Arquitectura de MHyper
La mayor parte de este proyecto ya fue
implementado, pero el planificador con el
que cuenta (heredado de Minix) utiliza un
algoritmo de planificación por prioridades
que no distingue entre procesos de
diferentes VMs. Por lo tanto, si los procesos
de una VM cambian su prioridad a una
mayor, éstos serán privilegiados por el
planificador frente a los procesos de otras
VMs. Es necesario entonces implementar
un mecanismo que administre de forma
justa
los
recursos
disponibles,
y
particularmente el tiempo de CPU, entre los
procesos de las diferentes VMs.
Para poder llevar adelante esta tarea, es
necesario reemplazar el actual planificador
de MHyper. En el planificador de Minix
cada proceso tiene una prioridad inicial,
relacionada a la arquitectura que se muestra
en la figura 1, donde los procesos de capas
inferiores tienen mayor prioridad que los de
los superiores.
El planificador mantiene 16 colas de
procesos en estado de listo, una por
prioridad, como se muestra en la figura 3.
Dentro de cada cola se aplica un algoritmo
de round robin.
Cuando a un proceso que está ejecutando se
le termina el quántum, es movido al final de
la cola. Cuando un proceso pasa de
bloqueado a listo, es colocado al principio
de la cola. Finalmente, cuando un proceso
se bloquea o recibe una señal kill es
removido de la cola.
El algoritmo de planificación consiste
entonces en encontrar la cola de mayor
prioridad no vacía y elegir el primer
proceso de esa cola. El proceso IDLE
siempre está listo y es el de menor
prioridad, es decir que será elegido cuando
las otras colas se encuentre vacías.
Para encolar y desencolar procesos, Minix
se basa en las funciones enqueue() y
dequeue(). Enqueue() coloca un proceso en
la cola que corresponda y llama a
pick_proc(), que determina cuál será el
próximo proceso a ejecutar.
El planificador de Minix sólo distingue
procesos. Sin embargo, para planificar un
sistema de virtualización es necesario
distinguir a qué VM pertenece cada
proceso. En caso contrario, podría darse un
escenario como el que se describe a
continuación. Supóngase que se tienen dos
VMs, corriendo procesos con igual
prioridad. VM1 y VM2 tienen 8 y 2
procesos
en
estado
de
listo,
respectivamente. En este caso, la VM1 tiene
una probabilidad del 80% de ocupar la
CPU, y la VM2 el 20%, y cuantos más
procesos ponga la VM1 a ejecutar, mayor
será la proporción de CPU que obtendrá.
Figura 3. Cola de prioridades de Minix (obtenida en [1])
Este comportamiento afecta el aislamiento
de rendimiento que toda VM debería tener.
Por otro lado, tampoco es suficiente con
distinguir solamente las diferentes VMs. Un
algoritmo que no distinga entre los tipos
procesos, podría por ejemplo planificar un
proceso de usuario de una VM, mientras
está en espera un driver con restricciones
temporales correspondiente a otra VM.
Generalmente los drivers son procesos
sensibles al tiempo, con lo cual deberían
mantener su prioridad frente al resto de los
procesos con el fin de reducir su tiempo de
respuesta.
Solución propuesta
Para cumplir con las condiciones de
aislamiento de rendimiento exigidas por la
virtualización, no es suficiente con
planificar procesos. Se propone adoptar un
algoritmo jerárquico que tenga en
consideración los diferentes tipos de
procesos y los recursos consumidos por
todos los procesos de cada VM.
En primer lugar, se divide a los procesos en
tres grupos:



Kernel (incluyendo SYSTASK y
CLOCK TASK): pertenecientes a la
capa 1 de la arquitectura de Minix;
Tareas (tasks): aquellos procesos
pertenecientes a la capa 2 que están
relacionados con la gestión de
dispositivos y por lo tanto tienen
restricciones temporales;
Procesos: pertenecientes a las capas
3 y 4.
Luego, a cada VM se le asigna una cubeta
de fichas o token bucket de tamaño dado.
Periódicamente esta cubeta es completada
de fichas (tokens) por el microkernel. Una
ficha simboliza un tick del timer de
MHyper. Cada vez que se produce un tick,
CLOCK task sustrae una ficha de la cubeta
de la VM a la que pertenece el proceso
interrumpido. Este mecanismo permite
limitar el tiempo de ejecución de procesos
de cada VM a fin de lograr una distribución
más equitativa de la CPU.
Cuando se inicia el sistema, arranca VM0 a
la que se le asignan 256 tokens. Al iniciar
una VM, el sistema extrae una cantidad
vm_bsize de tokens de la VM0 y se los
asigna a dicha VM. El tamaño de bucket
vm_bsize se obtiene como parámetro de
inicio y no puede superar la cantidad de
tokens que tenga la VM0 disponibles en ese
momento. Cuando una VM se detiene, estos
tokens se devuelven a la VM0.
La asignación de tokens a la VM0 se realiza
con el único fin de ser distribuidos a las
demás VMs. Por ser una VM con
características particulares, la VM0 está
exenta del control del consumo de tokens,
ya que sus procesos son prioritarios para el
funcionamiento del sistema.
Cuando el planificador es invocado, la
selección de VMs se realiza rastreando las
diferentes colas de prioridad buscando
procesos de acuerdo al siguiente orden:






Tareas y procesos de la VM0;
Tareas de VMs con tokens en sus
buckets;
Procesos de VMs con tokens en sus
buckets;
Tareas de VMs con sus buckets
vacíos;
Procesos de tareas con sus buckets
vacíos;
IDLE.
Cada cierto tiempo el sistema se refresca y
todos los buckets se vuelven a su estado
inicial. El tiempo de refresco 𝑡_𝑟𝑒𝑓𝑟𝑒𝑠𝑐𝑜 se
calcula como la cantidad de ticks de
temporizador que tomaría consumir todos
los tokens en el sistema:
𝑡_𝑟𝑒𝑓𝑟𝑒𝑠𝑐𝑜 =
∑
𝑣𝑚_𝑏𝑠𝑖𝑧𝑒[𝑖]
𝑖 ∈ 𝑉𝑀_𝑅𝑈𝑁𝑁𝐼𝑁𝐺
Metodología de trabajo
En primera instancia se optó por realizar
una serie de simulaciones del algoritmo
propuesto y otras variantes, a fin de
evaluarlas y compararlas.
Para ello se utilizó un software de
simulación de procesos llamado SimSo[5]
que permite realizar un prototipo de
planificador, definir procesos, simularlos y
obtener estadísticas del tiempo de
procesamiento y rendimiento del hardware.
Dichas
simulaciones
arrojaron
una
distribución más equitativa de la CPU entre
las VMs, como se puede observar en la
figura 4.
Figura 4. Distribución de CPU
Además se observó que entregándole más
tokens a una VM en particular se le estaría
entregando mayor prioridad que a las demás
como se observa en la figura 5, por lo que
se concluyó que el algoritmos no solo daba
equitatividad sino también la posibilidad de
dar diferentes prioridades a las VMs.
servers, usuario), se agregaron nuevos bits
en un campo del descriptor (p_misc_flags)
de procesos.
Luego se procedió a modificar el algoritmo
de planificación, particularmente la función
pick_proc(), encargada de elegir el próximo
proceso listo para ejecutarse (proc_ptr
apunta al descriptor de ese proceso). El
algoritmo realiza múltiples búsquedas en
las colas de prioridad de procesos listos
(ready_q). A continuación se presenta el
pseudocódigo
que
describe
su
funcionamiento:
pick_proc(){
//Tareas o procesos de la VM0
foreach q in ready_q:
proc_ptr = head[q];
if(proc_ptr != NULL &&
proc_ptr->vm == 0)
return;
//Tareas de las VMs con tokens
foreach q in ready_q:
proc_ptr = head[q];
if(proc_ptr != NULL &&
proc_ptr->level == TASK &&
proc_ptr->vm->tokens > 0)
return;
//Procesos de las VMs con tokens
foreach q in ready_q:
proc_ptr = head[q];
if(proc_ptr != NULL &&
proc_ptr->vm->tokens > 0)
return;
Figura 5. Priorización de las VMs
Implementación
Con los resultados positivos durante la
etapa de simulación se inició la
implementación del nuevo algoritmo.
En primer lugar, se modificó la estructura
de datos que describe las VMs. A dicha
estructura se le agregaron los campos
vm_bucket y vm_tokens que corresponden
al tamaño total del bucket y la cantidad
actual de tokens del mismo. El vm_bucket
es pasado a la VM como un parámetro de
inicio. Para que el algoritmo pueda
distinguir entre los diferentes tipos o
niveles (level) de procesos (kernel, tareas,
//Tareas de las VMs sin tokens
foreach q in ready_q:
proc_ptr = head[q];
if(proc_ptr != NULL &&
proc_ptr->level == TASK)
return;
//Procesos de las VMs sin tokens
foreach q in ready_q:
proc_ptr = head[q];
if(proc_ptr != NULL)
return;
//IDLE
proc_ptr = IDLE;
return;
Se puede observar que primero se buscan
procesos o tareas correspondientes a la
VM0. Seguido de estas se buscan tareas de
aquellas VMs que posean tokens. De no
haber ninguna se buscan procesos en la
misma condición.
Luego, para evitar la posibilidad de que el
procesador este ocioso hasta el próximo
refresco habiendo tareas o procesos
pendientes, se da la posibilidad de
ejecución a los procesos de VMs que no
posean tokens.
Por último, si no hay ningún proceso listo
para ejecutarse, se ejecuta IDLE.
La
otra
función
modificada
fue
clock_handler(),que se ejecuta en cada tick
del temporizador.
En primer lugar se encarga de hacer el
refresco de todos los bucket de las VMs.
Cada vez que se inicializa una VM se
actualiza la variable global t_refresco, que
contiene el tiempo de refresco de los
buckets (vm_bsize). Cada t_refresco ticks
todos los buckets son llenados nuevamente,
independientemente de la situación de la
VM.
t_counter++;
if(t_counter >= t_refresco)
t_counter = 0;
for(i = 0; i < NR_VMS; i++)
VM[i].vm_tokens=VM[i].vm_bsize;
En segundo lugar, se encarga de restarle un
token a la VM que se encuentra en
ejecución en ese instante.
clock_handler(), junto con pick_proc()
conforman el núcleo de la implementación
del
algoritmo de planificación token
bucket.
Para poder verificar que el algoritmo refleja
similares resultados a los obtenidos en las
simulaciones previas, se implementó un
sistema de métricas para evaluar el
dessempeño del algoritmo.
Para registrar las mediciones se creó un
vector de estructuras vm_metric.
struct vm_metric{
clock_t timestamp;
int kernel[NR_VMS];
int task[NR_VMS];
int proc[NR_VMS];
int idle;
};
Cada 300 ticks de reloj (5 segundos dado
que se producen 60 ticks/s) se almacena en
un elemento del vector la cantidad de ticks
en las que clock_handler() detectó en
ejecución un tipo de proceso dado de una
VM (kernel, task y proc) y un timestamp
del momento en que se registra esta
información.
Minix ofrece la facilidad de poder acceder a
cualquier dirección de memoria a través de
su sistema de archivos. El dispositivo
/dev/kmem es un dispositivo que representa
toda la memoria del computador. Se utilizó
esta facilidad para acceder desde un
programa de usuario al vector de
registración de métricas y así poder
recolectar y registrar en un archivo de disco
los datos de medición.
Resultados
Se realizó una serie de pruebas utilizando
MHyper con ambos planificadores, el de
Minix y el propuesto. Se crearon dos
programas en C, uno para ser ejecutado en
el hipervisor (testVM0.c) y otro para ser
ejecutado en las VMs (test.c). El programa
que se ejecuta en el hipervisor simplemente
ejecuta un bucle infinito de manera de
ocupar la CPU al 50%. El programa que se
ejecuta en las VMs también es un bucle
infinito para ocupar la CPU y además lee
datos de un archivo y los escribe en otro.
Con estos programas se corrieron diversos
escenarios, con la finalidad de observar el
comportamiento y comparar ambos
planificadores.
Priorización de la VM0
En primer lugar, se puso a correr testVM0
en la VM0. Luego de unos minutos se
inician VM2 y VM3, las cuales ejecutan el
proceso test. En este caso, como muestran
la figura 6, el comportamiento cuando sólo
está corriendo la VM0 es igual para los dos
planificadores,
dicha
VM
ocupa
aproximadamente el 50% de la CPU. Sin
embargo, cuando se inician las otras dos, el
planificador de Minix, al no distinguir a qué
VM pertenece cada proceso, le da mayor
importancia a la VM2 que a la VM0. En el
planificador de token bucket esto no ocurre,
sino que se atiende a las VMs, pero sin
relegar a la VM0 (figura 7).
Figura 6. Priorización VM0 sin VM
Priorización de las VMs
El tercer conjunto de benchmarks consitió
en ejecutar 2 VMs con diferentes
asignaciones de tokens: 32 y 32, 64 y 64,
128 y 128 y 32 y 128, respectivamente.
Como se puede ver en la figura 9, se pudo
comprobar que el uso de CPU es
proporcional a la cantidad de tokens que
cada VM tiene asignados. Es decir que si a
dos VMs se le asigna el mismo tamaño de
bucket, la distribución de CPU será
equitativa, mientras que si a una se le
asigna un bucket más grande, ésta tendrá
mayor prioridad.
Figura 7. Priorización VM0 con 3 VMs
Distribución de CPU
En segundo lugar, se iniciaron dos VMs
con igual cantidad de tokens. En cada una
de ellas se corrió el mismo proceso, sólo
que con diferentes prioridades. En VM2 se
ejecutó nice -n 10 test y en VM3 nice -n 20
test.
La figura 8 muestra que el planificador de
Minix realiza una planificación desigual
entre las dos VMs, atendiendo a la
prioridad de cada proceso, mientras que el
planificador de token bucket planifica la
CPU de forma equitativa.
Figura 8. Distribución de la CPU.
Figura 9. Priorización de las VMs
Priorización de tareas sobre procesos.
Con los datos obtenidos en el benchmark
anterior, se realizó una comparación del
comportamiento cuando se asignan 32 y 32
tokens, y 32 y 128. En la figura 10 puede
verse cómo la VM3 al bajar su proporción
de tokens, baja su porcentaje de CPU
ocupado por procesos (P) en un 11% (de
31% a 20%), mientras que sus tareas (T)
bajan en 6% (de 18% a 12%). Es decir, que
al perder porcentaje de CPU debido a un
cambio en el tamaño de bucket, la VM3
cede más tiempo de procesos que de tareas.
Figura 10: Priorización de Tareas sobre Procesos
Discusión
Tanto las simulaciones como las
mediciones realizadas sobre el sistema
arrojaron resultados que confirman la
efectividad del algoritmo propuesto.
En la figura 8 se observa un uso equitativo
de la CPU por parte de todas las VMs,
priorizando las tareas sin descuidar los
procesos, como se ve en la figura 10. En la
figura 9 se hace evidente cómo asignándole
más tokens a una VM se logra darle
prioridad por encima de las demás.
La utilización de un software de simulación
previo a la implementación es ventajosa, ya
que da la posibilidad de probar diferentes
algoritmos de una forma sencilla, y poder
compararlos y encontrar errores o defectos
antes de comenzar a implementar.
Otro punto a destacar es la implementación
del sistema de métricas que ayudó a ver de
forma fehaciente el comportamiento del
algoritmo y comprobar que el sistema
funciona de la manera que esperada.
Conclusión
En este trabajo se presentó un sistema de
planificación basado Minix, para el cual se
propone un algoritmo de planificación
alternativo, de manera de lograr una
distribución equitativa de la CPU entre
diferentes VMs.
El trabajo muestra dos aspectos importantes
sobre el diseño de un planificador para un
sistema de virtualización.
En primera instancia, la planificación de
procesos de un sistema de virtualización no
es una tarea trivial y mucho menos si se
pretende el aislamiento de rendimiento
requerido por las VMs. Existen un sin
número de factores a tener en cuenta y el
más mínimo cambio puede desestabilizar el
sistema.
Por otro lado Minix, al ser un sistema de
microkernel y estratificado, brinda una
excelente oportunidad para su modificación
y la libertad de trabajar sin la preocupación
de que algún driver o tarea interfiera o
altere la planificación.
Los conocimientos adquiridos al trabajar
con este sistema operativo no se limitan
solamente al mismo, sino que son un punto
de partida para el aprendizaje de cuestiones
relacionadas a los sistemas operativos en
general, con lo cual pueden extrapolarse a
futuros proyectos.
Agradecimientos
A nuestros docentes por el apoyo y permitirnos ser
parte de este proyecto.
Referencias
[1] Tanenbaum, A. S., Woodhull, A. (2006).
Operating Systems: Design and Implementation- 3o
edición. Prentice Hall.
[2] Tanenbaum, A. S. (2003). Redes
Computadoras - 4o edición. Prentice Hall.
de
[3] Tanenbaum, A. S. (2009). Sistemas Operativos
Modernos - 3o edición. Prentice Hall.
[4] LXC - Linux Containers
https://linuxcontainers.org
[5] SimSo - Simulation of Multiprocessor
Scheduling with Overheads.
http://homepages.laas.fr/mcheramy/simso/
[6] XEN Credit Scheduler.
http://wiki.xen.org/wiki/Credit_Scheduler
[7] MINIX 3 Documentation
http://www.minix3.org/documentation/index.html
Descargar