Bootloader x86: grafika 2D w trybie chronionym i 3D w 512 bajtach

Przetłumaczone automatycznie z angielskiego. Przeczytaj oryginał

Animowana grafika 2D rysowana przez bootloader w trybie chronionym w Bochs

Zastrzeżenie: opisane tu projekty są wynikiem mojej własnej, samodzielnej pracy, wykonanej bez użycia AI. Sam artykuł powstał z pomocą AI.

Katalog x86-bootloader w moim repozytorium retro-lab zawiera dwa programy, które działają bezpośrednio na procesorze po tym, jak BIOS przekaże im sterowanie. Nie ma systemu operacyjnego, libc ani sterowników: tylko asembler NASM, przerwania BIOS-u i pamięć wideo pod 0xA0000. Oba działają w emulatorze Bochs lub na prawdziwym sprzęcie uruchomionym z dyskietki.

Część Tryb Rozmiar Co robi
protected-mode-2d 32-bitowy tryb chroniony 4555 bajtów (512 B sektora rozruchowego + 4043 B programu) animowane kształty 2D, linie, logo, dźwięk z PC speakera, kopiowanie bufora z MMX
real-mode-3d 16-bitowy tryb rzeczywisty 512 bajtów linie Bresenhama i projekcja 3D na 2D, wszystko wewnątrz sektora rozruchowego

protected-mode-2d: kod z 2005 roku #

Ta część jest starsza niż kod na GitHubie. Napisałem ją około 2005 roku, a jej 11. wersję zacommitowałem w 2018. Jest podzielona na małe pliki: bootsector.asm, main.asm, point.asm, line.asm, rect.asm, logo.asm i sound.asm, asemblowane osobno i łączone w jeden obraz dyskietki.

Animowana grafika 2D rysowana przez bootloader w trybie chronionym w Bochs

Z trybu rzeczywistego do trybu chronionego #

Sektor rozruchowy jest ładowany przez BIOS pod adres 0x7C00 i nadal działa w 16-bitowym trybie rzeczywistym. Musi zrobić wszystko, co później, bez BIOS-u, stanie się niemożliwe:

  1. zresetować napęd i wczytać kolejne 10 sektorów (właściwy program) pod 0000:1000 za pomocą int 13h,
  2. przełączyć się w tryb graficzny 13h (320×200, 256 kolorów) za pomocą int 10h,
  3. wyłączyć przerwania i załadować globalną tablicę deskryptorów (GDT) instrukcją lgdt,
  4. ustawić bit 0 rejestru CR0 i wykonać daleki skok, aby opróżnić kolejkę prefetch,
  5. w kodzie 32-bitowym: ustawić segmenty danych i stosu oraz skoczyć do programu pod 08h:01000h.
x86asm
  cli                   ; Disable interrupts, we want to be alone
  xor  ax, ax
  mov  ds, ax           ; Set DS-register to 0 - used by lgdt
  lgdt  [gdt_desc]      ; Load the GDT descriptor
  mov  eax, cr0
  or   eax, 1           ; Set bit 0
  mov  cr0, eax
  jmp  08h:clear_pipe   ; Jump to code segment, offset clear_pipe

[BITS 32]
clear_pipe:
  mov  ax, 10h          ; Data segment selector
  mov  ds, ax
  mov  ss, ax
  mov  esp, 090000h
  jmp  08h:01000h       ; Jump to the program loaded from disk

GDT to absolutne minimum, które działa: deskryptor zerowy, jeden segment kodu (odczyt/wykonanie) i jeden segment danych (odczyt/zapis), oba płaskie na całą 4-gigabajtową przestrzeń adresową.

Podwójne buforowanie z MMX #

Rysowanie bezpośrednio pod 0xA0000 powoduje migotanie. Program rysuje każdą klatkę do bufora pod 0x10000, a następnie kopiuje 64 000 bajtów do pamięci wideo. Kopiowanie wykorzystuje osiem 64-bitowych rejestrów MMX, więc jedna iteracja pętli przenosi 64 bajty, a 1000 iteracji przenosi cały ekran:

