Intel x86 Assembly Language & Microarchitecture
다중 프로세서 관리
수색…
매개 변수
| LAPIC 레지스터 | 주소 ( APIC BASE 관련 ) |
|---|---|
| 로컬 APIC ID 등록 | + 20 시간 |
| 가짜 인터럽트 벡터 레지스터 | + 0f0h |
| 인터럽트 명령 레지스터 (ICR); 비트 0-31 | + 300 시간 |
| 인터럽트 명령 레지스터 (ICR); 비트 32-63 | + 310h |
비고
LAPIC 레지스터에 액세스하려면 세그먼트가 APIC베이스 ( IA32_APIC_BASE )에서 시작하는 주소 범위에 도달 할 수 있어야합니다.
이 주소는 재배치 가능하며 이론적으로 하위 메모리의 어딘가를 가리 키도록 설정 될 수 있으므로 범위를 실제 모드에서 주소 지정 가능하게 만듭니다.
LAPIC 범위에 대한 읽기 / 쓰기 사이클은 버스 인터페이스 장치에 전달 되지 않으므로 버스 인터페이스 장치의 "뒤에있는"주소에 대한 모든 액세스를 마스킹합니다.
독자는 Unreal 모드에 익숙하다고 가정합니다. 일부 예제에서 사용되기 때문입니다.
다음과도 능숙해질 필요가 있습니다.
- 논리적 주소와 물리적 주소의 차이 처리 1
- 리얼 모드 세분화.
- 메모리 별명 지정, 동일한 물리적 주소에 대해 서로 다른 논리 주소를 사용할 수 있는지 여부
- 절대, 상대, 멀리, 가까운 전화 및 점프.
- NASM 어셈블러 , 특히
ORG지시문은 전역 적입니다. 다른 섹션 서로 다른 여러 조직을 제공 할 수있을 것으로 여러 파일에 코드를 분할하면 크게 코딩을 단순화합니다.
마지막으로 CPU에 LAPIC ( Local Advanced Programmable Interrupt Controller )가 있다고 가정합니다.
컨텍스트에서 모호한 경우 APIC은 항상 LAPIC을 의미합니다 (일반적으로 IOAPIC 또는 xAPIC 아님).
참고 문헌 :
- 인텔 매뉴얼 8 장 및 10 장.
| 비트 필드 |
|---|
![]() |
![]() |
![]() |
![]() |
| MSR 이름 | 주소 |
|---|---|
| IA32_APIC_BASE | 1 시간 |
1 페이징을 사용하면 가상 주소도 작동합니다.
모든 프로세서 깨우기
이 예제는 모든 AP ( Application Processor)를 깨우고 BSP ( Bootstrap Processor) 와 함께 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
수행 할 두 가지 주요 단계가 있습니다.
1. AP 깨우기
이것은 모든 AP에 INIT-SIPI-SIPI (ISS) 시퀀스를 보증함으로써 성취됩니다.
ISS 시퀀스를 목적지로 사용하여 속기를 모두 포함 하는 BSP. 자기를 제외한 모든 AP를 대상으로합니다.
SIPI (Startup Inter Processor Interrupt)는 수신 할 때까지 깨어있는 모든 CPU에서 무시되므로 첫 번째 SIPI가 대상 프로세서를 깨우는 데 충분하면 두 번째 SIPI가 무시됩니다. 호환성에 대한 이유는 인텔에서 권장합니다.
SIPI에는 벡터 가 포함되어 있습니다. 이것은 의미는 비슷 하지만 실제로 는 인터럽트 벡터 (인터럽트 번호) 와 완전히 다릅니다 .
벡터는 값 V (기본 16에서 vv 로 표시됨)의 8 비트 숫자로 CPU가 실제 주소 0vv000h 에서 명령 실행을 시작하게합니다.
우리는 0vv000h 를 Wake-up address (WA)라고 부를 것입니다.
WA는 4KiB (또는 페이지) 경계에서 강제 실행됩니다.
우리는 V를 08h로 사용하고, WA는 부트 로더 이후 0800h , 400h 바이트입니다.
이렇게하면 AP에 제어 권한이 부여됩니다.
2. AP 초기화 및 차별화
WA에 실행 코드가 있어야합니다. 부트 로더는 7c00h 이므로 페이지 경계에서 일부 코드를 재배치해야합니다.
페이로드를 작성할 때 가장 먼저 기억해야 할 점은 공유 리소스에 대한 액세스는 보호되거나 차별화되어야한다는 것입니다.
일반적인 공유 리소스는 스택입니다 . 스택을 원래대로 초기화하면 모든 AP가 같은 스택을 사용하게됩니다!
첫 번째 단계에서는 다른 스택 주소를 사용하므로 스택을 구별 합니다.
각 CPU에 대해 0부터 시작하는 고유 번호를 할당하여이를 수행합니다. 이 숫자는 인덱스 라고 부르며 CPU가 APIC ID를 쓰면 스택과 라인을 구분하는 데 사용됩니다.
각 CPU의 스택 주소는 800h입니다 (인덱스 * 1000h) . 스택의 각 AP 64KiB를 제공합니다.
각 CPU의 행 번호는 인덱스 이며, 따라서 텍스트 버퍼의 포인터는 80 * 2 * index 입니다.
인덱스를 생성하기 위해 lock cmpxchg 는 워드를 원자 적으로 증가시키고 반환하는 데 사용됩니다.
최종 노트
- 포트 80h에 대한 쓰기는 1μs의 지연을 생성하는 데 사용됩니다.
-
unrealmode는 멀리 떨어진 일상이므로 깨어 난 후에도 호출 할 수 있습니다. - BSP는 또한 WA로 점프합니다.
스크린 샷
8 개의 프로세서가있는 Bochs에서




