Переход в защищенный режим i386
После того как я накатил FreeDOS [1] на Pocket 386 [0] и поэкспериментировал с ним какое-то время, мне захотелось написать для этой машины собственную операционную среду.
Но раз уж у нас целых 8 мегабайт оперативной памяти, то хотелось бы иметь возможность использовать её всю. В 16-битном режиме работы работы процессора это возможно, но жутко неудобно. Я вообще на дух не переношу всю эту адресную арифметику, основанную на сегментной модели памяти. Поэтому было принято решение перевести камень в защищенный режим (Protected Mode) с плоской (flat) моделью памяти.
Скачал datasheet на 386-ой процессор, за несколько вечеров освежил знания по нему. А затем уже начались эксперименты на qemu [4]. Для нашей задачи всего-навсего нужно сделать две вещи:
- в реальном (16-битном) режиме загрузить глобальную таблицу дескрипторов (Global Descriptor Table, GDT)
- переключить процессор в защищенный 32-битный режим (Protected Mode)
GDT формируем в соответствии с плоской моделью памяти (base=0, limit=0xfffff). Первый дескриптор в GDT должен быть нулевым, второй (то есть со смещением 8) у нас будет код, а за ним (со смещением 16) - данные. Формат GDT и её элементов выглядит очень криво: базовый адрес (base) и предел (limit) размазаны кусками по этим несчастным восьми байтам, требование первого пустого дескриптора. Складывается ощущение, что инженеры Intel решили отомстить системным программистам за что-то :)
Но все эти мелкие препятствия преодолены, загрузочный сектор сформирован, на qemu всё работает. Супер, заливаем загрузочный сектор на Pocket 386 и... бесконечная перезагрузка! Любопытно: первое расхождение с эмулятором. Оказалось все довольно банально: я забыл проинициализировать сегментный регистр ds для реального (16-битного) режима. Эмулятор qemu заносит туда нужное значение за нас, а вот реальное железо таких вольностей не прощает. Но пара ассемблерных инструкций и все исправлено.
Собирается содержимое загрузочного сектора ассемблером nasm [2][3] из файла boot.asm:
$ nasm -f bin -l boot.lst -o boot.bin boot.asm
Протестировать собранный boot.bin можно в эмуляторе qemu:
$ qemu-system-i386 -hda boot.bin
В качестве "полезной нагрузки" в защищённом режиме код ждёт нажатие на клавиатуру и выводит шестнадцатеричное представление scan-кода нажатой клавиши на экран.
Содержимое файла boot.asm:
ORG 0x7c00
bootsec_start:
BITS 16
mov ax, cs
mov ds, ax
cli
cld
; Enable A20
%macro wait42 0
%%wait42_loop:
in al, 0x64
and al, 0x02
jnz %%wait42_loop
%endmacro
wait42
mov al, 0xd1
out 0x64, al
wait42
mov al, 0xdf
out 0x60, al
wait42
; Load GDT
lgdt [LGDT_op]
; Enter to Protected Mode
mov eax, cr0
or al, 1
mov cr0, eax
; clear prefetch queue & load cs CODE descriptor (8)
jmp dword 8:bits32
BITS 32
bits32:
mov bx, 0x10 ; DATA descriptor
mov ds, bx
mov ss, bx
mov es, bx
; "payload"
mov edi, 0x000b8000 ; stosw -> putchar
%macro put_al_ascii 0
mov ah, 0x13
cmp edi, 0x000b8000 + 4000
jb %%in_range
mov edi, 0x000b8000
%%in_range:
stosw
%endmacro
%macro put_al_4bit_hex 0
add al, 0x30
cmp al, 0x3A
jb %%_below_ten
add al, 0x07
%%_below_ten:
put_al_ascii
%endmacro
mov al, 0x40
put_al_ascii
read_key:
in al, 0x64
test al, 0x01
jnz _kbd_output_ready
pause
jmp read_key
_kbd_output_ready:
in al, 0x60
test al, 0x80
jnz read_key
mov bl, al
shr al, 4
put_al_4bit_hex
mov al, bl
and al, 0x0F
put_al_4bit_hex
mov al, 0x20
put_al_ascii
jmp read_key
GDT:
; NULL descriptor
dd 0
dd 0
; CODE descriptor
dw 0xFFFF ; Limit (0-15)
dw 0 ; Base (0-15)
db 0 ; Base (16-23)
db 0x9e ; Access Byte (present, privilege level 0, code, <= DPL, readable)
db 0xcf ; Flags (4 KiB granularity, 32-bit protected), Limit (16-19)
db 0 ; Base (24-31)
; DATA descriptor
dw 0xFFFF ; Limit (0-15)
dw 0 ; Base (0-15)
db 0 ; Base (16-23)
db 0x92 ; Access Byte (present, privilege level 0, data, <= DPL, writeable)
db 0xcf ; Flags (4 KiB granularity, 32-bit protected), Limit (16-19)
db 0 ; Base (24-31)
GDT_end:
LGDT_op:
dw GDT_end - GDT
dd GDT
bootsec_sign:
resb 510-(bootsec_sign-bootsec_start)
db 0x55
db 0xAA