2013-05-03 5 views
6

In un ambiente di post-boot (senza sistema operativo), come si userebbe il BSP (primo core/processore) per creare IPI per gli AP (tutti gli altri core/processori)? In sostanza, come si sveglia e impostare il puntatore di istruzioni per gli altri core quando si inizia da uno?Come utilizzare l'APIC per creare IPI per riattivare gli AP per SMP nell'assemblaggio x86?

+0

[Questa domanda] (http://stackoverflow.com/questions/1622388/running-code-on-different-processor-x86-assembly) mostra l'algoritmo di base per avviare un altro processore. È questo quello che stai cercando, o vuoi dettagli su come specificamente inviare l'IPI? (In entrambi i casi, ti indicherò al [OSDev wiki] (http://wiki.osdev.org) e [forums] (http://forum.osdev.org), che hanno molte informazioni utili.) – ughoavgfhw

+0

Minimal esempio: http://stackoverflow.com/a/33651438/895245 –

risposta

9

ATTENZIONE: ho assunto 80x86 qui. Se non è 80x86 allora non lo so :-)

Per prima cosa devi scoprire quante altre CPU esistono e quali sono i loro ID APIC e determinare l'indirizzo fisico degli APIC locali. Per fare questo si analizzano le tabelle ACPI (vedere MADT/APIC nella specifica ACPI). Se non riesci a trovare tabelle ACPI valide (ad esempio, il computer è troppo vecchio), esiste una "Specifica MultiProcessor" più vecchia che definisce le proprie tabelle con le stesse informazioni in essa contenute. Notare che la "Specifica MultiProcessor" è ora deprecata (e ci sono alcuni computer con le tabelle fittizie MultiProcessor) ed è per questo che è necessario controllare prima le tabelle ACPI.

Il passaggio successivo consiste nel determinare il tipo di APIC locale in uso. Ci sono 3 casi - vecchi APIC locali "82489DX" esterni (non integrati nella CPU stessa), xAPIC e x2APIC.

Iniziare controllando CPUID per determinare se l'APIC locale è x2APIC. Se si dispone di 2 scelte, è possibile utilizzare x2APIC oppure utilizzare la "modalità di compatibilità xAPIC". Per "modalità di compatibilità xAPIC" è possibile utilizzare solo ID APIC a 8 bit e non sarà in grado di supportare computer con molte CPU (ad esempio 255 o più CPU). Ti consiglio di utilizzare x2APIC (anche se non ti interessa i computer con molte CPU) più veloce. Se si utilizza la modalità x2APIC, sarà necessario cambiare l'APIC locale in questa modalità.

In caso contrario, se non è x2APIC, leggere il registro versione APIC locale. Se la versione APIC locale è 0x10 o superiore, allora il suo xAPIC e se è 0x0F o inferiore, allora è un APIC locale "82489DX" esterno.

I vecchi APIC locali "82489DX" esterni sono stati utilizzati nei computer 80486 e precedenti, e questi sono estremamente rari (erano molto rari 20 anni fa, quindi la maggior parte moriva e/o veniva sostituita e gettata via). Poiché una sequenza diversa viene utilizzata per avviare altre CPU e poiché i computer che dispongono di questi APIC locali sono estremamente rari (ad esempio, probabilmente non sarai mai in grado di testare il tuo codice), ha molto senso non preoccuparsi di supportare questi computer. Se supporti affatto questi vecchi computer; Consiglierei di considerarli come "solo CPU singola" e semplicemente non avviare altre CPU/se l'APIC locale è "82489DX". Per questo motivo non descriverò il metodo utilizzato per avviarli qui (è descritto in Intel "MultiProcess Specification" se sei curioso).

