Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Herramientas de sincronizacin: - Lock - Semforos Se pueden obtener a partir de las otras. - Monitores - Mensajes Problemas clsicos: - Productor/Consumidor - Filsofos comensales - Lectores/Escritores Errores: - Data race - Deadlock - Hambruna Divesas Implementaciones: - Linux Threads Posix - Windows NT (estndar) - Java 1.4 y Java 1.5
Joel Fuentes
Otoo 2011
Temario
ESTRUCTURA DEL COMPUTADOR Busy-Waiting Interrupciones Canales Modo dual ESPACIOS DE DIRECCIONES VIRTUALES ARQUITECTURA DEL S.O. Identificador de proceso Descriptor de proceso Estados de un proceso Cambio de contexto Tajada de tiempo y rfaga de CPU SCHEDULING DE PROCESOS FCFS SJF y SJF con prioridad Round Robin EJEMPLO DE ARQUITECTURA - NSYSTEM
DRAM
VRAM (AGP)
ROM (BIOS)
Espacio de E/S:
0 64Kb
Dispositivos generales
Se accesa mediante instrucciones: in / out Mecanismos de E/S: Busy Waiting Interrupciones Canales DMA
Busy-waiting
Este mecanismo ocupa el 100% de la CPU. Ejemplo:
#define N 512 for (;;) { char buff[N]; read(buff, N); El truco procesar(buff, N); }
ctrl
data
hexadecimal
dos bytes
Caratersticas: En un procesador moderno se puede hacer 10 millones de consultas/seg 10 Mb/seg como velocidad de transferencia. (Tasa de transferencia)
Interrupciones (+ busy-waiting)
Se indica al dispositivo que interrumpa el programa en ejecucin Se comanda el dispositivo para realizar la operacin de E/S El programa se dedica a otras actividades (S.O. procesos) Cuando se completa la operacin de E/S se invoca una rutina de atencin, la que realiza la transferencia (en forma transparente para el programa interrumpido).
void read(char* pb, int n){ if (nb == 0) activarIntDisp(); while (nb < n) ; bcopy(pb, pb2, n); nb = 0; tot = n; activarIntDisp(); }
Manejo de error en memoria Manejo de error en disco Manejo de error divisin por cero
Evaluacin: 1 procesador moderno ejecuta 10 milln de interrupciones/seg (como mximo) 10MB/seg (tasa de transferencia)
Canales (+ interrupciones)
La diferencia con las interrupciones es que: Con interrupciones: Cuando ocurre la interrupcin los datos estn en el dispositivo (buffer) y hay que transferirlos a la memoria por la CPU, lo que toma tiempo. En el Canal: cuando ocurre la interrupcin los datos ya estn en memoria. Cuando se produce la interrupcin la informacin ya est en memoria porque el canal la transfiri. 100MB/seg (tasa de transferencia)
Modo Dual
Modo Sistema: Todas las instrucciones son vlidas, tiene acceso a todo el hardware, cdigo kernel. En este modo se ejecuta el S.O. Modo Usuario: Permite ejecutar un subconjunto de las instrucciones y tiene acceso limitado al hardware. Instrucciones prohibidas: E/S, proteccin de memoria, etc. En este modo se ejecutan los programas de usuario. Servicios del S.O. mediante llamadas al sistema. Para pasar de un modo a otro: interrupciones software (ejemplo: trap) o interrupciones hardware (divisin por cero, dispositivos de E/S).
2Gb
PILA SISTEMA (Ncleo)
4Gb
Las direcciones virtuales se traducen a direcciones reales en la MMU. Cada proceso posee su propio espacio de direcciones. Permite asegurar que un proceso no interfiera con otro.
Arquitectura del SO
fopen, fread, fwrite, malloc, free
aplicacin API
Ncleo
API
drivers Sist. Arch.
open, read, write, sbrk, fork, exec, exit Estructuras del ncleo: Descriptores de proceso, archivos abiertos, colas de procesos, recursos pedidos, etc.
Arquitectura del SO
Identificador de proceso: es la manera de identificar a los procesos ente s. Fuera del ncleo. Ej: PID en Unix/Linux Descriptor de proceso: es la estructura de datos que representa a un proceso dentro del ncleo. Almacena: estado del proceso, registros (virtuales), informacin de scheduling, recursos asignados, contabilidad, etc.).
/*DESCRIPTOR DE NSYSTEM*/ typedef struct Task { int status; /* READY, ZOMBIE,WAIT_TASK,WAIT_REPLY,WAIT_SEND,*/ char *taskname; SP sp; /*tope de la pila*/ SP stack; /*base de la pila*/ struct Task *nextTask; void *queue /*para nExitTask y nWaitTask*/ int rc; struct Task *wait_task; /* nsend, nReceive,nReply*/ struct queue * send-queue; union {void * mesg; int rc;} send; int wake_timer; int in_wait_squeue }*nTask;
Estados de un Proceso
Tpicamente un proceso puede pasar por distintos estados mientras existe: En creacin: El ncleo est obteniendo los recursos que necesita el proceso para correr (memoria o disco) Corriendo: El proceso est en posesin del procesador, el que ejecuta sus instrucciones. Esperando: el proceso espera que se lea un sector del disco, que llegue un mensaje de otro proceso, que transcurra un intervalo de tiempo, que termine otro proceso, etc. Listo: El proceso est activo pero no est en posesin del procesador. Terminado: El proceso termin su ejecucin pero sigue existiendo para que otros procesos puedan determinar que termin. Colas de Sheduling: Cuando un proceso espera, permanece en estas colas. Estas pueden ser FIFO o colas de prioridades
READY: elegible por el planificador o corriendo. ZOMBIE: la tarea llam nExitTask y espera nWaitTask. WAIT-REPLY: la tarea hizo nSend y espera nReply. WAIT-SEND: La tarea hizo nReceive y espera nSend. WAIT-READ: La tarea est bloqueada en un read. WAIT-WRITE: la tarea est bloqueada en un write. Entre otros
Cambio de Contexto
Consiste en el traspaso de la CPU de un proceso a otro. Esto involucra ciertas acciones: 1. Resguardar los registros 2. Contabilizar uso de los recursos (NSystem no tiene contabilizacin) 3. Cambiar el espacio de direcciones virtuales. (esto es lo ms caro en el costo final del cambio de contexto). 4. Restaurar registros del proceso receptor de la CPU.
Scheduling de Procesos
Es la labor fundamental del ncleo, se puede traducir como Planificacin de procesos. El objetivo de la planificacin de procesos es determinar el siguiente proceso a ejecutar (al que se le otorga la CPU). Existen 3 tipo de planificacin: De corto plazo: ejecucin de procesos por el kernel De mediano plazo: relacionado con la tcnica de memoria virtual De largo plazo: usada para programar ejecucin de procesos como por ejemplo: administrador de impresin, recolector de basura, etc. En este momento nos interesa la planificacin de corto plazo, los algoritmos ms comunes son: FCFS SJF Round Robin
Scheduling de Procesos
24
A
A,B,C
3
B
3
C
T. Despacho =
24 + 27 + 30 3 = 27
3
B
B,C,A
3
C
24
A
T. Despacho = =
3 + 6 + 30 3 13 Bastante mejor!
Ventajas: simple de implementar (con una cola FIFO) Desventajas: No sirve para procesos interactivos No se distribuye bien la carga entre procesos intensivos en CPU y aquellos intensivos en E/S.
Simplificado queda:
Este debe ser un clculo muy simple porque el scheduler debe ocupar lo menos posible la CPU.
A D C
Esta estrategia minimiza el tiempo de respuesta. En este caso, el tiempo transcurre desde que se inicia una interaccin hasta que aparece el primer resultado (parcial).
Arquitectura de nSystem
Mltiples tareas en 1 solo proceso Unix Multiplexar el tiempo de ese proceso Se necesita: Cronmetro regresivo Seales ( interrupciones) E/S no bloqueante
Aplicacin multitarea
E/S nio.c
semforos nSem.c
Mensajes nMsg.c
Tarea nProcess.c
$NSYSTEM/src NSYSTEM
SetAlarm nDep.c
setitimer
sigaction
UNIX
Arquitectura de nSystem
Cmo se realiza un cambio de contexto en NSYSTEM?
Descriptor de la tarea Pila de la tarea Descriptor de la tarea Pila de la tarea
nMain SP
La funcin Q se ejecuta actualmente
P Q
8Kb de pila
nMain SP
Si la tarea pasa a modo de espera, sin que Q termine de ejecutar su cdigo
P Q
PC + Reg activacin
Se utiliza la funcin: void ChangeContext(nTask sale, nTask entra); (19 instrucciones assembler en nstack-i386.s)
Scheduling de nSystem
NSYSTEM utiliza su propia estrategia de planificacin de procesos y se llama: PLCFS o Preemptive Last Come First Served. Funciona as: Las rfagas se atienden en orden LIFO La tarea que llega, recibe la CPU de inmediato La tarea perdedora se coloca en primer lugar en la cola READY Cada t milisegundos se suspende la tarea en ejecucin y se lleva al final de la cola.
llega
Tn T0 Fin tajada
T0
T1
T2
T3
sale
ready queue
Scheduling de nSystem
CPU
llega
Tn T0 Fin tajada
T0
T1
T2
T3
sale
ready queue
1) llega Tn 2) sale T0
Tn
T0
T1
T2
T3
T1
T2
T3
3) fin tajada
T1
T2
T3
T0
Scheduling de nSystem
Implementacin: Variables globales
nTask current_task; Queue ready_queue; int current_slice; /*tarea actual en el procesador*/ /*cola de procesos listos*/ /*duracin de la taja de tiempo*/
Ejercicio
Implementar el mecanismo de semforos presente en nSystem: nSem nMakeSem(int n); void nWaitSem(nSem s); void nSignalSem(nSem s);
typedef struct { int c; queue q; } *nSem; void nWaitSem(nSem s) { START_CRITICAL(); if (s->c > 0) s->c--; else { current_task->status= WAIT_SEM; PutTask(s->q, current_task); Resume(); } END_CRITICAL(); } void nSignalSem(nSem s) { START_CRITICAL(); if (EmptyQueue(s->q)) s->c++; else { nTask w= GetTask(s->q); w->status= READY; PushTask(ready_queue, current_task); PushTask(ready_queue, w); Resume(); } END_CRITICAL(); }
P1
nSystem
P2 P3
t1 t2
Fin de tajada de tiempo Unix
t1 termina su
tajada de tiempo
nSystem utiliza el cronmetro virtual para controlar sus tajadas de tiempo, es decir, el scheduler de corto plazo.
Implementacin de nSleep
La instruccin sleep(int ms) de unix permite a un proceso pesado dormir la cantidad de milisegundos indicada en ms. nSleep() de nSystem es similar, con la diferencia que funciona en un ambiente concurrente y es invocada por procesos livianos. Veamos el siguiente ejemplo de uso para nSleep(10)
a)
t1
t2
b)
t1
nSleep(10)
t2
t3
nSleep(10)
10 ms
nSleep(10)
10 ms
10 ms
Seal que indica que se vencieron los siguientes 10 ms
Implementacin de nSleep
El cdigo para implementar nSleep() es el siguiente:
void nSeelp(int delay) { START_CRITICAL(); current_task->status= WAIT_SLEEP; ProgramTask(delay); /*Programa la tarea para que se despierte en delay ms*/ Resume(); END_CRITICAL(); }
void ProgramTask(int timeout) { if (timeout>0) { /*si es mayor a cero*/ int curr_time= nGetTime(); /*obtenemos el tiempo actual*/ int wake_time= curr_time + timeout; /*calculamos el tiempo para despertarla*/ if (EmptySqueue(wait_squeue) || /*si la primera tarea debe despertarse*/ wake_time - GetNextTimeSqueue(wait_squeue)<0) /*despues de la actual*/ SetAlarm(REALTIMER, wake_time - curr_time, RtimerHandler); /*prog. timer*/ /*se usa una cola de prioridad, menor tiempo -> mejor prioridad*/ PutTaskSqueue(wait_squeue, current_task, wake_time); } else { /*si timeout es menor a cero, solo se encola en ready_queue nuevamente*/ current_task->status= READY; PushTask(ready_queue, current_task); } }
Implementacin de nSleep
La funcin RTimerHandler() es la encargada de manejar la seal cuando se cumple el tiempo.
void RTimerHandler() { int curr_time= nGetTime(); /* Despertamos todas las tareas con wake_time<=curr_time */ while (! EmptySqueue(wait_squeue) && GetNextTimeSqueue(wait_squeue)<=curr_time) { nTask task= GetTaskSqueue(wait_squeue); /* Ahora la tarea que dorma vuelve a estar READY */ task->status= READY; PushTask(ready_queue, task); } /* Preparamos la prxima interrupcin (seal)*/ SetAlarm(REALTIMER, EmptySqueue(wait_squeue)? 0 : GetNextTimeSqueue(wait_squeue)-curr_time, RtimerHandler); } Cuando se ejecute RTimerHandler() la tarea que est primera en la cola de prioridad provoc la alarma y por ende hay que despertarla. Sin embargo, el sistema puede haber sufrido retrasos y ahora pudieran haber varias tareas que hay que despertar
E/S en nSystem
Nn int fd; int rc; fd= nOpen(archivo, flag); rc= nRead(fd, buff, n); rc= nWrite(fd, buff, n); nClose(fd); Problema con nRead()
t1
t2
t3
read()
E/S en nSystem
Implementacin de nRead() Se deben colocar los descriptores (fd) en modo no bloqueante, para esto se utiliza la llamada al sistema fcntl de unix. rc = read(fd) Entrega -1 y la variable errno ser igual a la constante EAGAIN EAGAIN significa que el proceso se iba a bloquear.
int nRead(int fd, char *buf, int nbyte) { int rc; START_CRITICAL(); rc= read(fd, buf, nbyte); /* Intentamos leer */ while (rc<0 && errno==EAGAIN) { /* No hay nada disponible */ AddWaitingTask(fd, current_task); /* registrar el descriptor*/ current_task->status= WAIT_READ; ResumeNextReadyTask(); /* Pasamos a la proxima que este ready */ rc= read(fd, buf, nbyte); /* Ahora si que deberia funcionar */ } END_CRITICAL(); return rc; }
E/S en nSystem
static void SigioHandler() { int fd; fd_set readfds, writefds; FD_ZERO(&readfds); FD_ZERO(&writefds); /* Vemos que descriptores tienen asociada E/S pendiente */ for (fd=0; fd<maxsize_pending; fd++) { nTask task= pending_tasks[fd]; if (task!=NULL) /* Hay una tarea pendiente para este descriptor */ if (task->status==WAIT_READ) FD_SET(fd, &readfds); else if (task->status==WAIT_WRITE) FD_SET(fd, &writefds); else nFatalError("SigioHandler","Bug!\n"); } select(maxsize_pending, &readfds, &writefds, NULL, &zero_timeval); /* Vemos para que descriptores se ha resuelto la E/S pendiente */ for (fd=0; fd<maxsize_pending; fd++) { nTask task= pending_tasks[fd]; if (task!=NULL && (FD_ISSET(fd, &readfds) || FD_ISSET(fd, &writefds))) { task->status= READY; PushTask(ready_queue, task); pending_tasks[fd]=NULL; /* Se resolvio ese descriptor */ } } SetHandler(SIGIO, SigioHandler); }