Intel x86 Assembly Language & Microarchitecture
Gestion multiprocesseur
Recherche…
Paramètres
| Registre LAPIC | Adresse (relative à APIC BASE ) |
|---|---|
| Registre local APIC ID | + 20h |
| Registre vectoriel d'interruption parasite | + 0f0h |
| Registre de commande d'interruption (ICR); bits 0-31 | + 300h |
| Registre de commande d'interruption (ICR); bits 32-63 | + 310h |
Remarques
Pour accéder aux registres LAPIC, un segment doit pouvoir atteindre la plage d'adresses commençant à APIC Base (dans IA32_APIC_BASE ).
Cette adresse est relogeable et peut théoriquement être positionnée quelque part dans la mémoire inférieure, ce qui rend la plage adressable en mode réel.
Les cycles de lecture / écriture de la plage LAPIC ne sont cependant pas propagés à l'unité d'interface de bus, masquant ainsi tout accès aux adresses "derrière" celle-ci.
On suppose que le lecteur est familier avec le mode Unreal , car il sera utilisé dans certains exemples.
Il faut aussi maîtriser:
- Gérer la différence entre les adresses logiques et physiques 1
- Segmentation en mode réel .
- Alias de mémoire, possibilité d'utiliser différentes adresses logiques pour la même adresse physique
- Absolu, relatif, loin, proche des appels et des sauts.
- Assembleur NASM , en particulier que la directive
ORGest globale. Diviser le code en plusieurs fichiers simplifie grandement le codage car il sera possible de donner différents ORG de section différente.
Enfin, nous supposons que le CPU dispose d'un contrôleur d'interruption programmable avancé local ( LAPIC ).
S'il est ambigu du contexte, APIC signifie toujours LAPIC (et non IOAPIC ou xAPIC en général).
Les références:
- Chapitre 8 et 10 des manuels Intel .
| Bitfields |
|---|
![]() |
![]() |
![]() |
![]() |
| Nom MSR | Adresse |
|---|---|
| IA32_APIC_BASE | 1bh |
1 Si la pagination est utilisée, les adresses virtuelles entrent également en jeu.
Réveillez tous les processeurs
Cet exemple réveillera tous les processeurs d'application (AP) et les affichera, avec le processeur de démarrage (BSP), pour afficher leur identifiant 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
Il y a deux étapes principales à réaliser:
1. Réveiller les AP
Ceci est réalisé en insérant une séquence INIT-SIPI-SIPI (ISS) à tous les points d'accès .
Le BSP qui enverra la séquence ISS en utilisant comme destination le raccourci Tout excluant soi - même , ciblant ainsi tous les points d'accès.
Un SIPI (Startup Inter Processor Interrupt) est ignoré par tous les processeurs qui sont désactivés au moment où ils le reçoivent, de sorte que le second SIPI est ignoré si le premier suffit à réveiller les processeurs cibles. Il est conseillé par Intel pour des raisons de compatibilité.
Un SIPI contient un vecteur , qui a un sens similaire, mais absolument différent en pratique , d'un vecteur d'interruption (alias numéro d'interruption).
Le vecteur est un nombre de 8 bits, de valeur V (représenté par vv en base 16), qui fait que le CPU commence à exécuter des instructions à l'adresse physique 0vv000h .
Nous appellerons 0vv000h l' adresse de réveil (WA).
Le WA est forcé à une limite de 4 Ko (ou page).
Nous utiliserons 08h comme V , le WA sera alors 08000h , 400h après le bootloader.
Cela donne le contrôle aux points d'accès.
2. Initialiser et différencier les points d'accès
Il est nécessaire d'avoir un code exécutable au WA. Le chargeur de démarrage est à 7c00h , nous devons donc déplacer un peu de code à la limite de la page.
La première chose à retenir lors de l'écriture de la charge utile est que tout accès à une ressource partagée doit être protégé ou différencié.
Une ressource partagée commune est la pile , si nous initialisons la pile naïvement, chaque AP finira par utiliser la même pile!
La première étape consiste alors à utiliser différentes adresses de pile, différenciant ainsi la pile.
Nous accomplissons cela en attribuant un numéro unique, basé sur zéro, pour chaque CPU. Ce nombre, nous l’appellerons index , est utilisé pour différencier la pile et la ligne si le processeur écrit son identifiant APIC.
L'adresse de pile pour chaque CPU est 800h: (index * 1000h) donnant à chaque AP 64KiB de pile.
Le numéro de ligne de chaque CPU est index , le pointeur dans le tampon de texte est donc 80 * 2 * index .
Pour générer l'index, un lock cmpxchg est utilisé pour incrémenter et retourner de manière atomique un mot.
Notes finales
- Une écriture sur le port 80h est utilisée pour générer un délai de 1 µs.
-
unrealmodeest une routine de loin, donc il peut être appelé après le réveil aussi. - Le BSP passe également au WA.
Capture d'écran
De Bochs avec 8 processeurs




