Intel x86 Assembly Language & Microarchitecture
マルチプロセッサ管理
サーチ…
パラメーター
| LAPICレジスタ | 住所( APIC BASEを基準にして ) |
|---|---|
| ローカルAPIC ID登録 | + 20時間 |
| スプリアス割り込みベクタレジスタ | + 0f0h |
| 割り込みコマンドレジスタ(ICR);ビット0-31 | + 300h |
| 割り込みコマンドレジスタ(ICR);ビット32-63 | + 310h |
備考
LAPICレジスタにアクセスするには、セグメントがAPICベース ( IA32_APIC_BASE内 )から始まるアドレス範囲に到達できる必要があります。
このアドレスはリロケータブルであり、理論的には下位メモリのどこかを指すように設定できるため、範囲をリアルモードでアドレス可能にすることができます。
しかし、LAPIC範囲への読み出し/書き込みサイクルは、バスインターフェースユニットに伝搬されず 、それにより、その「背後にある」アドレスへのアクセスをマスクする。
読者はUnrealモードに精通していると仮定しています 。これは、いくつかの例で使用されるためです。
それにも堪能であることが必要です。
- 論理アドレスと物理アドレスの差異の処理1
- リアルモードセグメンテーション。
- メモリエイリアシング、id est同じ物理アドレスに対して異なる論理アドレスを使用する能力
- 絶対的な、相対的な、遠くの、近くの呼び出しとジャンプ。
- NASMアセンブラ 、特に
ORG指令はグローバルです。コードを複数のファイルに分割することにより、異なるセクションに異なるORGを与えることが可能になるので、コードを大幅に簡略化します。
最後に、CPUにはLAPIC ( Local Advanced Programmable Interrupt Controller )が搭載されているものとします。
コンテキストから曖昧な場合、APICは常にLAPICを意味します(一般的にはIOAPICまたはxAPICではありません)。
参考文献:
- インテルマニュアルの第8章と第10章
| ビットフィールド |
|---|
![]() |
![]() |
![]() |
![]() |
| MSR名 | 住所 |
|---|---|
| IA32_APIC_BASE | 1h |
1ページングを使用する場合は、 仮想アドレスも有効になります。
すべてのプロセッサを起動する
この例では、 アプリケーションプロセッサ (AP)ごとに起動し、 ブートストラッププロセッサ (BSP)と共にLAPIC IDを表示します。
; 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
実行する主な2つの手順があります。
1. APのスリープ
これは、すべてのAPにINIT-SIPI-SIPI (ISS)シーケンスを保証することによって実現されます。
宛先としてISSシーケンスを送信するBSP。すべて自己を除外し 、すべてのAPを対象とします。
SIPI(Startup Inter Processor Interrupt)は、受信するまでに目覚めたすべてのCPUによって無視されるため、最初のプロセッサがターゲットプロセッサのウェイクアップに十分な場合は、2番目のSIPIは無視されます。互換性の理由により、インテルからアドバイスを受けています。
SIPIにはベクトルが含まれていますが、これは意味が似ていますが、実際にはまったく異なっています。割り込みベクタ(割り込み番号とも呼ばれます)です。
このベクトルは、CPUが物理アドレス0vv000hで命令の実行を開始させる値V (基数16のvvで表される)の8ビットの数値です 。
0vv000hを ウェイクアップアドレス (WA)と呼びます。
WAは4KiB(またはページ)境界に強制されます。
08hをVとし、WAはブートローダーの後の08000h 、400hバイトです。
これは、APに制御を与えます。
2. APの初期化と差別化
WAには実行可能コードが必要です。ブートローダーは7c00hです 。そのため、ページ境界でコードを再配置する必要があります。
ペイロードを書くときに覚えておくべき最初のことは、共有リソースへのアクセスがすべて保護されているか、差別化されているかということです。
共通の共有リソースがスタックです。スタックを単純に初期化すると、すべてのAPが同じスタックを使用して終了します。
最初のステップでは、異なるスタックアドレスを使用して、スタックを区別します。
各CPUに固有の番号(ゼロベース)を割り当てることで、これを達成します。この番号はインデックスと呼ばれ、スタックとCPUのAPIC IDを書き込む行を区別するために使用されます。
各CPUのスタックアドレスは800hです:( インデックス * 1000h)スタックの各AP 64KiBを与えます。
各CPUの行番号はインデックスであり、したがってテキストバッファへのポインタは80 * 2 * indexです。
インデックスを生成するために、 lock cmpxchgを使用して、ワードをアトミックにインクリメントして返します。
最終ノート
- ポート80hへの書き込みは、1μsの遅延を生成するために使用されます。
-
unrealmodeは遠くのルーチンなので、起床後も呼び出すことができます。 - BSPもWAにジャンプします。
スクリーンショット
8プロセッサ搭載のBochsから




