← Planificación de la CPU Bloque 3 · Primero en llegar, primero en ser atendido · Tema 12 de 44

Tema 12

Simulador de FCFS

En los temas anteriores calculamos FCFS a mano y en C. Ahora lo programamos completo en JavaScript: una página que genera una carga de trabajo, ejecuta el algoritmo, dibuja el diagrama de Gantt y calcula las métricas, todo de forma interactiva. Este simulador es la plantilla que reutilizaremos para todos los algoritmos del curso.

1. Por qué un simulador de FCFS

Calcular a mano es indispensable para entender un algoritmo, pero tiene límites: con más de 5 procesos el cálculo se vuelve tedioso y propenso a errores, y no permite explorar «qué pasaría si». Un simulador interactivo permite cambiar un parámetro (la ráfaga de un proceso, el orden de llegada) y ver el efecto inmediato en el Gantt y en las métricas.

FCFS es el primer simulador del curso porque es el algoritmo más simple. Una vez que funciona, sirve como plantilla para los demás: solo cambia la función del algoritmo; el modelo de datos, el generador, el Gantt y la tabla son los mismos.

Un simulador, muchos algoritmos

El simulador de FCFS de este tema es la base de todos los demás. En los temas 16, 19, 23, 27, 31, 34 y 40 se construirán simuladores similares; la única diferencia será la función del algoritmo y, a veces, controles adicionales (quantum, prioridad, número de colas).

2. Arquitectura del simulador

El simulador se divide en cinco partes independientes, cada una con una responsabilidad:

ParteResponsabilidadFunciones
ModeloRepresentar un proceso y una carga de trabajo.crearProceso, copiar
GeneradorProducir cargas de trabajo reproducibles.crearGenerador, generarCarga
AlgoritmoEjecutar FCFS y rellenar las métricas.fcfs
VistaDibujar el Gantt y la tabla en el DOM.dibujarGantt, renderTabla
ControladorLeer los controles, llamar al algoritmo y actualizar la vista.dibujar, listeners

Esta separación permite cambiar una parte sin tocar las demás. Por ejemplo, para convertir el simulador de FCFS en uno de SJF, solo se reemplaza la función del algoritmo; el resto no cambia.

3. El modelo de datos

El modelo es el mismo que se definió en el tema 9. Un proceso es un objeto con campos de entrada y de salida:

modelo.js
function crearProceso(pid, llegada, rafaga) {
    return {
        pid: pid, llegada: llegada, rafaga: rafaga,
        inicio: 0, fin: 0, respuesta: 0, espera: 0, retorno: 0
    };
}

function copiar(x) {
    return {
        pid: x.pid, llegada: x.llegada, rafaga: x.rafaga,
        inicio: 0, fin: 0, respuesta: 0, espera: 0, retorno: 0
    };
}

La función copiar es esencial: el algoritmo trabaja sobre una copia para no modificar la carga original, que se necesita para comparar algoritmos o para regenerar.

4. La función fcfs completa

La función recibe un arreglo de procesos y devuelve un nuevo arreglo ordenado por ejecución, con las métricas rellenas. Incluye el manejo de CPU ociosa y el desempate por pid:

fcfs.js
function fcfs(procesos) {
    /* 1. Copiar para no modificar la entrada. */
    var p = procesos.map(function (x) { return copiar(x); });

    /* 2. Ordenar por llegada (desempate por pid). */
    p.sort(function (a, b) {
        if (a.llegada !== b.llegada) return a.llegada - b.llegada;
        return a.pid - b.pid;
    });

    /* 3. Recorrer la linea de tiempo. */
    var t = 0;
    for (var i = 0; i < p.length; i++) {
        if (t < p[i].llegada) t = p[i].llegada;   /* CPU ociosa */
        p[i].inicio = t;
        p[i].fin = t + p[i].rafaga;
        t = p[i].fin;
        p[i].respuesta = p[i].inicio - p[i].llegada;
        p[i].retorno = p[i].fin - p[i].llegada;
        p[i].espera = p[i].retorno - p[i].rafaga;
    }
    return p;
}

El arreglo devuelto está ordenado por ejecución: el primer elemento es el primero en recibir la CPU. Las métricas se calculan con las fórmulas del tema 7 y 8.

5. Dibujar el Gantt

El Gantt se dibuja como una fila de <span> dentro de un contenedor .gantt. Cada span tiene un flex proporcional a su duración, y se insertan huecos para la CPU ociosa:

