Режими мікропроцесора Intel

А ось – нагадування за дуже цікаве та корисне ремесло, якому я колись присвятив дуже багато часу. Я би і далі цим займався, якби не велика кількість справ, які ще більше подобаються і які окрім мене ніхто не зробить. Інформатика, проґрамування – вплинули, мабуть, найбільше на формування мого світогляду.

До найпочесніших і найнаближеніших до науки та високих мистецтв комп’ютерних ремесел відноситься низькорівневе проґрамування. Мікропроцесори 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