Apuntes DAM
Volver al inicio

Planificador de procesos: FCFS, SJF, SRTF y Round Robin

Ejercicio de PythonMuy difícilUnos 100 minutos

Simula cómo reparte la CPU un sistema operativo con cuatro algoritmos de planificación: diagrama de Gantt con los huecos de CPU ociosa, tiempos de fin, retorno y espera de cada proceso, medias y uso de la CPU. Colas, listas de diccionarios, min con key y simulación paso a paso.

  • Simulación por eventos
  • Colas con listas
  • min con key y tuplas
  • Funciones anidadas y nonlocal
  • Diccionarios
  • f-strings con anchos

Enunciado

Un ordenador ejecuta muchos más procesos que núcleos tiene, así que el planificador del sistema operativo decide en cada momento cuál usa la CPU. La decisión cambia mucho el tiempo que espera cada programa: es uno de los temas que más cae en los exámenes de Sistemas, siempre con el mismo tipo de ejercicio: dibujar el diagrama de Gantt y calcular tiempos.

En este ejercicio vas a escribir ese planificador. Los procesos llegan en distintos instantes con una ráfaga de CPU (lo que necesitan ejecutar), y el programa simula cuatro algoritmos clásicos: FCFS (por orden de llegada), SJF (el de ráfaga más corta entre los que esperan, sin interrumpirlo), SRTF (el de menor tiempo restante, interrumpiendo si llega uno más corto) y Round Robin (turnos de un quantum fijo).

El resultado de cada proceso se mide con dos tiempos: el de retorno (desde que llega hasta que termina) y el de espera (el retorno menos lo que de verdad ha usado la CPU). En la web tienes también el simulador visual de planificación para comprobar tus resultados.

Qué tiene que hacer el programa

  1. La primera línea es el algoritmo: FCFS, SJF, SRTF o RR q (con q, el quantum, un entero mayor que 0). Si no es válida, escribe Algoritmo no válido y nada más. Cada línea siguiente es un proceso: nombre llegada ráfaga, enteros con la ráfaga mayor que 0. Una línea con otro formato o un nombre repetido escribe Aviso: línea N ignorada. Si no queda ningún proceso, No hay procesos.
  2. Desempates: FCFS y Round Robin atienden por orden de llegada y, a igual llegada, por el orden de la entrada. SJF elige la menor ráfaga y SRTF el menor tiempo restante; si empatan, el que llegó antes y luego el de la entrada. En Round Robin, cuando a un proceso se le acaba el quantum, los procesos que han llegado hasta ese instante entran en la cola antes que él.
  3. SRTF reevalúa cada vez que llega un proceso: si el recién llegado necesita menos que lo que le queda al que está en la CPU, lo expulsa. Si nadie está listo, la CPU queda ociosa hasta la siguiente llegada.
  4. Escribe Algoritmo: … con su nombre completo (el de la tabla; en Round Robin, Round Robin con quantum q) y Diagrama: | P1 0-3 | P2 3-7 | … |: cada tramo con el proceso, el inicio y el fin, -- para la CPU ociosa, y los tramos seguidos del mismo proceso unidos en uno solo.
  5. Una tabla con la cabecera Proceso, Llegada, Ráfaga, Fin, Retorno, Espera y una fila por proceso en el orden de la entrada: el nombre en 8 caracteres a la izquierda y los números alineados a la derecha en 8, 8, 6, 9 y 8 caracteres.
  6. Para terminar, Media de retorno: X · Media de espera: Y y Uso de la CPU: Z % (tiempo ocupado entre el instante final), con dos decimales y coma decimal.

Entrada

Línea 1: FCFS, SJF, SRTF o RR q. Resto: nombre llegada ráfaga.

Datos de referencia

Nombre de cada algoritmo en la salida
EntradaAlgoritmo
FCFSFCFS (primero en llegar, primero en ser servido)
SJFSJF (el más corto primero, sin expulsión)
SRTFSRTF (menor tiempo restante, con expulsión)
RR qRound Robin con quantum q

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

Round Robin con quantum 2

Entrada

RR 2
P1 0 5
P2 1 3
P3 2 8
P4 3 6

Salida por consola

Algoritmo: Round Robin con quantum 2
Diagrama: | P1 0-2 | P2 2-4 | P3 4-6 | P1 6-8 | P4 8-10 | P2 10-11 | P3 11-13 | P1 13-14 | P4 14-16 | P3 16-18 | P4 18-20 | P3 20-22 |
Proceso  Llegada  Ráfaga   Fin  Retorno  Espera
P1             0       5    14       14       9
P2             1       3    11       10       7
P3             2       8    22       20      12
P4             3       6    20       17      11
Media de retorno: 15,25 · Media de espera: 9,75
Uso de la CPU: 100,00 %

SRTF con CPU ociosa

Entrada

SRTF
A 0 7
B 2 4
C 4 1
D 5 4
E 30 2
F x 3
A 1 1

Salida por consola

