feat: add MLFQ scheduling algorithm and thread scheduler
- Implemented MLFQ (Multilevel Feedback Queue) algorithm with dynamic priority levels and quantum management. - Created unit tests for MLFQ algorithm covering initialization, demotion, selection, priority boost, preemption, and quantum expiration. - Developed a ThreadScheduler class supporting FCFS, RR, and PRIORITY scheduling policies for intra-process thread management. - Added unit tests for ThreadScheduler to validate thread selection and quantum expiration logic.
This commit is contained in:
1 parent
532c14e382
commit
5761dfe70a
34 files changed
+3789
-334
No files matched your search
+667
-137
@@ -4,9 +4,12 @@ import type {
|
||||
SchedulerConfig,
|
||||
SimTick,
|
||||
StateLabel,
|
||||
Thread,
|
||||
ThreadRuntime,
|
||||
} from "./types";
|
||||
import { algorithms } from "./algorithms/index";
|
||||
import type { BaseAlgorithm } from "./algorithms/index";
|
||||
import { ThreadScheduler } from "./thread-scheduler";
|
||||
|
||||
/**
|
||||
* SimulationEngine — Núcleo de execução da simulação de escalonamento.
|
||||
@@ -14,42 +17,66 @@ import type { BaseAlgorithm } from "./algorithms/index";
|
||||
* Responsável por avançar o simulador tick a tick, aplicando o algoritmo
|
||||
* de escalonamento configurado e mantendo o estado de todos os processos.
|
||||
*
|
||||
* Suporta dois modos de operação:
|
||||
* - Processos simples: comportamento original, sem alterações.
|
||||
* - Processos com threads: escalonamento em dois níveis:
|
||||
* 1. Nível de processo — algoritmo global do SchedulerConfig.
|
||||
* 2. Nível de thread — ThreadScheduler com política intra-processo.
|
||||
*
|
||||
* Modelos de threads suportados:
|
||||
* MANY_TO_ONE — Uma kernel thread por processo. I/O bloqueia todo o processo.
|
||||
* ONE_TO_ONE — Uma kernel thread por thread de usuário. Threads bloqueiam
|
||||
* independentemente; o processo só bloqueia se todas as threads
|
||||
* estiverem em I/O.
|
||||
* MANY_TO_MANY — M kernel threads para N threads de usuário (M ≤ N).
|
||||
* Slots são liberados durante I/O e reatribuídos dinamicamente.
|
||||
*
|
||||
* Ciclo de vida de um único tick (método `step()`):
|
||||
* 1. Chegada — processos cujo `arrivalTime` é igual ao tick atual
|
||||
* transitam New → Ready e entram na fila de prontos.
|
||||
* 2. Preempção — o algoritmo decide se o processo em CPU deve ceder
|
||||
* lugar a um candidato mais prioritário.
|
||||
* 3. Despacho — seleciona e despacha o próximo processo (com overhead
|
||||
* de troca de contexto, se configurado).
|
||||
* 4. Snapshot — captura o estado de todos os processos neste tick.
|
||||
* 5. Passagem do tempo — decrementa bursts, acumula tempos de CPU/IO/espera.
|
||||
* 6. Aging — decrementa prioridade dos processos em espera prolongada.
|
||||
* 7. Conclusões — trata término de bursts de CPU e IO; avança para o
|
||||
* próximo burst ou termina o processo.
|
||||
*
|
||||
* Uso típico:
|
||||
* ```ts
|
||||
* const engine = new SimulationEngine(processes, config);
|
||||
* const ticks = engine.runAll(); // executa até todos os processos terminarem
|
||||
* ```
|
||||
*
|
||||
* Invariante: após `runAll()`, `isFinished` é `true` e todos os runtimes
|
||||
* têm `state === "Terminated"`.
|
||||
* 1. Chegada — New → Ready para processos/threads que chegam neste tick.
|
||||
* 2. Preempção — Algoritmo decide se processo em CPU deve ceder lugar.
|
||||
* 3. Despacho — Seleciona e despacha próximo processo (e thread).
|
||||
* 4. Snapshot — Captura estado completo (processos + threads).
|
||||
* 5. Passagem do tempo — Decrementa bursts, acumula tempos.
|
||||
* 6. Aging — Envelhecimento de prioridade anti-starvation.
|
||||
* 7. Conclusões — Trata término de bursts de CPU/IO (processo e threads).
|
||||
*/
|
||||
export class SimulationEngine {
|
||||
// ─── Campos privados ──────────────────────────────────────────────────────
|
||||
// ─── Campos privados — processos ──────────────────────────────────────────
|
||||
|
||||
private readonly _processes: Process[];
|
||||
private readonly _config: SchedulerConfig;
|
||||
private readonly _algorithm: BaseAlgorithm;
|
||||
|
||||
private _tick: number; // começa em -1; incrementado no início de step()
|
||||
private _tick: number;
|
||||
private _runtimes: Map<string, ProcessRuntime>;
|
||||
private _cpuProcess: ProcessRuntime | null;
|
||||
private _readyQueue: ProcessRuntime[]; // array ordenado (FIFO para RR)
|
||||
private _readyQueue: ProcessRuntime[];
|
||||
private _ioQueue: ProcessRuntime[];
|
||||
private _contextSwitchRemaining: number;
|
||||
private _prevCpuProcessId: string | null; // para detectar troca de contexto
|
||||
private _prevCpuProcessId: string | null;
|
||||
|
||||
// ─── Campos privados — threads ────────────────────────────────────────────
|
||||
|
||||
/** threadRuntimes[processId][threadId] = ThreadRuntime */
|
||||
private _threadRuntimes: Map<string, Map<string, ThreadRuntime>>;
|
||||
|
||||
/** Escalonador intra-processo por processo */
|
||||
private _threadSchedulers: Map<string, ThreadScheduler>;
|
||||
|
||||
/** Thread atualmente ativa na CPU para cada processo com threads */
|
||||
private _activeThreads: Map<string, ThreadRuntime | null>;
|
||||
|
||||
/** Threads em I/O por processo */
|
||||
private _threadIoQueues: Map<string, ThreadRuntime[]>;
|
||||
|
||||
/** Fila de threads prontas intra-processo */
|
||||
private _readyThreadQueues: Map<string, ThreadRuntime[]>;
|
||||
|
||||
/** MANY_TO_MANY: slots de kernel disponíveis por processo */
|
||||
private _kernelSlotsAvailable: Map<string, number>;
|
||||
|
||||
/** MANY_TO_MANY: threads aguardando slot de kernel */
|
||||
private _kernelWaitQueues: Map<string, ThreadRuntime[]>;
|
||||
|
||||
// ─── Construtor ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -64,37 +91,47 @@ export class SimulationEngine {
|
||||
this._ioQueue = [];
|
||||
this._contextSwitchRemaining = 0;
|
||||
this._prevCpuProcessId = null;
|
||||
|
||||
// Inicializa runtimes de processo
|
||||
this._runtimes = new Map(
|
||||
processes.map((p) => [p.id, SimulationEngine._initRuntime(p, config)]),
|
||||
processes.map((p) => [p.id, SimulationEngine._initProcessRuntime(p, config)]),
|
||||
);
|
||||
|
||||
// Inicializa estruturas de threads
|
||||
this._threadRuntimes = new Map();
|
||||
this._threadSchedulers = new Map();
|
||||
this._activeThreads = new Map();
|
||||
this._threadIoQueues = new Map();
|
||||
this._readyThreadQueues = new Map();
|
||||
this._kernelSlotsAvailable = new Map();
|
||||
this._kernelWaitQueues = new Map();
|
||||
|
||||
for (const p of processes) {
|
||||
if (this._hasThreads(p.id)) {
|
||||
this._initThreadStructures(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── API pública ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Avança a simulação por um tick e retorna o snapshot do estado neste tick.
|
||||
*
|
||||
* Lança um erro se a simulação já tiver terminado (`isFinished === true`).
|
||||
* O número do tick é incrementado no início; o primeiro tick válido é 0.
|
||||
*/
|
||||
step(): SimTick {
|
||||
if (this.isFinished) throw new Error("Simulation is already finished");
|
||||
|
||||
this._tick++;
|
||||
|
||||
this._handleArrivals(); // 1. New → Ready para quem chega agora
|
||||
this._checkPreemption(); // 2. Preempta CPU se candidato melhor chegou
|
||||
this._dispatchNext(); // 3. Despacha próximo (ou inicia context switch)
|
||||
const snapshot = this._snapshot(); // 4. Captura estado DURANTE este tick
|
||||
this._advanceTime(); // 5. Decrementa bursts, acumula métricas
|
||||
this._applyAging(); // 6. Aging de prioridade (se habilitado)
|
||||
this._handleCpuCompletion(); // 7a. Finaliza burst/quantum de CPU
|
||||
this._handleIoCompletions(); // 7b. Finaliza bursts de I/O
|
||||
this._handleArrivals();
|
||||
this._checkPreemption();
|
||||
this._dispatchNext();
|
||||
const snapshot = this._snapshot();
|
||||
this._advanceTime();
|
||||
this._applyAging();
|
||||
this._handleCpuCompletion();
|
||||
this._handleIoCompletions();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/** Executa até o fim e retorna todos os snapshots de tick. */
|
||||
runAll(): SimTick[] {
|
||||
const results: SimTick[] = [];
|
||||
while (!this.isFinished) {
|
||||
@@ -106,7 +143,6 @@ export class SimulationEngine {
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Verdadeiro quando todos os processos estão no estado "Terminated". */
|
||||
get isFinished(): boolean {
|
||||
for (const rt of this._runtimes.values()) {
|
||||
if (rt.state !== "Terminated") return false;
|
||||
@@ -114,23 +150,34 @@ export class SimulationEngine {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Número do tick atual (−1 antes do primeiro `step()`). */
|
||||
get currentTick(): number {
|
||||
return this._tick;
|
||||
}
|
||||
|
||||
/** Array com o estado de runtime de todos os processos. */
|
||||
get runtimeStates(): ProcessRuntime[] {
|
||||
return Array.from(this._runtimes.values());
|
||||
}
|
||||
|
||||
/** Retorna todos os runtimes de threads de todos os processos. */
|
||||
get threadRuntimeStates(): ThreadRuntime[] {
|
||||
const result: ThreadRuntime[] = [];
|
||||
for (const threadMap of this._threadRuntimes.values()) {
|
||||
for (const trt of threadMap.values()) {
|
||||
result.push(trt);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Informa se um processo possui threads definidas. */
|
||||
processHasThreads(processId: string): boolean {
|
||||
return this._hasThreads(processId);
|
||||
}
|
||||
|
||||
// ─── Fases do ciclo de simulação ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fase 1 — Chegada de processos.
|
||||
*
|
||||
* Processos cujo `arrivalTime` é igual ao tick atual transitam do estado
|
||||
* "New" para "Ready" e são inseridos na fila de prontos.
|
||||
* Fase 1 — Chegada de processos (e inicialização de threads).
|
||||
*/
|
||||
private _handleArrivals(): void {
|
||||
for (const p of this._processes) {
|
||||
@@ -138,17 +185,40 @@ export class SimulationEngine {
|
||||
if (p.arrivalTime === this._tick && rt.state === "New") {
|
||||
this._transition(rt, "Ready");
|
||||
this._readyQueue.push(rt);
|
||||
|
||||
if (this._hasThreads(p.id)) {
|
||||
// Transiciona todas as threads New → Ready na fila intra-processo
|
||||
const threadMap = this._threadRuntimes.get(p.id)!;
|
||||
const readyQueue = this._readyThreadQueues.get(p.id)!;
|
||||
const model = p.threadModel ?? "ONE_TO_ONE";
|
||||
|
||||
for (const trt of threadMap.values()) {
|
||||
if (trt.state === "New") {
|
||||
if (model === "MANY_TO_MANY") {
|
||||
const slotsAvail = this._kernelSlotsAvailable.get(p.id) ?? 0;
|
||||
if (slotsAvail > 0) {
|
||||
this._kernelSlotsAvailable.set(p.id, slotsAvail - 1);
|
||||
this._transitionThread(trt, "Ready");
|
||||
readyQueue.push(trt);
|
||||
} else {
|
||||
// Sem slot disponível → espera kernel thread
|
||||
trt.kernelWaiting = true;
|
||||
this._transitionThread(trt, "Ready");
|
||||
this._kernelWaitQueues.get(p.id)!.push(trt);
|
||||
}
|
||||
} else {
|
||||
this._transitionThread(trt, "Ready");
|
||||
readyQueue.push(trt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fase 2 — Verificação de preempção.
|
||||
*
|
||||
* Consulta o algoritmo: se houver um candidato na fila de prontos que deva
|
||||
* preemptar o processo em CPU, o processo atual retorna à fila de prontos
|
||||
* e `_cpuProcess` é zerado. Chamado após a chegada para que processos
|
||||
* recém-chegados possam imediatamente preemptar.
|
||||
* Fase 2 — Verificação de preempção no nível de processo.
|
||||
*/
|
||||
private _checkPreemption(): void {
|
||||
if (this._cpuProcess === null || this._readyQueue.length === 0) return;
|
||||
@@ -158,6 +228,17 @@ export class SimulationEngine {
|
||||
this._algorithm.shouldPreempt(this._cpuProcess, candidate, this._config)
|
||||
) {
|
||||
const cpu = this._cpuProcess;
|
||||
|
||||
// Retorna thread ativa à fila intra-processo (se processo com threads)
|
||||
if (this._hasThreads(cpu.processId)) {
|
||||
const activeThread = this._activeThreads.get(cpu.processId) ?? null;
|
||||
if (activeThread !== null) {
|
||||
this._transitionThread(activeThread, "Ready");
|
||||
this._readyThreadQueues.get(cpu.processId)!.unshift(activeThread);
|
||||
this._activeThreads.set(cpu.processId, null);
|
||||
}
|
||||
}
|
||||
|
||||
this._transition(cpu, "Ready");
|
||||
this._readyQueue.push(cpu);
|
||||
this._cpuProcess = null;
|
||||
@@ -165,16 +246,7 @@ export class SimulationEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fase 3 — Despacho / troca de contexto.
|
||||
*
|
||||
* Se um context switch estiver em andamento, nenhum processo é despachado
|
||||
* neste tick. Caso contrário, e se a CPU estiver ociosa com processos
|
||||
* prontos, o algoritmo seleciona o próximo processo:
|
||||
*
|
||||
* - Se houver overhead de troca de contexto configurado e o processo
|
||||
* selecionado for diferente do último, o processo volta ao início da
|
||||
* fila e o contador de context-switch é iniciado.
|
||||
* - Caso contrário, o processo é despachado imediatamente para a CPU.
|
||||
* Fase 3 — Despacho de processo (e thread).
|
||||
*/
|
||||
private _dispatchNext(): void {
|
||||
if (this._contextSwitchRemaining > 0) return;
|
||||
@@ -196,21 +268,71 @@ export class SimulationEngine {
|
||||
this._prevCpuProcessId = next.processId;
|
||||
} else {
|
||||
if (next.startTick === null) next.startTick = this._tick;
|
||||
next.quantumRemaining = this._config.quantum;
|
||||
next.quantumRemaining = this._algorithm.getQuantumForProcess(next, this._config);
|
||||
this._transition(next, "Running");
|
||||
this._prevCpuProcessId = next.processId;
|
||||
this._cpuProcess = next;
|
||||
|
||||
// Despacha thread intra-processo (se aplicável)
|
||||
if (this._hasThreads(next.processId)) {
|
||||
this._dispatchThreadForProcess(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fase 4 — Snapshot do estado.
|
||||
*/
|
||||
private _snapshot(): SimTick {
|
||||
const states: Record<string, StateLabel> = {};
|
||||
for (const [id, rt] of this._runtimes) {
|
||||
states[id] = rt.state;
|
||||
}
|
||||
const contextSwitching = this._contextSwitchRemaining > 0;
|
||||
|
||||
const snap: SimTick = {
|
||||
tick: this._tick,
|
||||
cpuProcess: contextSwitching ? null : (this._cpuProcess?.processId ?? null),
|
||||
ioProcesses: this._ioQueue.map((rt) => rt.processId),
|
||||
readyQueue: this._readyQueue.map((rt) => rt.processId),
|
||||
states,
|
||||
contextSwitching,
|
||||
ctxSwitchForProcess: contextSwitching
|
||||
? (this._readyQueue[0]?.processId ?? null)
|
||||
: null,
|
||||
};
|
||||
|
||||
// Inclui estado de threads se houver processos com threads
|
||||
if (this._threadRuntimes.size > 0) {
|
||||
const threadStates: Record<string, Record<string, StateLabel>> = {};
|
||||
const cpuThreads: Record<string, string | null> = {};
|
||||
const kernelWaitingThreads: Record<string, string[]> = {};
|
||||
|
||||
for (const [processId, threadMap] of this._threadRuntimes) {
|
||||
threadStates[processId] = {};
|
||||
for (const [threadId, trt] of threadMap) {
|
||||
threadStates[processId][threadId] = trt.state;
|
||||
}
|
||||
const active = this._activeThreads.get(processId) ?? null;
|
||||
cpuThreads[processId] = active?.threadId ?? null;
|
||||
const kwQueue = this._kernelWaitQueues.get(processId) ?? [];
|
||||
if (kwQueue.length > 0) {
|
||||
kernelWaitingThreads[processId] = kwQueue.map((t) => t.threadId);
|
||||
}
|
||||
}
|
||||
|
||||
snap.threadStates = threadStates;
|
||||
snap.cpuThreads = cpuThreads;
|
||||
if (Object.keys(kernelWaitingThreads).length > 0) {
|
||||
snap.kernelWaitingThreads = kernelWaitingThreads;
|
||||
}
|
||||
}
|
||||
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fase 5 — Passagem do tempo.
|
||||
*
|
||||
* Simula a execução de 1 tick:
|
||||
* - Decrementa o contador de context-switch restante (se ativo).
|
||||
* - Decrementa o burst restante do processo em CPU e acumula cpuTime/quantum.
|
||||
* - Decrementa o burst restante de cada processo em I/O e acumula ioTime.
|
||||
* - Incrementa o waitingTime de todos os processos na fila de prontos.
|
||||
*/
|
||||
private _advanceTime(): void {
|
||||
if (this._contextSwitchRemaining > 0) {
|
||||
@@ -219,29 +341,67 @@ export class SimulationEngine {
|
||||
|
||||
if (this._cpuProcess !== null) {
|
||||
const cpu = this._cpuProcess;
|
||||
cpu.remainingBurst--;
|
||||
cpu.cpuTime++;
|
||||
cpu.quantumRemaining--;
|
||||
|
||||
if (this._hasThreads(cpu.processId)) {
|
||||
// CPU time acumulado no nível de processo
|
||||
cpu.cpuTime++;
|
||||
cpu.quantumRemaining--;
|
||||
|
||||
// Decrementa burst e tempo da thread ativa
|
||||
const activeThread = this._activeThreads.get(cpu.processId) ?? null;
|
||||
if (activeThread !== null) {
|
||||
activeThread.remainingBurst--;
|
||||
activeThread.cpuTime++;
|
||||
activeThread.quantumRemaining--;
|
||||
// Sincroniza processo com thread ativa
|
||||
cpu.remainingBurst = activeThread.remainingBurst;
|
||||
}
|
||||
} else {
|
||||
cpu.remainingBurst--;
|
||||
cpu.cpuTime++;
|
||||
cpu.quantumRemaining--;
|
||||
}
|
||||
}
|
||||
|
||||
// I/O de processos sem threads
|
||||
for (const rt of this._ioQueue) {
|
||||
rt.remainingBurst--;
|
||||
rt.ioTime++;
|
||||
if (!this._hasThreads(rt.processId)) {
|
||||
rt.remainingBurst--;
|
||||
rt.ioTime++;
|
||||
}
|
||||
// Processos com threads (MANY_TO_ONE): tempo de I/O acumulado abaixo
|
||||
}
|
||||
|
||||
// I/O de threads (todos os modelos)
|
||||
for (const [, threadIoQueue] of this._threadIoQueues) {
|
||||
for (const trt of threadIoQueue) {
|
||||
trt.remainingBurst--;
|
||||
trt.ioTime++;
|
||||
}
|
||||
}
|
||||
|
||||
// Tempo de espera — fila de prontos de processos
|
||||
for (const rt of this._readyQueue) {
|
||||
rt.waitingTime++;
|
||||
}
|
||||
|
||||
// Tempo de espera — fila de prontas de threads intra-processo
|
||||
for (const [, threadReadyQueue] of this._readyThreadQueues) {
|
||||
for (const trt of threadReadyQueue) {
|
||||
trt.waitingTime++;
|
||||
}
|
||||
}
|
||||
|
||||
// Tempo de espera — threads em kernel wait (MANY_TO_MANY)
|
||||
for (const [, kwQueue] of this._kernelWaitQueues) {
|
||||
for (const trt of kwQueue) {
|
||||
trt.waitingTime++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fase 6 — Aging (envelhecimento de prioridade).
|
||||
*
|
||||
* Quando o aging está habilitado, a cada `agingInterval` ticks o valor de
|
||||
* `currentPriority` de todos os processos na fila de prontos é decrementado
|
||||
* em 1 (até o mínimo de 1). Isso previne inanição em algoritmos de prioridade.
|
||||
*
|
||||
* Não é executado no tick 0 para evitar decremento prematuro.
|
||||
* Fase 6 — Aging de prioridade.
|
||||
*/
|
||||
private _applyAging(): void {
|
||||
if (
|
||||
@@ -258,24 +418,20 @@ export class SimulationEngine {
|
||||
|
||||
/**
|
||||
* Fase 7a — Conclusão de burst de CPU.
|
||||
*
|
||||
* Após a passagem do tempo, verifica se o processo em CPU terminou seu burst
|
||||
* ou esgotou o quantum:
|
||||
*
|
||||
* - Burst zerado: avança para o próximo burst do processo.
|
||||
* · Se o próximo for I/O → transita para "Waiting" e entra na ioQueue.
|
||||
* · Se o próximo for CPU → retorna à fila de prontos ("Ready").
|
||||
* · Se não houver próximo → termina o processo ("Terminated").
|
||||
*
|
||||
* - Quantum expirado (sem término de burst): retorna à fila de prontos.
|
||||
*/
|
||||
private _handleCpuCompletion(): void {
|
||||
if (this._cpuProcess === null) return;
|
||||
|
||||
const cpu = this._cpuProcess;
|
||||
|
||||
if (this._hasThreads(cpu.processId)) {
|
||||
this._handleThreadedCpuCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Processo sem threads (comportamento original) ──────────────────────
|
||||
if (cpu.remainingBurst === 0) {
|
||||
this._advanceBurst(cpu);
|
||||
cpu.quantumRemaining = 1;
|
||||
const process = this._processes.find((p) => p.id === cpu.processId)!;
|
||||
const nextBurst = process.bursts[cpu.burstIndex];
|
||||
if (nextBurst !== undefined && nextBurst.type === "io") {
|
||||
@@ -298,16 +454,16 @@ export class SimulationEngine {
|
||||
|
||||
/**
|
||||
* Fase 7b — Conclusão de bursts de I/O.
|
||||
*
|
||||
* Percorre a ioQueue e, para cada processo cujo burst de I/O zerou:
|
||||
* - Se houver próximo burst → transita para "Ready" e insere na readyQueue.
|
||||
* - Caso contrário → termina o processo ("Terminated").
|
||||
*
|
||||
* Processos ainda com I/O pendente permanecem na ioQueue.
|
||||
*/
|
||||
private _handleIoCompletions(): void {
|
||||
// ── Processos sem threads ──────────────────────────────────────────────
|
||||
const nextIoQueue: ProcessRuntime[] = [];
|
||||
for (const rt of this._ioQueue) {
|
||||
if (this._hasThreads(rt.processId)) {
|
||||
// Processos com threads gerenciados em _handleThreadIoCompletions
|
||||
nextIoQueue.push(rt);
|
||||
continue;
|
||||
}
|
||||
if (rt.remainingBurst === 0) {
|
||||
this._advanceBurst(rt);
|
||||
const process = this._processes.find((p) => p.id === rt.processId)!;
|
||||
@@ -323,22 +479,341 @@ export class SimulationEngine {
|
||||
}
|
||||
}
|
||||
this._ioQueue = nextIoQueue;
|
||||
|
||||
// ── Threads em I/O ────────────────────────────────────────────────────
|
||||
this._handleThreadIoCompletions();
|
||||
}
|
||||
|
||||
// ─── Lógica de threads ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Despacha a próxima thread intra-processo quando o processo recebe CPU.
|
||||
*/
|
||||
private _dispatchThreadForProcess(processRt: ProcessRuntime): void {
|
||||
const processId = processRt.processId;
|
||||
const readyThreadQueue = this._readyThreadQueues.get(processId)!;
|
||||
const scheduler = this._threadSchedulers.get(processId)!;
|
||||
|
||||
const nextThread = scheduler.select(readyThreadQueue);
|
||||
if (nextThread === null) {
|
||||
// Nenhuma thread pronta — mantém processo em Running mas sem thread ativa
|
||||
// (Pode ocorrer se todas as threads estão em I/O; ONE_TO_ONE pode despachar
|
||||
// o processo mesmo assim para que ele "exista" na CPU enquanto threads esperam)
|
||||
this._activeThreads.set(processId, null);
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = readyThreadQueue.indexOf(nextThread);
|
||||
if (idx !== -1) readyThreadQueue.splice(idx, 1);
|
||||
|
||||
if (nextThread.startTick === null) {
|
||||
nextThread.startTick = this._tick;
|
||||
nextThread.responseTime = this._tick - nextThread.arrivalTick;
|
||||
}
|
||||
nextThread.quantumRemaining = scheduler.quantum;
|
||||
nextThread.kernelWaiting = false;
|
||||
this._transitionThread(nextThread, "Running");
|
||||
this._activeThreads.set(processId, nextThread);
|
||||
|
||||
// Sincroniza process runtime com thread ativa
|
||||
processRt.remainingBurst = nextThread.remainingBurst;
|
||||
if (processRt.startTick === null) {
|
||||
processRt.startTick = this._tick;
|
||||
processRt.responseTime = this._tick - processRt.arrivalTick;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gerencia conclusão de burst de CPU para processos com threads.
|
||||
*/
|
||||
private _handleThreadedCpuCompletion(): void {
|
||||
const cpu = this._cpuProcess!;
|
||||
const processId = cpu.processId;
|
||||
const process = this._processes.find((p) => p.id === processId)!;
|
||||
const policy = process.threadSchedulingPolicy ?? "FCFS";
|
||||
const scheduler = this._threadSchedulers.get(processId)!;
|
||||
const activeThread = this._activeThreads.get(processId) ?? null;
|
||||
|
||||
if (activeThread === null) {
|
||||
// Sem thread ativa — verifica se processo deve continuar ou aguardar
|
||||
if (this._readyThreadQueues.get(processId)!.length > 0) {
|
||||
this._dispatchThreadForProcess(cpu);
|
||||
} else {
|
||||
// Todas as threads em I/O: processo vai para Waiting
|
||||
this._transition(cpu, "Waiting");
|
||||
if (!this._ioQueue.includes(cpu)) this._ioQueue.push(cpu);
|
||||
this._cpuProcess = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeThread.remainingBurst === 0) {
|
||||
// ── CPU burst da thread concluído ────────────────────────────────────
|
||||
this._activeThreads.set(processId, null);
|
||||
this._handleThreadBurstComplete(cpu, activeThread, process);
|
||||
} else if (scheduler.isQuantumExpired(activeThread)) {
|
||||
// ── Quantum intra-processo expirou (RR): rotaciona thread ────────────
|
||||
this._transitionThread(activeThread, "Ready");
|
||||
this._readyThreadQueues.get(processId)!.push(activeThread); // reinserido no fim (FIFO)
|
||||
this._activeThreads.set(processId, null);
|
||||
|
||||
// Seleciona próxima thread — processo permanece na CPU
|
||||
this._dispatchThreadForProcess(cpu);
|
||||
|
||||
// Se não há próxima thread pronta, processo aguarda
|
||||
if (this._activeThreads.get(processId) === null) {
|
||||
this._transition(cpu, "Waiting");
|
||||
if (!this._ioQueue.includes(cpu)) this._ioQueue.push(cpu);
|
||||
this._cpuProcess = null;
|
||||
}
|
||||
} else if (this._algorithm.isQuantumExpired(cpu, this._config)) {
|
||||
// ── Quantum de processo expirou: devolve processo à fila global ───────
|
||||
// Thread volta à fila intra-processo
|
||||
this._transitionThread(activeThread, "Ready");
|
||||
if (policy === "RR") {
|
||||
this._readyThreadQueues.get(processId)!.push(activeThread);
|
||||
} else {
|
||||
this._readyThreadQueues.get(processId)!.unshift(activeThread);
|
||||
}
|
||||
this._activeThreads.set(processId, null);
|
||||
this._transition(cpu, "Ready");
|
||||
this._readyQueue.push(cpu);
|
||||
this._cpuProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trata conclusão do burst de CPU de uma thread específica.
|
||||
* Decide o próximo estado da thread e do processo conforme o modelo.
|
||||
*/
|
||||
private _handleThreadBurstComplete(
|
||||
processRt: ProcessRuntime,
|
||||
thread: ThreadRuntime,
|
||||
process: Process,
|
||||
): void {
|
||||
const processId = process.id;
|
||||
const model = process.threadModel ?? "ONE_TO_ONE";
|
||||
const threadDef = process.threads!.find((t) => t.tid === thread.threadId)!;
|
||||
|
||||
// Avança ao próximo burst da thread
|
||||
thread.burstIndex++;
|
||||
const nextBurst = threadDef.bursts[thread.burstIndex];
|
||||
thread.quantumRemaining = 1; // sentinela anti-falsa-demoção
|
||||
|
||||
if (nextBurst !== undefined && nextBurst.type === "io") {
|
||||
// ── Thread vai para I/O ───────────────────────────────────────────────
|
||||
thread.remainingBurst = nextBurst.duration;
|
||||
this._transitionThread(thread, "Waiting");
|
||||
this._threadIoQueues.get(processId)!.push(thread);
|
||||
|
||||
// Para MANY_TO_MANY: libera slot de kernel
|
||||
if (model === "MANY_TO_MANY") {
|
||||
this._releaseKernelSlot(processId);
|
||||
}
|
||||
|
||||
if (model === "MANY_TO_ONE") {
|
||||
// Kernel thread bloqueado → bloqueia todo o processo
|
||||
this._transition(processRt, "Waiting");
|
||||
this._ioQueue.push(processRt);
|
||||
this._cpuProcess = null;
|
||||
} else {
|
||||
// ONE_TO_ONE / MANY_TO_MANY: verifica se há outra thread pronta
|
||||
this._trySelectNextThreadOrBlock(processRt, process);
|
||||
}
|
||||
} else if (nextBurst !== undefined && nextBurst.type === "cpu") {
|
||||
// ── Thread tem próximo burst de CPU ───────────────────────────────────
|
||||
thread.remainingBurst = nextBurst.duration;
|
||||
this._transitionThread(thread, "Ready");
|
||||
this._readyThreadQueues.get(processId)!.push(thread);
|
||||
// Processo permanece na CPU; seleciona próxima thread
|
||||
this._dispatchThreadForProcess(processRt);
|
||||
} else {
|
||||
// ── Thread terminada ──────────────────────────────────────────────────
|
||||
this._transitionThread(thread, "Terminated");
|
||||
thread.finishTick = this._tick + 1;
|
||||
|
||||
// Para MANY_TO_MANY: libera slot de kernel
|
||||
if (model === "MANY_TO_MANY") {
|
||||
this._releaseKernelSlot(processId);
|
||||
}
|
||||
|
||||
// Verifica se processo deve terminar ou continuar com outra thread
|
||||
if (this._allThreadsTerminated(processId)) {
|
||||
this._transition(processRt, "Terminated");
|
||||
processRt.finishTick = this._tick + 1;
|
||||
this._cpuProcess = null;
|
||||
} else if (model === "MANY_TO_ONE") {
|
||||
// Seleciona próxima thread pronta (se houver)
|
||||
this._trySelectNextThreadOrBlock(processRt, process);
|
||||
} else {
|
||||
this._trySelectNextThreadOrBlock(processRt, process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tenta selecionar a próxima thread pronta para o processo.
|
||||
* Se não houver threads prontas e há threads em I/O: processo aguarda.
|
||||
* Se não houver threads prontas e todas terminadas: processo termina.
|
||||
*/
|
||||
private _trySelectNextThreadOrBlock(
|
||||
processRt: ProcessRuntime,
|
||||
process: Process,
|
||||
): void {
|
||||
const processId = process.id;
|
||||
const readyThreadQueue = this._readyThreadQueues.get(processId)!;
|
||||
|
||||
if (readyThreadQueue.length > 0) {
|
||||
// Há threads prontas: seleciona a próxima
|
||||
this._dispatchThreadForProcess(processRt);
|
||||
} else if (this._allThreadsTerminated(processId)) {
|
||||
this._transition(processRt, "Terminated");
|
||||
processRt.finishTick = this._tick + 1;
|
||||
this._cpuProcess = null;
|
||||
} else {
|
||||
// Todas as threads em I/O: processo aguarda
|
||||
this._transition(processRt, "Waiting");
|
||||
if (!this._ioQueue.includes(processRt)) {
|
||||
this._ioQueue.push(processRt);
|
||||
}
|
||||
this._cpuProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gerencia conclusão de bursts de I/O para threads.
|
||||
*/
|
||||
private _handleThreadIoCompletions(): void {
|
||||
for (const [processId, threadIoQueue] of this._threadIoQueues) {
|
||||
const processRt = this._runtimes.get(processId)!;
|
||||
const process = this._processes.find((p) => p.id === processId)!;
|
||||
const model = process.threadModel ?? "ONE_TO_ONE";
|
||||
|
||||
const remaining: ThreadRuntime[] = [];
|
||||
|
||||
for (const trt of threadIoQueue) {
|
||||
if (trt.remainingBurst === 0) {
|
||||
// ── I/O da thread concluído ────────────────────────────────────────
|
||||
const threadDef = process.threads!.find((t) => t.tid === trt.threadId)!;
|
||||
trt.burstIndex++;
|
||||
const nextBurst = threadDef.bursts[trt.burstIndex];
|
||||
trt.quantumRemaining = 1; // sentinela
|
||||
|
||||
if (nextBurst !== undefined && nextBurst.type === "cpu") {
|
||||
trt.remainingBurst = nextBurst.duration;
|
||||
|
||||
if (model === "MANY_TO_MANY") {
|
||||
// Tenta adquirir slot de kernel
|
||||
const slotsAvail = this._kernelSlotsAvailable.get(processId) ?? 0;
|
||||
if (slotsAvail > 0) {
|
||||
this._kernelSlotsAvailable.set(processId, slotsAvail - 1);
|
||||
trt.kernelWaiting = false;
|
||||
this._transitionThread(trt, "Ready");
|
||||
this._readyThreadQueues.get(processId)!.push(trt);
|
||||
} else {
|
||||
// Sem slot: vai para kernel wait queue
|
||||
trt.kernelWaiting = true;
|
||||
this._kernelWaitQueues.get(processId)!.push(trt);
|
||||
}
|
||||
} else {
|
||||
this._transitionThread(trt, "Ready");
|
||||
this._readyThreadQueues.get(processId)!.push(trt);
|
||||
}
|
||||
} else {
|
||||
// Thread termina após I/O (ou sem próximo burst)
|
||||
this._transitionThread(trt, "Terminated");
|
||||
trt.finishTick = this._tick + 1;
|
||||
if (model === "MANY_TO_MANY") {
|
||||
// Não havia slot em uso durante I/O; nada a liberar
|
||||
}
|
||||
}
|
||||
|
||||
// Decide estado do processo com base no modelo
|
||||
this._updateProcessStateAfterThreadIo(processRt, process, model);
|
||||
} else {
|
||||
remaining.push(trt);
|
||||
}
|
||||
}
|
||||
|
||||
this._threadIoQueues.set(processId, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza o estado do processo após conclusão de I/O de uma thread.
|
||||
*/
|
||||
private _updateProcessStateAfterThreadIo(
|
||||
processRt: ProcessRuntime,
|
||||
process: Process,
|
||||
model: string,
|
||||
): void {
|
||||
const processId = process.id;
|
||||
|
||||
if (model === "MANY_TO_ONE") {
|
||||
// O processo estava bloqueado (Waiting) devido ao I/O da thread
|
||||
// Agora a thread está pronta → processo volta para Ready
|
||||
if (processRt.state === "Waiting") {
|
||||
this._ioQueue = this._ioQueue.filter((r) => r.processId !== processId);
|
||||
this._transition(processRt, "Ready");
|
||||
this._readyQueue.push(processRt);
|
||||
this._activeThreads.set(processId, null); // será selecionada no dispatch
|
||||
}
|
||||
} else {
|
||||
// ONE_TO_ONE / MANY_TO_MANY
|
||||
if (processRt.state === "Waiting") {
|
||||
// Processo estava aguardando porque todas as threads estavam em I/O
|
||||
// Verifica se alguma thread ficou pronta
|
||||
const readyThreadQueue = this._readyThreadQueues.get(processId)!;
|
||||
if (readyThreadQueue.length > 0) {
|
||||
this._ioQueue = this._ioQueue.filter((r) => r.processId !== processId);
|
||||
this._transition(processRt, "Ready");
|
||||
this._readyQueue.push(processRt);
|
||||
}
|
||||
}
|
||||
// Se processo está Running: a thread retornou; será selecionada na próxima rodada
|
||||
// Se processo está Ready: a thread apenas entra na fila intra-processo
|
||||
}
|
||||
|
||||
// Verifica se todas as threads terminaram
|
||||
if (this._allThreadsTerminated(processId) && processRt.state !== "Terminated") {
|
||||
this._ioQueue = this._ioQueue.filter((r) => r.processId !== processId);
|
||||
this._readyQueue = this._readyQueue.filter((r) => r.processId !== processId);
|
||||
this._transition(processRt, "Terminated");
|
||||
processRt.finishTick = this._tick + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── MANY_TO_MANY — gerenciamento de slots de kernel ─────────────────────
|
||||
|
||||
/**
|
||||
* Libera um slot de kernel e tenta promover a próxima thread da kernel wait queue.
|
||||
*/
|
||||
private _releaseKernelSlot(processId: string): void {
|
||||
const kwQueue = this._kernelWaitQueues.get(processId)!;
|
||||
if (kwQueue.length > 0) {
|
||||
// Repassa o slot diretamente para a próxima thread aguardando
|
||||
const next = kwQueue.shift()!;
|
||||
next.kernelWaiting = false;
|
||||
this._transitionThread(next, "Ready");
|
||||
this._readyThreadQueues.get(processId)!.push(next);
|
||||
} else {
|
||||
this._kernelSlotsAvailable.set(
|
||||
processId,
|
||||
(this._kernelSlotsAvailable.get(processId) ?? 0) + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Helpers internos ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Aplica uma transição de estado a um runtime, atualizando `state`.
|
||||
* Centraliza todas as transições para facilitar rastreamento e depuração.
|
||||
*/
|
||||
private _transition(rt: ProcessRuntime, state: StateLabel): void {
|
||||
rt.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avança o índice de burst de um runtime e atualiza `remainingBurst`
|
||||
* com a duração do próximo burst. Se não houver próximo burst, `remainingBurst` é 0.
|
||||
*/
|
||||
private _transitionThread(trt: ThreadRuntime, state: StateLabel): void {
|
||||
trt.state = state;
|
||||
}
|
||||
|
||||
private _advanceBurst(rt: ProcessRuntime): void {
|
||||
rt.burstIndex++;
|
||||
const process = this._processes.find((p) => p.id === rt.processId)!;
|
||||
@@ -346,45 +821,36 @@ export class SimulationEngine {
|
||||
rt.remainingBurst = nextBurst !== undefined ? nextBurst.duration : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captura o estado instantâneo de todos os processos no tick atual.
|
||||
* Se um context switch estiver em andamento, `cpuProcess` é null e
|
||||
* `ctxSwitchForProcess` identifica o processo prestes a executar.
|
||||
*/
|
||||
private _snapshot(): SimTick {
|
||||
const states: Record<string, StateLabel> = {};
|
||||
for (const [id, rt] of this._runtimes) {
|
||||
states[id] = rt.state;
|
||||
private _hasThreads(processId: string): boolean {
|
||||
const p = this._processes.find((proc) => proc.id === processId);
|
||||
return (p?.threads?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
private _allThreadsTerminated(processId: string): boolean {
|
||||
const threadMap = this._threadRuntimes.get(processId);
|
||||
if (!threadMap) return true;
|
||||
for (const trt of threadMap.values()) {
|
||||
if (trt.state !== "Terminated") return false;
|
||||
}
|
||||
const contextSwitching = this._contextSwitchRemaining > 0;
|
||||
return {
|
||||
tick: this._tick,
|
||||
cpuProcess: contextSwitching ? null : (this._cpuProcess?.processId ?? null),
|
||||
ioProcesses: this._ioQueue.map((rt) => rt.processId),
|
||||
readyQueue: this._readyQueue.map((rt) => rt.processId),
|
||||
states,
|
||||
contextSwitching,
|
||||
ctxSwitchForProcess: contextSwitching
|
||||
? (this._readyQueue[0]?.processId ?? null)
|
||||
: null,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Inicialização ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Inicializa o estado de runtime de um processo a partir de sua definição.
|
||||
* O primeiro burst deve ser do tipo "cpu"; caso contrário, um erro é lançado.
|
||||
*/
|
||||
private static _initRuntime(p: Process, config: SchedulerConfig): ProcessRuntime {
|
||||
const firstBurst = p.bursts[0];
|
||||
if (firstBurst === undefined || firstBurst.type !== "cpu") {
|
||||
throw new Error(`Process ${p.id}: first burst must be a CPU burst`);
|
||||
private static _initProcessRuntime(p: Process, config: SchedulerConfig): ProcessRuntime {
|
||||
const isThreaded = (p.threads?.length ?? 0) > 0;
|
||||
|
||||
if (!isThreaded) {
|
||||
const firstBurst = p.bursts[0];
|
||||
if (firstBurst === undefined || firstBurst.type !== "cpu") {
|
||||
throw new Error(`Process ${p.id}: first burst must be a CPU burst`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
processId: p.id,
|
||||
state: "New",
|
||||
remainingBurst: firstBurst.duration,
|
||||
remainingBurst: isThreaded ? 1 : (p.bursts[0]?.duration ?? 0),
|
||||
burstIndex: 0,
|
||||
arrivalTick: p.arrivalTime,
|
||||
startTick: null,
|
||||
@@ -397,4 +863,68 @@ export class SimulationEngine {
|
||||
currentPriority: p.priority,
|
||||
};
|
||||
}
|
||||
|
||||
private _initThreadStructures(p: Process): void {
|
||||
const processId = p.id;
|
||||
const model = p.threadModel ?? "ONE_TO_ONE";
|
||||
const policy = p.threadSchedulingPolicy ?? "FCFS";
|
||||
const quantum = p.threadQuantum ?? 2;
|
||||
const threads = p.threads!;
|
||||
|
||||
// Criação do mapa de runtimes de threads
|
||||
const threadMap = new Map<string, ThreadRuntime>(
|
||||
threads.map((t) => [
|
||||
t.tid,
|
||||
SimulationEngine._initThreadRuntime(t, processId, p.arrivalTime, quantum),
|
||||
]),
|
||||
);
|
||||
this._threadRuntimes.set(processId, threadMap);
|
||||
|
||||
// Escalonador intra-processo
|
||||
this._threadSchedulers.set(processId, new ThreadScheduler(policy, quantum));
|
||||
|
||||
// Estruturas de estado
|
||||
this._activeThreads.set(processId, null);
|
||||
this._threadIoQueues.set(processId, []);
|
||||
this._readyThreadQueues.set(processId, []);
|
||||
|
||||
// MANY_TO_MANY: inicializa slots de kernel
|
||||
if (model === "MANY_TO_MANY") {
|
||||
const M =
|
||||
p.kernelThreadCount ??
|
||||
Math.max(1, Math.ceil(threads.length / 2));
|
||||
this._kernelSlotsAvailable.set(processId, M);
|
||||
this._kernelWaitQueues.set(processId, []);
|
||||
} else {
|
||||
this._kernelWaitQueues.set(processId, []);
|
||||
}
|
||||
}
|
||||
|
||||
private static _initThreadRuntime(
|
||||
t: Thread,
|
||||
processId: string,
|
||||
arrivalTick: number,
|
||||
quantum: number,
|
||||
): ThreadRuntime {
|
||||
const firstBurst = t.bursts[0];
|
||||
if (firstBurst === undefined || firstBurst.type !== "cpu") {
|
||||
throw new Error(`Thread ${t.tid}: first burst must be a CPU burst`);
|
||||
}
|
||||
return {
|
||||
threadId: t.tid,
|
||||
processId,
|
||||
state: "New",
|
||||
remainingBurst: firstBurst.duration,
|
||||
burstIndex: 0,
|
||||
arrivalTick,
|
||||
startTick: null,
|
||||
finishTick: null,
|
||||
waitingTime: 0,
|
||||
cpuTime: 0,
|
||||
ioTime: 0,
|
||||
responseTime: null,
|
||||
quantumRemaining: quantum,
|
||||
currentPriority: t.priority ?? 5,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user