Sto scrivendo un emulatore di basso livello (in Java) dell'IBM 1620, un computer di fine anni '50. Con "livello basso" intendo che l'unità di lavoro osservabile più bassa è il "trigger" da 20 microsecondi - le istruzioni della macchina raggiungono i loro obiettivi eseguendo una serie di questi trigger; e ho scritto codice per emularli quasi tutti. Con "osservabile" intendo che la console del 1620 conteneva un tasto "Single Cycle Execution" che permetteva di far avanzare manualmente un trigger alla volta e la maggior parte dello stato interno della CPU (registri, PC, condizioni) venivano visualizzati su una grande console di luci. Garantisco la correttezza dello stato a livello di singolo ciclo.
Normalmente l'emulatore è in attesa che l'operatore preme START, o è nel suo ciclo di esecuzione principale, eseguendo il trigger dopo l'attivazione. Deve anche essere in grado di rispondere a eventi esterni come un interruttore a levetta, o la suddetta chiave Single Cycle Execution, che vengono trasmessi al thread CPU dal thread GUI da una coda di eventi. Il ciclo di esecuzione fa capolino in questa coda prima di sparare il prossimo trigger, e finora questo ha funzionato bene.
Ora, tuttavia, sto implementando le istruzioni I / O del lettore di schede e ho un problema. Tutti i 1620 I / O erano sincroni (il 1620 non aveva capacità di interruzione), quindi quando eseguiva l'istruzione ReadCard, avrebbe letteralmente atteso a metà trigger fino a quando il lettore di schede non consegnava una scheda. Questo normalmente richiedeva un decimo di secondo, a meno che l'operatore non avesse montato il mazzo di carte ! È quest'ultima contingenza che crea il problema: il 1620 deve attendere a metà trigger (cioè il ciclo di esecuzione principale non è in esecuzione), rimanendo comunque reattivo a eventi esterni (interruttori lanciati, tasto Reset o, ovviamente, una scheda da il lettore di schede).
Non riesco a pensare a un modo pulito ed elegante per progettare questo. Il polling e la gestione degli eventi "non richiesti" all'inizio del ciclo di esecuzione hanno funzionato bene finora, ma ora ho bisogno di un meccanismo chiamabile che faccia la stessa cosa ma sia in grado di restituire il controllo al chiamante quando si verifica l'evento giusto. Ecco come appare adesso:
state:
MANUAL means unable to immediately process next trigger
AUTO means we can go ahead and process next trigger
do forever {
processQueuedEvents() // event-processing never blocks
if (state == MANUAL) {
waitForEvents() // this blocks until queue not empty
} else { // state == AUTO
trigger = doTrigger(trigger) // run trigger and get next one
}
}
Trigger CPU E30 invia un messaggio a CardReader che richiede un buffer di scheda e deve attendere per ricevere l'evento contenente il buffer. Nel frattempo, la CPU deve continuare a ricevere ed elaborare gli eventi come al solito.
Potrei immaginare che questo sia un problema di progettazione del sistema operativo, ma non è il mio strong motivo. Qualcuno può fornire una guida?