Aviso: línea 7 ignorada
Aviso: línea 8 ignorada
Algoritmo: SRTF (menor tiempo restante, con expulsión)
Diagrama: | A 0-2 | B 2-4 | C 4-5 | B 5-7 | D 7-11 | A 11-16 | -- 16-30 | E 30-32 |
Proceso  Llegada  Ráfaga   Fin  Retorno  Espera
A              0       7    16       16       9
B              2       4     7        5       1
C              4       1     5        1       0
D              5       4    11        6       2
E             30       2    32        2       0
Media de retorno: 6,00 · Media de espera: 2,40
Uso de la CPU: 56,25 %

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. El estado de la simulación

Necesitas el reloj t, la lista de procesos que aún no han llegado (ordenada por llegada), la cola de listos, lo que le queda a cada uno y el diagrama. Una función admitir(t) que pase a la cola los que ya han llegado se usa después de cada paso.

2. Un bucle para todos los algoritmos

Mientras queden procesos: si la cola está vacía, la CPU está ociosa hasta la siguiente llegada; si no, el algoritmo elige uno y lo ejecuta un tiempo. Lo único que cambia entre algoritmos es a quién se elige y durante cuánto.

3. Elegir con min y una tupla

min con una clave en forma de tupla resuelve la elección y los desempates de una vez: primero la ráfaga (o lo que queda), después la llegada y por último el orden de la entrada.

python
p = min(listos, key=lambda q: (restante[q["nombre"]], q["llegada"], orden[q["nombre"]]))
4. Expulsión en SRTF y en Round Robin

En SRTF ejecuta el proceso elegido hasta que termine o hasta la próxima llegada, lo que ocurra antes, y vuelve a elegir. En Round Robin ejecuta como mucho un quantum; después admite a los recién llegados y, si al proceso le queda ráfaga, ponlo al final de la cola.

5. Un diagrama sin tramos repetidos

Al añadir un tramo, si el último es del mismo proceso y acaba justo ahora, alárgalo en lugar de crear otro: en SRTF un proceso puede seguir en la CPU tras una llegada que no lo expulsa.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 4 casos ocultos que buscan los errores típicos.

🐍PythonPlanificador de procesos: FCFS, SJF, SRTF y Round RobinMuy difícil

Ejemplo

