А ось – нагадування за дуже цікаве та корисне ремесло, якому я колись присвятив дуже багато часу. Я би і далі цим займався, якби не велика кількість справ, які ще більше подобаються і які окрім мене ніхто не зробить. Інформатика, проґрамування – вплинули, мабуть, найбільше на формування мого світогляду.
До найпочесніших і найнаближеніших до науки та високих мистецтв комп’ютерних ремесел відноситься низькорівневе проґрамування. Мікропроцесори Intel – як і мову анґлійську – можна по різному оцінювати, але знати їх треба.
Ця стаття написана не мною і мені дуже-дуже жаль, що я не пам’ятаю автора і що в інтернеті її більше ніде нема. Здається, це була частина великого підручника. Стаття була написана у середині двотисячних, коли, наприклад, обмаль було в інтернеті фільмів українською мовою – не те, що технічної літератури.
Велика подяка автору статті. Сподіваюся, ми колись перетнемося і ще більше сподіваюся, що тепер на моєму сайті стаття системникам-початківцям стане у нагоді.
Стаття подана без правок – із непрацюючими нині, на жаль, посиланнями й авторським правописом.
Реальний режим – атавізм сучасних мікропроцесорів Intel. Він існує тому, що існують програми, розраховані на цей режим. Цей режим був єдиним в якому працювали процесори 8086 і 80186, але вже в 80286 був запроваджений [|захищений режим], який був удосконалений в 80386
Для доступу до оперативної памяті в реальному режимі використовується сегментна адресація з використанням сегментних реєстрів CS, DS, ES, SS, FS и GS. Розмір цих реєстрів в 8086 і 80186 становив два байта. При цьому будь яке звернення до памяті використовує який-небуть сегментний реєстр і Фізичний адрес Визначається так:
сегментний_реєстр*16+вказівний_адрес
Наприклад команда:
mov [0x1234], al
помістить вмістиме реєстра al в наступну комірку памяті:
DS*16+0x1234
Звичайно команди працюючі зі стеком використовують SS як сегментний реєстр по замовчуванню, вибірка коду для наступного використання процесором виконується в адресі CS:IP (CS*16+IP), інші, які працюють з памяттю, звичайно використовують DS.
Будь-який сегмент може бути перероспреділено за допомогою префіксів перерозподілу сегментів :
es mov [0x1234], al
приведе до того, що вмістиме al запишеться в адресі:
ES*16+0x1234
Максимально можливий адрес памяті в реальному режимі складає 10FFEF (1114095 байт), які отримуються при використанні FFFF в якості сегменту і FFFF в якості адреси.
0 – 3FF | RAM | Таблиця векторів переривань |
400 – 4FF | RAM | Район данних BIOS |
500 – 9FFFF | RAM | Вільно |
A0000 – BFFFF | video RAM | Фреймбуфери VGA |
C0000 – C7FFF | ROM | Video BIOS |
C8000 – EFFFF | Пусто | – |
F0000 – FFFFF | ROM | BIOS материньської плати |
100000+ | ROM | Вільно |
Обробка переривання в захищеному режимі відрізняється від обробки в реальному режимі. Головний елемент системи обробки переривань в захищеному режимі – таблиця IDT (Interrupt Descriptors Table – таблиця декрипторів переривань).
Як і в випадку з декрипторами сегментів, кожний декриптор з таблиці IDT – восьмибайтна структура. Три види декрипторів можуть знаходитись в таблиці IDT:
Шлюз переривань
Шлюз пастка
Шлюз задачі
Відмінність між шлюзами переривань і пастки: підчас виконання обробки переривань всі переривання автоматично забороняються до завершення обробника (командою IRETD), а також зкидається прапорець трасування.
Формат декриптора переривань або пастки:
байти 0-1: біти 15-0 зміщення
байти 2-3: селектор сегменту
байт 4: нульовий
байт 5:
біт 7 – Наявність сегменту
біти 6-5 – рівень переваг сегменту
біт 4 – тип декриптора (0 – системний)
біт 3-0: тип шлюзу
байти 6-7: біти 31-16 зміщення
Можливі типи шлюзу:
0110 – 16 бітний шлюз переривання
0111 – 16 бітний шлюз пастки
1110 – 32 бітний шлюз переривання
1111 – 32 бітний шлюз пастки
Оскільки можливих переривань – 256, то в таблиці IDT повинно бути 256 декрипторів. Порядковий номер декриптора визначається підчас виклику якого переривання він буде використаний. При виклику обробника переривань процесор розмущує в стек реєстр прапорців і адресу повернення. Деякі переривання також додатково розміщують туди код помилки.
Дії, БІОС підчас завантаження з дискети, такі:
Читання першого (завантажувального) сектора дискети в оперативну память за адресою 0000:7C00
Передавання управління в цю точку – під час старту завантажувача, в сегментному реєстрі CS знаходиться 0. Інші сегментні реєстри потрібно ініціювати самостійно
Оскільки один сектор – це всього лиш 512 байт, то можливості первинного завантажувача обмежені. Звичайно, його обовязки такі – Завантаження вторинного завантажувача. (наприклад в ОС Linux, другий завантажувач зветься Setup), який вже виконує завантаження ядра, перехід в захищений режим та ін .
При виконанні первинного завантажувача, процесор знаходиться в реальному режимі..
Нижче показано вихідний код простого завантажувача,який читає 17 секторів (8704 байт) в адресі 7E00 і передає їм управління (всі асемблерні приклади, які я буду наводити, розраховані на компіляцію в асемблері NASM (nasm.zip):
BITS 16
;Вторинний завантажувач буде в цій адресі:
%define SECOND 0x7e00
org 0x7C00
section .text
start:
xor ax, ax
cli ;відключення переривань для встановлення стека.
mov ss, ax
mov sp, start
sti ;нам неодхідно переривання БІОС
mov ds, ax ;Завантажимо потрібні реєстри нулем
mov es, ax ;
mov ch, 0 ;Циліндр
mov bx,SECOND ;Адреса по якій будуть завантажуватись данні
mov ah, 02h ;Функція дискового сервіса 02 – читання секторів
mov al,17 ;Число секторів
mov cl,2 ;Сектор, з якого почати
mov dl,0 ;Диск (дисковід А – 0)
mov dh,0 ;Голівка
int 0x13 ;Переривання 0x13 – дисковий сервіс БІОС
jmp SECOND ;Перехід на вторинний завантажувач
Для спрощення програми ми не виконували перевірку правильності читання. Завантажувач розрахований на те, що код вторинного завантажувача знаходиться на диску зразу після першого сектора – тобто.починаючи з 512 байт.
Вторинний завантажувач
Нижче подано приклад програми (вторинного завантажувача), який виконує ініціалізацію GDT і перехід в захищений режим. Вторинний завантажувач розрахований на те, що б завантажуватись з дискети з допомогою первинного завантажувача
BITS16
ORG 0x7E00 ;вторинний завантажувач
;Виконується в 0x7E00
section .text
_entry:
cli ;Відключення переривань
;Встановлення базового вектора переривань у 0x20:
mov al,00010001b
out 0x20,al
mov al,0x20
out 0x21,al
mov al,00000100b
out 0x21,al
;ручний EOI:
mov al,00000001b
out 0x21,al
;Вмикання адресної лінії A20:
in al, 0x92
or al, 2
out 0x92, al
;Завантаження GDT:
lgdt [gd_reg]
;Очищення реєстру :
push byte 2
popf
;Вмикання захищеного режиму (біт PE в реєстрі CR0):
mov eax, cr0
or al, 1
mov cr0, eax
;Завантаження селектора сегментів коду в CS шляхом довгого стрибку
;в 32-бітний сегмент
jmp 16:_protected
;=======================================================;
BITS 32
_protected:
;Цей код виконується в захищеному режимі
Завантаження реєстрів сегменту, які потрібні селекторам:
mov ax, 1000b
mov es, ax
mov ds, ax
mov ss, ax
mov [$], byte 0
mov [0xB8000], byte ‘S’
xor eax, eax
xor ecx, ecx
;===========================================;
;Копіювання ядра за адресою 0x200000 (2 мб):
mov ecx, _kernel
mov eax, 0x200000
.loop:
mov bl, [ecx]
mov [ecx], byte 0
mov [eax], bl
inc ecx
inc eax
cmp ecx, _kernel+0x10000
jl .loop
;===========================================;
jmp 16:0x200000 ;Перехід в ядро
;===============================================;
BITS 16
gd_table: ; GDT
dw 0xFFFF,0x0000,0x9200,0x00CF ;декриптор данних
dw 0xFFFF,0x0000,0x9A00,0x00CF ;декриптор коду
db 0x67, 0,0,0,0 ,10001001b, 01000000b, 0 ;сегмент стану задачі
gd_reg:
dw 255 ;Ліміт GDT
dd gd_table-8 ;Лінійна адреса GDT
BITS32
TIMES 0x1000-($-$$) db 0 ;Розмір вторинного завантажувача – 0x1000 байтів
_kernel:
incbin ‘kernel.bin’ ;Шлях до ядра
TIMES 20000-($-$$) db 0