Per xAPIC e x2APIC, la sequenza per l'avvio di un'altra CPU è essenzialmente la stessa (solo modi diversi per accedere all'APIC locale - MSR o memoria mappata). Consiglierei di utilizzare (ad esempio) i puntatori di funzione per nascondere queste differenze; in modo che il codice successivo possa chiamare una funzione "invia IPI" tramite. il puntatore della funzione senza preoccuparsi se l'APIC locale è x2APIC o xAPIC.

Per avviare effettivamente un'altra CPU è necessario inviare una sequenza di IPI (Inter Processor Interrupts) ad essa. Il metodo di Intel va così:

Send an INIT IPI to the CPU you're starting 
Wait for 10 ms 
Send a STARTUP IPI to the CPU you're starting 
Wait for 200 us 
Send another STARTUP IPI to the CPU you're starting 
Wait for 200 us 
Wait for started CPU to set a flag (so you know it started) 
    If flag was set by other CPU, other CPU was started successfully 
    Else if time-out, other CPU failed to start 

Ci sono 2 problemi con il metodo di Intel. Spesso l'altra CPU viene avviata dal primo IPI STARTUP e in alcuni casi ciò può portare a problemi (ad esempio se il codice di avvio dell'altro CPU fa qualcosa come total_CPUs++; allora ogni CPU potrebbe eseguirlo due volte. Per evitare questo problema è possibile aggiungere altro sincronizzazione (ad esempio, un'altra CPU attende che il flag "I know you started" sia impostato dalla prima CPU prima che continui). Il secondo problema con il metodo di Intel è la misurazione di tali ritardi.In genere un sistema operativo avvia le altre CPU, quindi individua le funzionalità supportate dalle CPU e quale hardware è presente in seguito e non dispone di un timer/s preciso per misurare con precisione quei 200 ritardi.

Per evitare questi problemi; Utilizzo un metodo alternativo simile a questo:

Send an INIT IPI to the CPU you're starting 
Wait for 10 ms 
Send a STARTUP IPI to the CPU you're starting 
Wait for started CPU to set a flag (so you know it started) with a short timeout (e.g. 1 ms) 
    If flag was set by other CPU, other CPU was started successfully 
    Else if time-out 
     Send another STARTUP IPI to the CPU you're starting 
     Wait for started CPU to set a flag with a long timeout (e.g. 200 ms) 
      If flag was set by other CPU, other CPU was started successfully 
      Else if time-out, other CPU failed to start 
If CPU started successfully 
    Set flag to tell other CPU it can continue 

Inoltre, è necessario avviare le CPU singolarmente. Ho visto persone che avviano tutte le CPU allo stesso tempo usando la funzione "broadcast IPI to all but self" - questo è sbagliato, rotto e dubbia (non farlo a meno che non si stia scrivendo il firmware). Il problema con questo è che alcune CPU potrebbero essere guaste (ad esempio fallire il loro autotest BIST/built-in) e alcune CPU potrebbero essere disabilitate (ad esempio hyper-threading quando hyper-threading è disabilitato nel firmware); e il metodo "broadcast IPI to all but self" può avviare CPU che non dovrebbero mai essere state avviate.

Infine, per i computer con un numero elevato di CPU è possibile impiegare un tempo relativamente lungo per avviarli tutti se li si avvia uno alla volta. Ad esempio, se occorrono 11 ms per avviare ciascuna CPU e ci sono 128 CPU, ci vorranno 1,4 secondi. Se vuoi avviare velocemente ci sono modi per evitarlo. Ad esempio, la prima CPU può avviare la seconda CPU, quindi la 1a e la 2a CPU possono avviare la 3a e 4a CPU, quindi quelle quattro CPU possono avviare le successive quattro CPU, ecc. In questo modo è possibile avviare 128 CPU in 77 ms invece di 1,4 secondi.

Nota: si consiglia di avviare le CPU una alla volta e assicurarsi che funzioni prima di tentare qualsiasi tipo di "avvio parallelo" (è qualcosa di cui ci si può preoccupare dopo aver saputo che il resto funziona).

L'indirizzo che l'altra/le altre CPU inizieranno a eseguire è codificato nel campo "vector" dell'IPI STARTUP. La CPU inizierà l'esecuzione del codice (in modalità reale) con CS = vector * 256 e IP = 0. Il campo vettoriale è a 8 bit, quindi l'indirizzo di partenza più alto che è possibile utilizzare è 0x000FF000 (0xFF00: 0x0000 in modalità reale). Tuttavia, questa è l'area ROM legacy (in pratica l'indirizzo iniziale dovrebbe essere inferiore). In genere copi un pezzetto di codice di avvio in un indirizzo adatto; dove il codice di avvio gestisce la sincronizzazione (ad esempio impostando un flag "I started" che un'altra CPU può vedere e in attesa di essere avvisato che è OK continuare) e poi fa cose come abilitare la modalità protetta/long e impostare uno stack prima di saltare a una voce punto nel codice normale del sistema operativo. Questo piccolo pezzo di codice di avvio è chiamato "trampolino di avvio CPU AP". Questo è anche ciò che rende la "startup parallela" un po 'complicata; poiché ogni CPU che viene avviata ha bisogno dei propri flag di sincronizzazione/stack separati; e poiché queste cose sono normalmente implementate con variabili nel trampolino (ad esempio mov esp,[cs:stackTop]) significa finire con più trampolini.

+0

Come aspettare un determinato periodo di tempo? Http://stackoverflow.com/questions/9971405/assembly-display-in-screen-and-system-sleep correlati –

Problemi correlati