Entrada (lo que se escribe por teclado)
RR 2
P1 0 5
P2 1 3
P3 2 8
P4 3 6
Salida esperada
Algoritmo: Round Robin con quantum 2
Diagrama: | P1 0-2 | P2 2-4 | P3 4-6 | P1 6-8 | P4 8-10 | P2 10-11 | P3 11-13 | P1 13-14 | P4 14-16 | P3 16-18 | P4 18-20 | P3 20-22 |
Proceso  Llegada  Ráfaga   Fin  Retorno  Espera
P1             0       5    14       14       9
P2             1       3    11       10       7
P3             2       8    22       20      12
P4             3       6    20       17      11
Media de retorno: 15,25 · Media de espera: 9,75
Uso de la CPU: 100,00 %
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
⏳
Test oculto #6
0/6 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
python
1import sys
2
3NOMBRES = {"FCFS": "FCFS (primero en llegar, primero en ser servido)", "SJF": "SJF (el más corto primero, sin expulsión)",
4           "SRTF": "SRTF (menor tiempo restante, con expulsión)", "RR": "Round Robin"}
5
6
7def coma(x):
8    return f"{x:.2f}".replace(".", ",")
9
10
11def simular(algoritmo, quantum, procesos):
12    """Devuelve el diagrama de Gantt como lista de [nombre, inicio, fin] y el instante en que termina cada proceso."""
13    restante = {p["nombre"]: p["rafaga"] for p in procesos}
14    orden = {p["nombre"]: i for i, p in enumerate(procesos)}
15    pendientes = sorted(procesos, key=lambda p: (p["llegada"], orden[p["nombre"]]))
16    listos, gantt, fin = [], [], {}
17    t = 0
18
19    def admitir(hasta):
20        """Pasa a la cola de listos los procesos que han llegado hasta el instante «hasta»."""
21        while pendientes and pendientes[0]["llegada"] <= hasta:
22            listos.append(pendientes.pop(0))
23
24    def ejecutar(p, duracion):
25        nonlocal t
26        if gantt and gantt[-1][0] == p["nombre"] and gantt[-1][2] == t:
27            gantt[-1][2] += duracion          # el mismo proceso sigue: se alarga el tramo
28        else:
29            gantt.append([p["nombre"], t, t + duracion])
30        t += duracion
31        restante[p["nombre"]] -= duracion
32
33    admitir(t)
34    while listos or pendientes:
35        if not listos:                        # CPU ociosa hasta la siguiente llegada
36            gantt.append(["--", t, pendientes[0]["llegada"]])
37            t = pendientes[0]["llegada"]
38            admitir(t)
39            continue
40        if algoritmo == "FCFS":
41            p = listos.pop(0)
42            ejecutar(p, restante[p["nombre"]])
43        elif algoritmo == "SJF":
44            p = min(listos, key=lambda q: (q["rafaga"], q["llegada"], orden[q["nombre"]]))
45            listos.remove(p)
46            ejecutar(p, restante[p["nombre"]])
47        elif algoritmo == "SRTF":
48            p = min(listos, key=lambda q: (restante[q["nombre"]], q["llegada"], orden[q["nombre"]]))
49            # Se ejecuta hasta que acabe o hasta la próxima llegada, que puede expulsarlo
50            hasta = t + restante[p["nombre"]]
51            if pendientes:
52                hasta = min(hasta, pendientes[0]["llegada"])
53            ejecutar(p, hasta - t)
54            if restante[p["nombre"]] > 0:
55                admitir(t)
56                continue
57            listos.remove(p)
58        else:  # RR
59            p = listos.pop(0)
60            ejecutar(p, min(quantum, restante[p["nombre"]]))
61            admitir(t)                        # los que llegan entran en la cola antes que el expulsado
62            if restante[p["nombre"]] > 0:
63                listos.append(p)
64                continue
65        fin[p["nombre"]] = t
66        admitir(t)
67    return gantt, fin
68
69
70def main():
71    lineas = sys.stdin.read().split("\n")
72    cabecera = lineas[0].split()
73    algoritmo = cabecera[0].upper() if cabecera else ""
74    quantum = 0
75    if algoritmo == "RR":
76        if len(cabecera) != 2 or not cabecera[1].isdigit() or int(cabecera[1]) == 0:
77            print("Algoritmo no válido")
78            return
79        quantum = int(cabecera[1])
80    elif algoritmo not in NOMBRES or len(cabecera) != 1:
81        print("Algoritmo no válido")
82        return
83
84    procesos = []
85    for num, linea in enumerate(lineas[1:], start=2):
86        partes = linea.split()
87        if not partes:
88            continue
89        if len(partes) != 3 or not partes[1].isdigit() or not partes[2].isdigit() or int(partes[2]) == 0 \
90                or any(p["nombre"] == partes[0] for p in procesos):
91            print(f"Aviso: línea {num} ignorada")
92            continue
93        procesos.append({"nombre": partes[0], "llegada": int(partes[1]), "rafaga": int(partes[2])})
94    if not procesos:
95        print("No hay procesos")
96        return
97
98    gantt, fin = simular(algoritmo, quantum, procesos)
99    print("Algoritmo: " + NOMBRES[algoritmo] + (f" con quantum {quantum}" if algoritmo == "RR" else ""))
100    print("Diagrama: " + " ".join(f"| {n} {a}-{b}" for n, a, b in gantt) + " |")
101    print(f"{'Proceso':<8}{'Llegada':>8}{'Ráfaga':>8}{'Fin':>6}{'Retorno':>9}{'Espera':>8}")
102    retornos, esperas = [], []
103    for p in procesos:
104        retorno = fin[p["nombre"]] - p["llegada"]
105        espera = retorno - p["rafaga"]
106        retornos.append(retorno)
107        esperas.append(espera)
108        print(f"{p['nombre']:<8}{p['llegada']:>8}{p['rafaga']:>8}{fin[p['nombre']]:>6}{retorno:>9}{espera:>8}")
109    print(f"Media de retorno: {coma(sum(retornos) / len(procesos))} · Media de espera: {coma(sum(esperas) / len(procesos))}")
110    ocupada = sum(b - a for n, a, b in gantt if n != "--")
111    print(f"Uso de la CPU: {coma(100 * ocupada / gantt[-1][2])} %")
112
113
114main()

La simulación avanza por eventos: en lugar de mirar el reloj de unidad en unidad, salta de una decisión a la siguiente (fin de un proceso, fin de un quantum o llegada de un proceso). Es más rápido y deja claro en qué instantes decide el planificador.

Los cuatro algoritmos comparten el bucle y las funciones admitir y ejecutar; solo cambia la elección del proceso y el tiempo que se le deja. Es la misma estructura que usan los sistemas operativos reales, donde la política de planificación es un módulo intercambiable.

Las tuplas como clave de min codifican la política y los desempates en una sola expresión, y hacen que el resultado sea siempre el mismo para la misma entrada, sin depender del orden en que se guardan los procesos.

Retorno y espera salen solos al final: retorno = fin − llegada y espera = retorno − ráfaga. Comparar las medias entre algoritmos con los mismos procesos muestra por qué SJF y SRTF minimizan la espera media y por qué Round Robin, aunque espere más de media, responde antes a los procesos cortos.

Para ir más allá

  • Añade la planificación por prioridades (con y sin expulsión) y el envejecimiento para evitar la inanición.
  • Calcula también el tiempo de respuesta (desde que llega hasta que usa la CPU por primera vez) de cada proceso.
  • Simula el coste del cambio de contexto: suma 1 unidad cada vez que la CPU pasa de un proceso a otro y compara Round Robin con quantums de 1, 2 y 4.

Dónde se explica