dibujarGantt.js
function dibujarGantt(procesos, ganttEl, ticksEl) {
    ganttEl.innerHTML = '';
    ticksEl.innerHTML = '';
    if (procesos.length === 0) return;

    var total = procesos[procesos.length - 1].fin;
    var t = 0;

    procesos.forEach(function (p, idx) {
        /* Hueco de CPU ociosa. */
        if (p.inicio > t) {
            var hueco = document.createElement('span');
            hueco.style.flex = (p.inicio - t) + ' 1 0';
            hueco.textContent = 'ociosa';
            hueco.style.background = 'var(--surface-2)';
            hueco.style.color = 'var(--ink-soft)';
            ganttEl.appendChild(hueco);
        }
        /* Bloque del proceso. */
        var span = document.createElement('span');
        span.className = 'p' + (idx % 3);
        span.style.flex = p.rafaga + ' 1 0';
        span.textContent = 'P' + p.pid;
        ganttEl.appendChild(span);

        /* Marca de tiempo. */
        var marca = document.createElement('span');
        marca.style.left = (p.inicio / total * 100) + '%';
        marca.textContent = p.inicio;
        ticksEl.appendChild(marca);

        t = p.fin;
    });

    /* Marca final. */
    var fin = document.createElement('span');
    fin.style.left = '100%';
    fin.textContent = total;
    ticksEl.appendChild(fin);
}

La función asume que el arreglo está ordenado por ejecución. Los colores se asignan por índice (idx % 3), no por pid, para que procesos adyacentes tengan colores distintos.

6. La tabla de métricas

La tabla muestra los resultados ordenados por pid (no por orden de ejecución), para que sea fácil comparar procesos:

renderTabla.js
function renderTabla(procesos, filasEl, promEl) {
    var ordenado = procesos.slice().sort(function (a, b) {
        return a.pid - b.pid;
    });
    filasEl.innerHTML = '';
    var sr = 0, se = 0, st = 0;
    ordenado.forEach(function (p) {
        var tr = document.createElement('tr');
        [ 'P' + p.pid, p.llegada, p.rafaga, p.inicio, p.fin,
          p.respuesta, p.espera, p.retorno ].forEach(function (v) {
            var td = document.createElement('td');
            td.textContent = v;
            tr.appendChild(td);
        });
        filasEl.appendChild(tr);
        sr += p.respuesta; se += p.espera; st += p.retorno;
    });
    var n = ordenado.length;
    promEl.textContent = 'Promedio: respuesta=' + (sr/n).toFixed(2) +
        ' espera=' + (se/n).toFixed(2) + ' retorno=' + (st/n).toFixed(2);
}

7. Los controles interactivos

Los controles permiten ajustar la carga de trabajo: número de procesos, llegada máxima, ráfaga máxima y semilla. Cada cambio regenera la carga y recalcula:

controlador.js
function dibujar() {
    var n = parseInt(nInput.value, 10) || 2;
    var maxL = parseInt(llegInput.value, 10) || 0;
    var maxR = parseInt(rafInput.value, 10) || 1;
    var seed = parseInt(seedInput.value, 10) || 1;
    if (n < 2) n = 2; if (n > 8) n = 8;
    if (maxR < 1) maxR = 1;
    var carga = generarCarga(n, maxL, maxR, seed);
    var resultado = fcfs(carga);
    dibujarGantt(resultado, gantt, ticks);
    renderTabla(resultado, filas, prom);
    nota.innerHTML = 'Carga generada con semilla ' + seed + ': ' + n +
        ' procesos. Algoritmo: FCFS.';
}
[nInput, llegInput, rafInput, seedInput].forEach(function (inp) {
    inp.addEventListener('input', dibujar);
});
dibujar();

Los valores se validan (mínimos y máximos) antes de generar la carga. El primer dibujo se hace al cargar la página, para que el simulador no aparezca vacío.

8. Entrada manual de procesos

Además de generar cargas aleatorias, es útil poder introducir una carga específica para reproducir un ejemplo del libro o verificar un cálculo a mano. La entrada manual usa un campo de texto con formato pid,llegada,rafaga por línea:

entrada manual
1,0,5
2,1,3
3,2,4
4,4,2

La función de parseo convierte el texto en una carga de trabajo:

parsear.js
function parsearCarga(texto) {
    var lineas = texto.trim().split('\n');
    var carga = [];
    lineas.forEach(function (linea) {
        var partes = linea.split(',').map(function (s) { return parseInt(s.trim(), 10); });
        if (partes.length === 3 && !isNaN(partes[0]) && !isNaN(partes[1]) && !isNaN(partes[2])) {
            carga.push(crearProceso(partes[0], partes[1], partes[2]));
        }
    });
    return carga;
}

Si la entrada manual está vacía o es inválida, el simulador usa el generador aleatorio. Esto permite combinar ambos modos en la misma página.

