IIC 2332 -- Sistemas Operativos
Tarea de Threads

En esta tarea implementarás algunos primitivos de sincronización y los usarás para solucionar el problema de productores y consumidores con buffer limitado. No necesitarás escribir mucho código para la tarea, pero tendrás que diseñar tus soluciones con cuidado. También necesitarás algún tiempo para conocer a Nachos.

El plazo para la tarea vence el lunes el 15 de abril a 11:30. Tu compañero de trabajo y tú deben mandarme un email con sus nombres antes de la clase del 10 de abril.

Tu compañero y tú recibirán la misma nota para la tarea.

Introducción a Nachos

Debieras conseguir una cuenta en las máquinas de Linux (usa "telnet plomo" con login cuentas). Puedes usar las máquinas en la sala LIAC o con telnet a torvalds, kernighan, o ritchie. El administrador de estas máquinas es Sergio Rademacher <sergio@ing.puc.cl>.

Nachos está en /usr/local/nachos. Copia el código (aproximadamente 551K) a tu cuenta con

cp -pr /usr/local/nachos/code .
Para probar si todo está funcionando con tu cuenta, paths, etc., haz lo siguiente:
cd code/threads
make depend
make
./nachos
El make generará algunos avisos. Si Nachos corre correctamente, tendrás el siguiente output:
*** thread 0 looped 0 times
*** thread 1 looped 0 times
*** thread 0 looped 1 times
*** thread 1 looped 1 times
*** thread 0 looped 2 times
*** thread 1 looped 2 times
*** thread 0 looped 3 times
*** thread 1 looped 3 times
*** thread 0 looped 4 times
*** thread 1 looped 4 times
No threads ready or runnable, and no pending interrupts.
Assuming the program completed.
Machine halting!

Ticks: total 130, idle 0, system 130, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0

Cleaning up...
Debieras imprimir los archivos en el directorio de threads y usarlos para seguir la ejecución del sistema. Empieza con main en main.cc, la llamada a Initialize en system.cc, y la llamada a ThreadTest en threadtest.cc.

Hay un archivo de TAGS en el directorio code. Si usas Emacs, es muy fácil encontrar una función o una clase con M-. (find-tag). Debieras leer también la Sección 3, 6.1, y 6.2 de A Roadmap Through Nachos, que explica la estructura del sistema de threads usado por Nachos.

Si no entiendes una parte del sistema, ¡pregunta! Usa el newsgroup del curso (puc.cursos.iic2332). Puedes discutir la tarea con otros alumnos, pero tu código debe ser el tuyo propio. Por supuesto, puedes mandarme correo electrónico con preguntas, pero el newsgroup sería mejor. Y puedes visitarme cuando quieras.

Lo que tienes que hacer

Hay tres partes de la tarea. Necesitarás modificar algunos archivos en el directorio threads; tus cambios y código nuevo en archivos existentes deben ser marcados como lo siguiente:
#ifdef CHANGED
...codigo modificado o nuevo...
#endif // CHANGED
Los comentarios son críticos para revisar tu trabajo. ¡No los olvides!

Crearás también algunos archivos nuevos en el directorio threads. Para compilarlos tendrás que modificar las definiciones de THREAD_H, THREAD_C, y THREAD_O en code/Makefile.common. Tienes que ejecutar un "make depend" en threads después de cada cambio a Makefile.common.

Locks (30%)

En synch.h encontrarás la definición de la clase Lock. Sus dos operaciones más importantes son Acquire y Release, las que usamos para establecer la exclusión mutua. Ambas operaciones son atómicas. Tenemos el siguiente pseudo-código:
acquire(lock)
  if lock.value = 1 then lock.value = 0
  else begin
    poner el proceso en lock.queue
    bloquear
  end

release(lock)
  if lock.queue es vacio then lock.value = 1
  else begin
    sacar un proceso P de lock.queue
    despertar P
  end
Un thread despertado continua del punto inmediatamente siguiente a la llamada a la función para bloquear.

Implementa locks. Añade declaraciones para variables privadas en synch.h. En synch.cc, implementa Lock, ~Lock, Acquire, Release, y isHeldByCurrentThread. La última vuelve TRUE si el lock pertenece al thread corriente y es útil en Acquire, Release, y la segunda parte de la tarea. ¿Debiera ser atómica? Ten cuidado.

