Intel x86 Assembly Language & Microarchitecture
Gestione multiprocessore
Ricerca…
Parametri
| Registro LAPIC | Indirizzo (relativo a BASE APIC ) |
|---|---|
| Registro ID APIC locale | + 20h |
| Registro di vettore di interruzione spuria | + 0f0h |
| Interrupt Command Register (ICR); bit 0-31 | + 300h |
| Interrupt Command Register (ICR); bit 32-63 | + 310H |
Osservazioni
Per accedere ai registri LAPIC, un segmento deve essere in grado di raggiungere l'intervallo di indirizzi a partire da APIC Base (in IA32_APIC_BASE ).
Questo indirizzo è ricollocabile e può teoricamente essere impostato in modo da puntare da qualche parte nella memoria inferiore, rendendo così l'intervallo indirizzabile in modalità reale.
I cicli di lettura / scrittura sull'intervallo LAPIC non vengono tuttavia propagati all'interfaccia bus, mascherando in tal modo qualsiasi accesso agli indirizzi "dietro".
Si presume che il lettore abbia familiarità con la modalità Unreal , poiché verrà utilizzata in alcuni esempi.
È anche necessario essere abile con:
- Gestire la differenza tra indirizzi logici e fisici 1
- Segmentazione della modalità reale .
- Alias di memoria, id est la possibilità di utilizzare diversi indirizzi logici per lo stesso indirizzo fisico
- Assoluto, relativo, lontano, vicino a chiamate e salti.
- Assemblatore NASM , in particolare che la direttiva
ORGè globale. La suddivisione del codice in più file semplifica notevolmente la codifica in quanto sarà possibile assegnare a diverse sezioni ORG differenti.
Infine, supponiamo che la CPU abbia un controller di interrupt programmabile locale avanzato ( LAPIC ).
Se ambiguo dal contesto, APIC significa sempre LAPIC (e non IOAPIC, o xAPIC in generale).
Riferimenti:
- Capitolo 8 e 10 dei manuali Intel .
| bitfields |
|---|
![]() |
![]() |
![]() |
![]() |
| Nome MSR | Indirizzo |
|---|---|
| IA32_APIC_BASE | 1BH |
1 Se verrà utilizzato il paging, entrano in gioco anche gli indirizzi virtuali .
Sveglia tutti i processori
Questo esempio risveglierà tutti gli Application Processor (AP) e li renderà, insieme al Processore Bootstrap (BSP), il loro ID LAPIC.
; Assemble boot sector and insert it into a 1.44MiB floppy image
;
; nasm -f bin boot.asm -o boot.bin
; dd if=/dev/zero of=disk.img bs=512 count=2880
; dd if=boot.bin of=disk.img bs=512 conv=notrunc
BITS 16
; Bootloader starts at segment:offset 07c0h:0000h
section bootloader, vstart=0000h
jmp 7c0h:__START__
__START__:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
xor sp, sp
cld
;Clear screen
mov ax, 03h
int 10h
;Set limit of 4GiB and base 0 for FS and GS
call 7c0h:unrealmode
;Enable the APIC
call enable_lapic
;Move the payload to the expected address
mov si, payload_start_abs
mov cx, payload_end-payload + 1
mov di, 400h ;7c0h:400h = 8000h
rep movsb
;Wakeup the other APs
;INIT
call lapic_send_init
mov cx, WAIT_10_ms
call us_wait
;SIPI
call lapic_send_sipi
mov cx, WAIT_200_us
call us_wait
;SIPI
call lapic_send_sipi
;Jump to the payload
jmp 0000h:8000h
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;CX = Wait (in ms) Max 65536 us (=0 on input)
us_wait:
mov dx, 80h ;POST Diagnose port, 1us per IO
xor si, si
rep outsb
ret
WAIT_10_ms EQU 10000
WAIT_200_us EQU 200
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
enable_lapic:
;Enable the APIC globally
;On P6 CPU once this flag is set to 0, it cannot be set back to 16
;Without an HARD RESET
mov ecx, IA32_APIC_BASE_MSR
rdmsr
or ah, 08h ;bit11: APIC GLOBAL Enable/Disable
wrmsr
;Mask off lower 12 bits to get the APIC base address
and ah, 0f0h
mov DWORD [APIC_BASE], eax
;Newer processors enables the APIC through the Spurious Interrupt Vector register
mov ecx, DWORD [fs: eax + APIC_REG_SIV]
or ch, 01h ;bit8: APIC SOFTWARE enable/disable
mov DWORD [fs: eax+APIC_REG_SIV], ecx
ret
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
lapic_send_sipi:
mov eax, DWORD [APIC_BASE]
;Destination field is set to 0 has we will use a shorthand
xor ebx, ebx
mov DWORD [fs: eax+APIC_REG_ICR_HIGH], ebx
;Vector: 08h (Will make the CPU execute instruction ad address 08000h)
;Delivery mode: Startup
;Destination mode: ignored (0)
;Level: ignored (1)
;Trigger mode: ignored (0)
;Shorthand: All excluding self (3)
mov ebx, 0c4608h
mov DWORD [fs: eax+APIC_REG_ICR_LOW], ebx ;Writing the low DWORD sent the IPI
ret
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
lapic_send_init:
mov eax, DWORD [APIC_BASE]
;Destination field is set to 0 has we will use a shorthand
xor ebx, ebx
mov DWORD [fs: eax+APIC_REG_ICR_HIGH], ebx
;Vector: 00h
;Delivery mode: Startup
;Destination mode: ignored (0)
;Level: ignored (1)
;Trigger mode: ignored (0)
;Shorthand: All excluding self (3)
mov ebx, 0c4500h
mov DWORD [fs: eax+APIC_REG_ICR_LOW], ebx ;Writing the low DWORD sent the IPI
ret
IA32_APIC_BASE_MSR EQU 1bh
APIC_REG_SIV EQU 0f0h
APIC_REG_ICR_LOW EQU 300h
APIC_REG_ICR_HIGH EQU 310h
APIC_REG_ID EQU 20h
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
APIC_BASE dd 00h
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
unrealmode:
lgdt [cs:GDT]
cli
mov eax, cr0
or ax, 01h
mov cr0, eax
mov bx, 08h
mov fs, bx
mov gs, bx
and ax, 0fffeh
mov cr0, eax
sti
;IMPORTAT: This call is FAR!
;So it can be called from everywhere
retf
GDT:
dw 0fh
dd GDT + 7c00h
dw 00h
dd 0000ffffh
dd 00cf9200h
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
; Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
;Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll Ll
payload_start_abs:
; payload starts at segment:offset 0800h:0000h
section payload, vstart=0000h, align=1
payload:
;IMPORTANT NOTE: Here we are in a "new" CPU every state we set before is no
;more present here (except for the BSP, but we handler every processor with
;the same code).
jmp 800h: __RESTART__
__RESTART__:
mov ax, cs
mov ds, ax
xor sp, sp
cld
;IMPORTANT: We can't use the stack yet. Every CPU is pointing to the same stack!
;Get an unique id
mov ax, WORD [counter]
.try:
mov bx, ax
inc bx
lock cmpxchg WORD [counter], bx
jnz .try
mov cx, ax ;Save this unique id
;Stack segment = CS + unique id * 1000
shl ax, 12
mov bx, cs
add ax, bx
mov ss, ax
;Text buffer
push 0b800h
pop es
;Set unreal mode again
call 7c0h:unrealmode
;Use GS for old variables
mov ax, 7c0h
mov gs, ax
;Calculate text row
mov ax, cx
mov bx, 160d ;80 * 2
mul bx
mov di, ax
;Get LAPIC id
mov ebx, DWORD [gs:APIC_BASE]
mov edx, DWORD [fs:ebx + APIC_REG_ID]
shr edx, 24d
call itoa8
cli
hlt
;DL = Number
;DI = ptr to text buffer
itoa8:
mov bx, dx
shr bx, 0fh
mov al, BYTE [bx + digits]
mov ah, 09h
stosw
mov bx, dx
and bx, 0fh
mov al, BYTE [bx + digits]
mov ah, 09h
stosw
ret
digits db "0123456789abcdef"
counter dw 0
payload_end:
; Boot signature is at physical offset 01feh of
; the boot sector
section bootsig, start=01feh
dw 0aa55h
Ci sono due passaggi principali da eseguire:
1. Svegliare gli AP
Ciò si ottiene inserendo una sequenza INIT-SIPI-SIPI (ISS) in tutti gli AP.
Il BSP che invierà la sequenza ISS utilizzando come destinazione la stenografia All exclusing self , quindi indirizzando tutti gli AP.
Un SIPI (Startup Inter Processor Interrupt) viene ignorato da tutte le CPU che sono svegliate dal momento in cui lo ricevono, quindi il secondo SIPI viene ignorato se il primo è sufficiente per riattivare i processori di destinazione. È consigliato da Intel per motivi di compatibilità.
Un SIPI contiene un vettore , questo è simile nel significato, ma assolutamente diverso nella pratica , a un vettore di interruzione (noto anche come numero di interruzione).
Il vettore è un numero di 8 bit, di valore V (rappresentato come vv nella base 16), che fa sì che la CPU inizi a eseguire le istruzioni all'indirizzo fisico 0vv000h .
Chiameremo 0vv000h l' indirizzo Wake-up (WA).
Il WA è forzato a un limite di 4KiB (o pagina).
Useremo 08h come V , il WA è quindi 08000h , 400h byte dopo il bootloader.
Questo dà il controllo agli AP.
2. Inizializzazione e differenziazione degli AP
È necessario avere un codice eseguibile al WA. Il bootloader è a 7c00h , quindi dobbiamo riposizionare del codice al limite della pagina.
La prima cosa da ricordare quando si scrive il payload è che qualsiasi accesso a una risorsa condivisa deve essere protetto o differenziato.
Una risorsa condivisa comune è lo stack , se inizializziamo lo stack in modo ingenuo, ogni AP finirà per utilizzare lo stesso stack!
Il primo passo è quindi utilizzare diversi indirizzi di stack, differenziando così lo stack.
Lo realizziamo assegnando un numero univoco, basato su zero, per ogni CPU. Questo numero, che chiameremo indice , è usato per differenziare lo stack e la linea è dove la CPU scriverà il suo ID APIC.
L'indirizzo di stack per ogni CPU è 800h: (indice * 1000h) dando ad ogni AP 64KiB di stack.
Il numero di riga per ogni CPU è indice , il puntatore nel buffer di testo è quindi l' indice 80 * 2 *.
Per generare l'indice viene usato un lock cmpxchg per incrementare e restituire una WORD atomicamente.
Note finali
- Una scrittura sulla porta 80h viene utilizzata per generare un ritardo di 1 μs.
-
unrealmodeè una routine lontana, quindi può essere chiamata anche dopo il risveglio. - Anche il BSP salta al WA.
Immagine dello schermo
Da Bochs con 8 processori