9. Verificación y casos de prueba

Un simulador debe verificarse contra casos conocidos. Estos son los casos de prueba mínimos para FCFS:

CasoEntradaResultado esperado
Un procesoP1(0,5)Gantt: P1 0-5. Respuesta 0, espera 0, retorno 5.
Todos en 0P1(0,3), P2(0,2), P3(0,1)Gantt: P1 0-3, P2 3-5, P3 5-6. Esperas: 0, 3, 5.
Llegadas distintasP1(0,5), P2(1,3), P3(2,4), P4(4,2)Gantt: P1 0-5, P2 5-8, P3 8-12, P4 12-14. Promedios: resp 4,50, esp 4,50, ret 8,00.
Hueco de CPUP1(0,3), P2(5,2)Gantt: P1 0-3, ociosa 3-5, P2 5-7. Tiempo total 7.
Llegadas igualesP1(2,4), P2(2,3)Desempate por pid: P1 primero. Gantt: P1 2-6, P2 6-9.
Ráfaga de 1P1(0,10), P2(0,1)P2 espera 10 (efecto convoy). Gantt: P1 0-10, P2 10-11.

Si el simulador pasa todos estos casos, se puede confiar en él para cargas más grandes.

10. El código completo

A continuación se muestra el simulador completo en una sola página. Puedes copiarlo en un archivo HTML y abrirlo en el navegador:

simulador_fcfs.html (estructura)
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8" />
  <title>Simulador FCFS</title>
  <style>
    /* estilos del Gantt, tabla, controles */
  </style>
</head>
<body>
  <h1>Simulador de FCFS</h1>
  <div id="controles">...</div>
  <div id="gantt"></div>
  <div id="ticks"></div>
  <table id="tabla">...</table>
  <script>
    /* modelo + generador + fcfs + vista + controlador */
  </script>
</body>
</html>

El simulador de este tema (abajo) es una versión reducida que usa los estilos del curso. El código completo está en el script de la página.

11. Extensiones posibles

Una vez que el simulador de FCFS funciona, se le pueden añadir extensiones sin cambiar la arquitectura:

Estas extensiones se verán en el tema 42 (proyecto: banco de pruebas de algoritmos).

12. Simulador interactivo

Este simulador reúne todo lo anterior: genera una carga de trabajo (o la recibe manualmente) y la ejecuta con FCFS. Cambia los parámetros o escribe una carga propia en el campo de texto:

Simulador · FCFS completo

ProcesoLlegadaRáfagaInicioFinRespuestaEsperaRetorno

13. Resumen

14. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Cuáles son las cinco partes del simulador?

Modelo, generador, algoritmo, vista y controlador.

2. ¿Por qué la función del algoritmo trabaja sobre una copia?

Para no modificar la carga original, que se necesita para comparar algoritmos o regenerar.

3. ¿En qué orden devuelve la función fcfs el arreglo?

En orden de ejecución: el primer elemento es el primero en recibir la CPU.

4. ¿Cómo se hace proporcional la longitud de un bloque del Gantt?

Asignando flex: rafaga + ' 1 0' al span.

5. ¿Por qué la tabla se ordena por pid?

Para mostrar los resultados por proceso de forma consistente, independientemente del algoritmo.

6. ¿Qué debe hacer el simulador si la CPU está ociosa entre dos procesos?

Insertar un bloque «ociosa» en el Gantt y saltar el reloj a la llegada del siguiente proceso.

7. ¿Qué es la entrada manual y para qué sirve?

Un campo de texto donde se introducen procesos en formato pid,llegada,rafaga. Sirve para reproducir ejemplos específicos.

8. ¿Por qué se valida el simulador con casos de prueba?

Para asegurar que el simulador reproduce los resultados conocidos antes de confiar en él para cargas grandes.

9. ¿Qué parte del simulador cambia al pasar de FCFS a SJF?

Solo la función del algoritmo. El modelo, el generador, la vista y el controlador son los mismos.

10. ¿Qué extensión permite comparar dos algoritmos sobre la misma carga?

Ejecutar ambos sobre la misma carga y mostrar los dos Gantt uno encima del otro.

Para practicar

  1. Implementa el simulador completo en un archivo HTML y verifica los seis casos de prueba.
  2. Añade la entrada manual al simulador de este tema y pruébala con el ejemplo del tema 10.
  3. Implementa el modo paso a paso: un botón que avanza un evento a la vez.
  4. Añade la exportación a CSV de la tabla de métricas.
  5. Compara FCFS y SJF sobre la misma carga generada y muestra ambos Gantt.
  6. Añade la animación del Gantt bloque por bloque.