Acquire debiera chequear a través de un ASSERT si el thread que llama ya lo ha adquirido. Release debiera chequear con ASSERT si el thread que llama tiene el lock.

No uses los semáforos (clase Semaphore) para la implementación de locks. Empero, puedes usar el código para semáforos como una guía.

Para probar tu solución debieras modificar threadtest.cc. Puedes encontrar mis archivos de prueba en

/usr/local/nachos/iic2332/tarea1/threadtestN.cc
Use Nachos sin argumentos (threads ceden el control solamente a través de llamadas a Yield) y con el argumento "-rs n" (Nachos produzca llamadas adicionales de Yield en puntos aleatorios), donde n es un entero que sirve como una semilla aleatoria. El argumento "-d t" produzca información sobre los cambios de control.

Condition variables (35%)

El archivo synch.h define también la clase Condition. Se usan las variables de condición siempre con un lock; más de una variable de condición pueden compartir el mismo lock. La idea es que el lock controla acceso a algún recurso. Después de que adquiere el lock, un thread prueba una condición. Por ejemplo, la condición podría ser si el recurso está listo. Si la prueba tiene éxito, el thread continua, accesa al recurso, y libera el lock. Pero si la prueba es falsa, el thread querría esperar (con Wait) hasta que la condición sea verdadera. Por supuesto, debiera liberar el lock antes de la espera, así que otros threads pueden adquirirlo y cambiar el estado del recurso.

Cuando sabe que la condición es verdadera, un thread que tiene el lock puede desear señalar a cualesquier threads que esperan la condición. En este caso el thread llama a Signal (para despertar un thread) o Broadcast (para despertar todos los threads que esperan la condición). Entonces el thread libera el lock.

Un thread despertado tiene que readquirir el lock. Existe la posibilidad cuando el thread empieza a correr que la condición no sea verdadera. La razón es que algún otro thread puede accesar el recurso y cambiarlo antes de que el thread despertado sea planificado (i.e., entre el señal y el tiempo cuando el thread despertado empiezas a correr). El thread despertado puede tener que esperar otra vez.

Las operaciones para variables de condición tienen que ser atómicas. Por ejemplo, en Wait, un thread no puede liberar el lock antes de bloquearse si la acción no es atómica. En este caso otro thread podría adquirir el lock y señalar la condición antes de que el primer thread se bloquee y empiece a esperar la condición.

Implementa la clase Condition en synch.cc. Algunos de mis archivos de threadtestN.cc prueban variables de condición, pero debieras implementar tus propias pruebas también.

Productor-consumidor (35%)

Para la parte final de la tarea vas a solucionar el problema de productor-consumidor con buffer limitado. Vas a implementar una clase SynchBuffer (ve a la clase SynchList en synchlist.cc). Un SynchBuffer es un buffer de un tamaño fijo que apoya las operaciones Insert y Remove. Insert añade un item al buffer; si el buffer está lleno, Insert se bloquea hasta que puede añadir el item. De la misma manera Remove saca un item del buffer; si el buffer está vacío, se bloquea hasta que puede sacar un item.

La ventaja de SynchBuffer es que sus usuarios no tienen que saber nada sobre la sincronización. Toda la sincronización es dentro de la implementación de la clase.

En el directorio tarea1 están synchbuffer.h, con un esqueleto de la definición de la clase, y prodcon.cc, con código para productores y consumidores. Tienes que modificar synchbuffer.h y crear synchbuffer.cc. Para probar tu solución, reemplaza la llamada a ThreadTest en main con una llamada a ProdCon. Empieza con pruebas de solamente un productor y un consumidor (tendrás que comentar algunas líneas en ProdCon) y continua con pruebas con más de uno de ambos. Puedes ver más de tres mensajes seguidos de Get o Put. ¿Por qué?

Necesitarás también los archivos buffer.h y buffer.cc. No olvides modificar Makefile.common.

Tu solución debiera usar variables de condición y locks. No uses los semáforos. Incluye en synchbuffer.cc un comentario que describa tu diseño.

Mi solución para synchbuffer.cc tiene 46 líneas, incluyendo líneas vacías (pero no comentarios).

Entrega

Habrá un aviso con los detalles para entregar tu trabajo.
Last edited April 1, 1996, by knabe@ing.puc.cl