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.
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:
| Parte | Responsabilidad | Funciones |
|---|---|---|
| Modelo | Representar un proceso y una carga de trabajo. | crearProceso, copiar |
| Generador | Producir cargas de trabajo reproducibles. | crearGenerador, generarCarga |
| Algoritmo | Ejecutar FCFS y rellenar las métricas. | fcfs |
| Vista | Dibujar el Gantt y la tabla en el DOM. | dibujarGantt, renderTabla |
| Controlador | Leer 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:
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:
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:
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:
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:
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:
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:
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:
| Caso | Entrada | Resultado esperado |
|---|---|---|
| Un proceso | P1(0,5) | Gantt: P1 0-5. Respuesta 0, espera 0, retorno 5. |
| Todos en 0 | P1(0,3), P2(0,2), P3(0,1) | Gantt: P1 0-3, P2 3-5, P3 5-6. Esperas: 0, 3, 5. |
| Llegadas distintas | P1(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 CPU | P1(0,3), P2(5,2) | Gantt: P1 0-3, ociosa 3-5, P2 5-7. Tiempo total 7. |
| Llegadas iguales | P1(2,4), P2(2,3) | Desempate por pid: P1 primero. Gantt: P1 2-6, P2 6-9. |
| Ráfaga de 1 | P1(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:
<!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:
- Modo paso a paso: un botón «siguiente» que avanza el reloj un evento a la vez, mostrando la cola de listos en cada paso.
- Exportar a CSV: un botón que descarga la tabla de métricas como archivo CSV.
- Comparar algoritmos: ejecutar FCFS y SJF sobre la misma carga y mostrar ambos Gantt uno encima del otro.
- Estadísticas adicionales: máximo de espera, desviación estándar, percentiles.
- Animación: reproducir el Gantt bloque por bloque con un temporizador.
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
| Proceso | Llegada | Ráfaga | Inicio | Fin | Respuesta | Espera | Retorno |
|---|---|---|---|---|---|---|---|
13. Resumen
- El simulador de FCFS se divide en cinco partes: modelo, generador, algoritmo, vista y controlador.
- El modelo es un objeto con campos de entrada y de salida; la función
copiarprotege la entrada. - La función
fcfsordena por llegada, recorre la línea de tiempo y maneja la CPU ociosa. - El Gantt se dibuja con spans flexibles; los huecos se insertan como spans «ociosa».
- La tabla se ordena por pid para mostrar los resultados por proceso.
- Los controles regeneran la carga y recalculan al cambiar cualquier parámetro.
- La entrada manual permite reproducir ejemplos específicos.
- Los casos de prueba verifican el simulador contra resultados conocidos.
- Este simulador es la plantilla para todos los demás algoritmos del curso.
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
- Implementa el simulador completo en un archivo HTML y verifica los seis casos de prueba.
- Añade la entrada manual al simulador de este tema y pruébala con el ejemplo del tema 10.
- Implementa el modo paso a paso: un botón que avanza un evento a la vez.
- Añade la exportación a CSV de la tabla de métricas.
- Compara FCFS y SJF sobre la misma carga generada y muestra ambos Gantt.
- Añade la animación del Gantt bloque por bloque.