x86asm
%macro draw_screen 0
  mov     esi, 010000h
  mov     edi, 0A0000h
  mov     cx, 1000
  %%copy_loop:
    movq  mm0, [esi]
    movq  mm1, [esi+8]
    ; ... mm2 - mm7
    movq  [edi],    mm0
    movq  [edi+8],  mm1
    ; ... mm2 - mm7
    add  esi, 64
    add  edi, 64
    loop    %%copy_loop
%endmacro

MMX pojawiło się w 1996 roku, więc dla hobbystycznego projektu z 2005 roku było to najtańsze „SIMD”, jakie mogłem dostać na dowolnym PC.

real-mode-3d: wszystko w jednym sektorze #

Drugi program, napisany w 2018 roku, stawia ostrzejszy limit: całość musi zmieścić się w 512-bajtowym sektorze rozruchowym, który ładuje BIOS. Bez drugiego etapu, bez dodatkowych sektorów.

Projekcja 3D w trybie rzeczywistym renderowana z 512-bajtowego sektora rozruchowego

Program pozostaje w trybie rzeczywistym, wchodzi w tryb 13h przez int 10h, ustawia ES na segment 0xA000 i zapisuje piksele przez [es:di]. Rysowanie pikseli i linii to makra NASM, a nie procedury, co oszczędza narzut call/ret i pozwala asemblerowi pominąć to, co nie jest używane:

x86asm
%macro PutPixelAt 3
  mov bx, %2
  imul bx,320
  add bx, %1
  mov di, bx
  mov al, %3
  mov [es:di], al
%endmacro

Bresenham najpierw w Rubym, potem w asemblerze #

Linie korzystają z algorytmu Bresenhama, operującego wyłącznie na liczbach całkowitych. Najpierw napisałem go w Rubym (bresenham_integer.rb), żeby mieć czytelną implementację referencyjną, a potem przetłumaczyłem linia po linii na makro LineFromTo. Komentarze w asemblerze to kod w Rubym:

x86asm
  mov ax, [vdy]                       ; d = 2*dy - dx
  imul ax, 2
  sub ax, [vdx]
  mov [vd], ax
  ...
  cmp word [vd], 0                    ; if d > 0
  jg  %%.True

Część 3D to ta sama uproszczona projekcja, której użyłem na Commodore 64 (zob. 3D na Commodore 64): głębokość jest dzielona przez stałe i dodawana do x i y, co daje widok ukośny, niemal ortograficzny, przy użyciu wyłącznie całkowitoliczbowego div. Bez liczb zmiennoprzecinkowych, bez FPU, bez tablic lookup.

Budowanie i uruchamianie #

Bash
nasm main.asm -f bin -o boot.bin
dd if=/dev/zero of=disk.img bs=1024 count=1440
dd if=boot.bin of=disk.img conv=notrunc

Bochs uruchamia to z minimalnym plikiem bochsrc.bxrc:

Text
megs: 32
floppya: 1_44=disk.img, status=inserted
boot: a
log: bochsout.txt
cpu: ips=15000000

Czego się z tego nauczyłem #

  • Przejście z trybu rzeczywistego do chronionego to około dwudziestu instrukcji, ale każda z nich musi być we właściwej kolejności. Log Bochsa (bochsout.txt) to jedyny debugger, jaki masz, i to wystarcza.
  • 512 bajtów to bardzo dobry nauczyciel. Makra kontra procedury, ponowne wykorzystanie rejestrów i rozmiar instrukcji nagle zaczynają mieć znaczenie.
  • Napisanie algorytmu najpierw w języku wysokiego poziomu (tutaj w Rubym), a potem ręczne przetłumaczenie go, to najszybszy znany mi sposób na uzyskanie poprawnego kodu w asemblerze.

Kod źródłowy: retro-lab/x86-bootloader. Zobacz też przegląd moich praktycznych projektów.

Wersja Markdown

Wszystkie treści na tej stronie są udostępniane na licencji Uznanie autorstwa – Użycie niekomercyjne – Bez utworów zależnych 4.0 Międzynarodowe (CC BY-NC-ND 4.0). Cytuj jako: Michał Sobczak, „Bootloader x86: grafika 2D w trybie chronionym i 3D w 512 bajtach”, 25 października 2026, https://michalasobczak.pl/pl/posts/x86-bootloader-2d-and-3d-graphics-in-nasm.html