BOS
boot
gregor.brunmar@home.se
BOS - 32-битная операционная система типа DOS .
Сайт находится тут
Несколько слов о доступе к памяти . В Real mode имеет место линейный доступ к памяти .
В Protected mode все по-другому . В x86 есть 2 механизма доступа к памяти - сегментный и страничный.
При сегментном подходе , вся память разбивается на сегменты .
Допустим , нам что-то нужно записать в память по адресу 0xB8000 .
Определим сегмент , стартовый адрес которого будет начинаться с 0xB8000.
Пусть индекс этого сегмента будет 08h . Тогда доступ к линейному адресу 0xB8002
будет как 08h:0002h . Число таких сегментов не может быть больше чем 8192.
Мы можем дать каждому такому сегменту права на чтение/запись.
В другой модели - страничной - используется принцип виртуальной памяти .
Страница памяти может быть закеширована на диск и извлечена оттуда в нужный момент .
Стандартный размер страницы - 4 килобайта.
Мы выбираем сегментную модель , потому что она легче в реализации .
Вообще говоря , при переключении процессора в protected mode , мы по умолчанию попадаем в
segment-модель .
Для переключения в paging-модель , нужно в регистре CR0 установить 31-й бит в единицу .
Для начала мы загрузим GDT, Global Description Table . Нам нужны 2 сегмента - один для кода
и один для данных . Структура GDT - первые 32 бита :
Double word:
Bits | Function | Description |
0-15 | Limit 0:15 | ограничение на размер сегмента |
16-31 | Base 0:15 | стартовый адрес сегмента |
Структура GDT - следующие 32 бита :
Double word:
Bits | Function | Description |
0-7 | Base 16:23 | базовый адрес |
8-12 | Type | Segment type and attributes |
13-14 | Privilege Level | 0 = Highest privilege (OS), 3 = Lowest privilege (User applications) |
15 | Present flag | Set to 1 if segment is present |
16-19 | Limit 16:19 | Bits 16-19 in the segment limiter |
20-22 | Attributes | Different attributes, depending on the segment type |
23 | Granularity | Used together with the limiter, to determine the size of the segment |
24-31 | Base 24:31 | The last 24-31 bits in the base address |
Заполним нашу GDT . Первый сегмент всегда зарезервирован и называется Null Segment.
Для инициализации этого нулевого сегмента заполним 64 бита - или 8 байт - нулем :
gdt:
gdt_null:
dq 0
Теперь проинициализируем кодовый сегмент . Выставим ему ограничение на 4 гига и установим
нулевой стартовый адрес :
gdt_code:
dw 0FFFFh
dw 0
Следующие 8 бит - базовый адрес :
db 0
Следующие биты устанавливаем так :
8 - 0 - access flag
9 - 1 - сегмент на чтение
10 - 0 - запрещаем доступ к сегменту извне
11 - 1 - сегмент кода , а не данных
12 - 1 - это либо сегмент данных , либо сегмент кода
13-14 - 0 - уровень привилегий = 0
и т.д.
Для сегмента данных инициализация аналогичная .
Вкупе инициализация GDT будет такой :
gdt: ; Address for the GDT
gdt_null: ; Null Segment
dd 0
dd 0
gdt_code: ; Code segment, read/execute, nonconforming
dw 0FFFFh
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_data: ; Data segment, read/write, expand down
dw 0FFFFh
dw 0
db 0
db 10010010b
db 11001111b
db 0
После инициализации GDT необходимо выполнить инструкцию LGDT.
В качестве аргумента ей нужно указать дескриптор GDT 48-битной длины .
16 бит этого дескриптора - размер GDT в байтах , остальные 32 бита -
адрес GDT . Для вычисления адреса gdt , после инициализации сразу пишем :
gdt_end: ; метка для определения размера GDT
gdt_desc: ; GDT descriptor
dw gdt_end - gdt - 1 ; Limit (size)
dd gdt ; собственно сам адрес GDT
Запрещаем прерывание , очищаем регистр DS , поскольку именно в нем
находится gdt_desc :
cli
xor ax, ax
mov ds, ax
lgdt [gdt_desc]
Теперь нужно установить в единицу 0-й бит регистра CRO :
mov eax, cr0
or eax, 1
mov cr0, eax
После этого нужно прыгнуть в кодовый сегмент . Кодовый сегмент располагается сразу
после нулевого сегмента в таблице GDT.
jmp 08h:clear_pipe
[BITS 32]
clear_pipe:
Необходимо заполнить значения сегментных регистров .
В регистры SS и DS мы загружаем адрес сегмента данных .
mov ax, 10h
mov ds, ax
mov ss, ax
Таблица память , которая располагается в диапазоне 1-го мегабайта :
Linear address range (hex) | Memory type | Use |
0 - 3FF | RAM | Real mode, IVT (Interrupt Vector Table) |
400 - 4FF | RAM | BDA (BIOS data area) |
500 - 9FFFF | RAM | Free memory, 7C00 used for boot sector |
A0000 - BFFFF | Video RAM | Video memory |
C0000 - C7FFF | Video ROM | Video BIOS |
C8000 - EFFFF | ? | BIOS shadow area |
F0000 - FFFFF | ROM | System BIOS |
Полный код загрузчика :
[BITS 16] ; We need 16-bit intructions for Real mode
[ORG 0x7C00] ; The BIOS loads the boot sector into memory location 0x7C00
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 ; Copy the contents of CR0 into EAX
or eax, 1 ; Set bit 0
mov cr0, eax ; Copy the contents of EAX into CR0
jmp 08h:clear_pipe ; Jump to code segment, offset clear_pipe
[BITS 32] ; We now need 32-bit instructions
clear_pipe:
mov ax, 10h ; Save data segment identifyer
mov ds, ax ; Move a valid data segment into the data segment register
mov ss, ax ; Move a valid data segment into the stack segment register
mov esp, 090000h ; Move the stack pointer to 090000h
mov byte [ds:0B8000h], 'P' ; Move the ASCII-code of 'P' into first video memory
mov byte [ds:0B8001h], 1Bh ; Assign a color code
hang:
jmp hang ; Loop, self-jump
gdt: ; Address for the GDT
gdt_null: ; Null Segment
dd 0
dd 0
gdt_code: ; Code segment, read/execute, nonconforming
dw 0FFFFh
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_data: ; Data segment, read/write, expand down
dw 0FFFFh
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_end: ; Used to calculate the size of the GDT
gdt_desc: ; The GDT descriptor
dw gdt_end - gdt - 1 ; Limit (size)
dd gdt ; Address of the GDT
times 510-($-$$) db 0 ; Fill up the file with zeros
dw 0AA55h ; Boot sector identifyer
Теперь несколько слов о клавиатуре .
Существуют 2 формы взаимодействия железа и процессора : либо процессор сам постоянно
делает опрос устройств , либо устройства сами дают о себе знать .
Прерывание останавливает процессор . Прерывание может быть внутренним и внешним .
Внутренне вызывается программно . Внешнее прерывание порождено внешним устройством .
Примером внешнего прерывания служит нажатие на клавишу , которое поступает
на PIC (Programmable Interrupt Controller).
Сигнал , поступающий на PIC , проходит через фильтр-маску , и далее передается процессору .
Процессор сохраняет на всякий пожарный свое состояние на момент прерывания в стеке
и обращается к interrupt table или interrupt vector . Допустим , процессор получил 34 прерывание.
Он находит в interrupt table строку по индексу 34 , берет оттуда адрес процедуры прерывания ,
которую и вызывает . В данном случае читается код нажатой клавиши , который сохраняется
в буфере . После возвращения из процедуры процессор восстанавливает из кеша свой статус-кво .
Для таблицы прерываний мы выделим отдельный сектор начиная с адреса 0x1000.
Структура таблицы IDT похожа на структуру таблицы GDT .
gdt:
gdt_null:
dd 0
dd 0
gdt_code:
dw 0FFFFh
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_data:
dw 0FFFFh
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_interrupts:
dw 0FFFFh
dw 01000h
db 0
db 10011010b
db 11001111b
db 0
gdt_end:
gdt_desc:
dw gdt_end - gdt - 1
dd gdt
Прерывание имеет номер . Первые 20 прерываний - базовые прерывания процессора .
Следующие 12 прерываний зарезервированы интелом , и никто не знает , что они делают .
Далее мы можем размещать свои собственные прерывания , начиная с 32 индекса .
Interrupt | Description |
0 | Divide by Zero |
1 | Debug: Trap |
2 | Nonmaskable external interrupt |
3 | Debug: Breakpoint |
4 | Overflow |
5 | BOUND range exceeded(?) |
6 | Invalid opcode |
7 | No Math Cooprocessor (when trying to use it) |
8 | Double fault (something went terrably wrong!) |
9 | Cooprocessor segment overrun(?) |
10 | Invalid Task switch |
11 | Memory: Segment not present |
12 | Memory: Stack-segment fault |
13 | Memory: General protection |
14 | Memory: Page fault |
15 | Reserved by Intel |
16 | FPU error or WAIT/FWAIT instruction |
17 | Memory: Alignment check |
18 | Machine Check(?) |
19 | SSE/SSE2-Exception |
20-31 | Reserved by Intel |
32-255 | Доступны для программирования! |
IDT имеет размерность в 64 бита .
Напишем структуру :
typedef struct {
unsigned long dword0;
unsigned long dword1;
} segment_desc;
Обьявим процедуру :
extern void keyb_int();
Стартовая процедура инициализации , вызываемая из main ()
void init_interrupts()
{
segment_desc idt[0x100];
unsigned long idt_desc[2];
unsigned long idt_address;
unsigned long keyb_address;
Здесь idt - структура , в которой будут записаны адреса функций прерывания .
Нужно заставить PIC перенаправить прерывание по нужному адресу .
Это делается с помощью портов 0x20-0x21 и 0xA0-0xA1. Фактически это два PIC-а - master и slave ,
и каждый может управлять восемью прерываниями .
Кстати , существует еще одна разновидность PIC - это Advanced Programmable Interrupt Controller - APIC ,
но из-за совместимости мы не будем его рассматривать.
Первое слово , которое посылается портам , называется ICW - (Interrupt Control Words).
2 контрольных слова - это минимум , который можно послать портам .
Максимум - 4 .
out(0x20, 0x11);
out(0xA0, 0x11);
out(0x21, 0x20);
out(0xA1, 0x28);
Таблица масок прерываний :
PIC 1 | PIC 2 |
Bit | Interrupt to mask |
0 | System Timer |
1 | Keyboard |
2 | Redirect to IRQ9 (PIC2) |
3 | Serial Port 1 (COM2/4) |
4 | Serial Port 2 (COM1/3) |
5 | Sound Card |
6 | Floppy Disk |
7 | Parallell Port |
|
Bit | Interrupt to mask |
0 | Real-Time Clock |
1 | Redirect from IRQ2 (PIC1) |
2 | Reserved |
3 | Reserved |
4 | PS/2 Mouse |
5 | Math Co-Processor |
6 | Hard Disk |
7 | Reserved |
|
Если бит в маске установлен , процессор его проигнорирует.
Поскольку нам нужно только прерывание клавиатуры , нужно установить 1-й бит в PIC1 .
Также нужно установить бит IRQ2 для того , чтобы работала связка из 2-х PIC
out(0x21, 0xFD);
out(0xA1, 0xFF);
Адрес в IDT имеет 64-битную длину . Первые 16 бит - смещение адреса в таблице прерываний .
Следующие 16 бит указывают на сегмент gdt_interrupt .
Адрес процедуры прерывания нужно сконвертировать в long и сохранить в переменной :
keyb_address = (unsigned long)keyb_int;
idt[0x21].dword0 = (keyb_address & 0xFFFF) | (0x18 << 16);
Для инициализации прерываний , нужно загрузить регистр LIDT
asm("lidt %0\n"
"sti"
:"=m"(idt_desc));
Protected Mode
В 8088 нет доступа более чем к 1 метру физической памяти.
Следующая модель - 80286 имеет 2 режима: real
mode, в котором 286 копирует повадки 8088, и protected mode.
Последний позволяет программам получить доступ к памяти более 1 метра ,
причем память эта защищена .
В следующей модели - 386 - защищенный режим получает дальнейшее развитие .
В чем разница между real mode и protected mode ?
Table 1: разница между real- и protected modes.
| Real Mode
| 16-bit Protected Mode
| 32-bit Protected Mode |
Базовый адрес сегмента
| 20-bit (1M byte range) = 16 * segment register
| 24-bit (16M byte range), from descriptor
| 32-bit (4G byte range), from descriptor |
Ограничение на размер сегмента
| 16-bit, 64K bytes (fixed)
| 16-bit, 1-64K bytes
| 20-bit, 1-1M bytes or 4K-4G bytes |
Защита сегмента
| no
| yes
| yes |
Segment register
| segment base adr / 16
| selector
| selector |
Я почему-то думал , что защищенный режим не использует сегментированную память...
В 32-битном protected mode, вы можете создать сегмент размером до 4 гиг .
Вообще-то это - полный максимум для 32-битной адресной шины .
В этом случае сегментация как-бы изживает себя .
Что такое дескриптор ?
В ревльном режиме размер сегмента ограничен 64 килобайтами ,
и вы вольны делать с ним все что угодно - хранить стек , данные или код .
Базовым адресом сегмента является просто значение одного из сегментных регистров .
В защищенном режиме уже немного по-другому .
Добавляются флаги , которые указывают на характер использования сегмента .
Вся эта информация хранится в дескрипторе , который в защищенном режиме
имеет уже длину 8 байт - или 64 бита .
Table 2: code/data segment descriptor.
Lowest byte
| Byte 1
| Byte 2
| Byte 3
| Byte 4
| Byte 5
| Byte 6
| Highest byte |
Limit 7:0
| Limit 15:8
| Base 7:0
| Base 15:8
| Base 23:16
| Access
| Flags, Limit 19:16
| Base 31:24 | Это 32-битный ('386) дескриптор.
16-битный ('286) дескриптор 2 старших байта (Limit 19:16, Flags, and Base
31:24) устанавливает в ноль .
Table 3: байты доступа code/data segment descriptor.
Highest bit
| Bits 6, 5
| Bit 4
| Bits 3
| Bit 2
| Bit 1
| Lowest bit |
Present
| Privilege
| 1
| Executable
| Expansion direction/ conforming
| Writable/ readable
| Accessed |
- Present bit. Если установлен в единицу - значит доступ к сегменту разрешен .
- Privilege. 0 - высший уровень привилегий (Нулевое кольцо), 3 - низший
(Ring 3).
- Executable bit. 1 - кодовый сегмент , иначе - stack/data segment.
- Expansion direction (stack/data segment). 1 - сегмен6т растет вниз
- Conforming (code segment). Privilege-related.
- Writable (stack/data segment). 1 - сегмент на запись
- Readable (code segment). 1 - сегмент на чтение (Кодовый сегмент)
- Accessed. Устанавливается независимо от того , на запись сегмент или на чтение
The 4-битный флаг не обнуляется только для 32-битных сегментов :
Table 4: flags nybble.
Highest bit
| Bit 6
| Bit 5
| Bit 4 |
Granularity
| Default Size
| 0
| 0 | Бит Granularity указывает на
размер страницы памяти в 4 килобайта .
Для стековых сегментов , Default Size бит известен как B (Big) бит,
он указывает на размерность значений - 16 или 32 бит -
которыми оперирует стек.
Для кодовых сегментов , бит D указывает на то , какова размерность операндов у инструкций -
16 или 32 бит .
В зависимости от бита D одна и та же последовательность 16-ричных байтов может
быть интерпретирована по-разному .
Если D=1 , то это 32-бит , и например последовательность
: B8 90 90 90 90 будет дизассемблирована как mov eax, 90909090h
В 16-битном режиме это будет mov ax,9090h
nop
nop 2 байта названные Operand Size Prefix и Address Length Prefix
зарезервированы .
Бит 4 Access byte =1 для data/stack segments. Если он = 0 , это system segment.
Варианты:
- Task State Segment (TSS). Используется для многозадачности.
386 имеет 4 разновидности TSS.
- Local Descriptor Table (LDT). Отдельные процессы хранят свои дескрипторы здесь ,
а не в GDT.
- Gates. Осуществляют переход процессора от одного уровня привилегий к другому.
Гейтовые дескрипторы имеют следующий формат :
Table 5: gate descriptor.
Lowest byte
| Byte 1
| Byte 2
| Byte 3
| Byte 4
| Byte 5
| Byte 6
| Highest byte |
Offset 7:0
| Offset 15:8
| Selector 7:0
| Selector 15:8
| Word Count 4:0
| Access
| Offset 23:16
| Offset 31:24 |
Table 6: access byte of system segment descriptor.
Highest bit
| Bits 6, 5
| Bit 4
| Bits 3, 2, 1, 0 |
Present
| Privilege
| 0
| Type | Table 7: System segment types.
Type
| Segment function
|
| Type
| Segment function |
0
| (invalid)
|
| 8
| (invalid) |
1
| Available '286 TSS
|
| 9
| Available '386 TSS |
2
| LDT
|
| 10
| (undefined, reserved) |
3
| Busy '286 TSS
|
| 11
| Busy '386 TSS |
4
| '286 Call Gate
|
| 12
| '386 Call Gate |
5
| Task Gate
|
| 13
| (undefined, reserved) |
6
| '286 Interrupt Gate
|
| 14
| '386 Interrupt Gate |
7
| '286 Trap Gate
|
| 15
| '386 Trap Gate |
TSS, LDT, и гейты - 3 основных типа т.н. системных сегментов.
Где живут дескрипторы?
где-то здесь :Global Descriptor Table (GDT), Interrupt Descriptor Table (IDT),
или в Local Descriptor Tables. У CPU есть 3 регистра: GDTR, IDTR, LDTR.
В каждой таблице может храниться не более 8192 дескрипторов .
Что такое селектор?
Это индех одной из перечисленных таблиц , его размерность - 13 бит .
Еще 2 бита нужны для уровня привилегий .
Как попасть в защищенный режим?
Для этого нужно:
- Создать Global Descriptor Table (GDT),
- Создать Interrupt Descriptor Table (IDT),
- Запрещаем прерывания
- Грузим GDTR
- Грузим IDTR
- Устанавливаем бит PE в регистре MSW
- Делаем far jump (при этом происходит одновременная загрузка регистров CS и IP/EIP)
- Загружаем DS и SS data/stack segment selector
- Инициализируем pmode stack,
- Разрешаем прерывания
Как вернуться в Real Mode? Для 386:
- Отключаем прерывания
- far jump в 16-битный кодовый сегмент (переход в 16-битный pmode)
- грузим в SS селектор 16-битного стека
- обнуляем PE bit,
- far jump в real-mode адрес
- грузим DS, ES, FS, GS, SS real-mode-значениями
- грузим IDTR (base 0, limit 0xFFFF),
- разрешаем прерывания
Перед возвращением регистры CS и SS должны быть загружены "реальными" дескрипторами.
Нужно помнить о : ограничении в 64 килобайт, byte-granular (Flags nybble=0), expand-up,
writable (для сегментов data/stack), Access byte=1xx1001x.
На что следует обратить внимание ?
- Один неверный бит может погубить все .
Protected mode errors обычно перегружают компьютер.
- Стандартные библиотечные процедуры работать не будут. printf() работать не будет .
- Перед обнулением PE bit, сегментные регистры должны быть загружены
реальными дескрипторами. Это означает явное ограничение 0xFFFF.
Вообще, для DS, ES, FS и GS, сегментное ограничение должно быть 0xFFFF .
Если вы дадите ограничение в 0xFFFFF и сделаете сегмент page-granular,
вы сможете получить доступ к 4 гигам для real mode.
Это еще называется как unreal mode. В любом случае , ограничение на размер сегмента
большее чем 0xFFFF для CS или SS создаст большие проблемы в real mode.
- Для очистки PE bit используем MOV CR0, nnn.
- Не все инструкции работают в real mode , например LTR .
- Дескрипторные таблицы в ROM работать не будут .
GDT (да и LDT) нужен RAM, поскольку процессор будет модифицировать биты доступа.
Вообще , нужно быть проще:
- Если вам нужно вернуться в real mode , просто нажмите reset :)
- Не отключайте прерываний
- Не используйте LDT.
- Положите в GDT только 4 дескриптора : null, code, stack/data, text video.
- Установите базовые адреса сегментов как 16 помноженное на real-mode segment register value.
Один и тот же сегментный адрес будет работать в обоих режимах.
- Установите ограничение сегмента (0xFFFF для 16-bit protected mode).
- Проинициализируйте эксепшины , которые будут выводить сообщения в видеопамять
:
void unhand(void)
{ static const char Msg[]="U n h a n d l e d I n t e r r u p t ";
disable();
movedata(SYS_DATA_SEL, (unsigned)Msg,
LINEAR_SEL, 0xB8000,
sizeof(Msg));
while(1); }
Преимущества Protected Mode
Доступ к 4 гигам памяти -
4 гига на все - на код , на данные , на стек .
Также можно в real-mode получить доступ к памяти за пределами 1MB.
Виртуальная память - Memory Management Unit (MMU) на 386
позволяет реализовать виртуальную память ,
при которой для процесса создается иллюзия обладания 4 гигами памяти.
Конечно , при этом нужно иметь 4 гига свободного места на харде .
386 конвертирует логические адреса в 32-битные линейные . После чего линейные конвертируются в физические .
Например : B800:0010 - логический адрес .
B8010H - его линейный аналог . В real mode он будет являться одновременно и физическим .
Улучшенная сегментация
Для real mode размер сегмента не превышает 64KB и фиксирован .
В protected mode, сегмент может иметь размер от одного байта до 4GB ,
иметь любой базовый адрес.
Все указатели на память - 32-битные - flat pointers - и соответствуют линейным адресам .
Защита памяти -
386 защищает память. Программа пользователя не способна переписать
сектор с операционкой .
Защита процесса -
Процесс не имеет доступа к данным другого процесса ,
в то время как операционка имеет доступ ко всему .
32-битные регистры
Все основные регистры - 32-битные .
К ним добавляется префикс E (ex: EAX вместо AX).
Добавлены 2 новых сегментных регистра - FS и GS.
Real-mode-программы также могут использовать 32-битные регистры,
но не в состоянии использовать их для индексации .
Использование 32-битных регистров сокращает размер асм-кода .
Улучшенная адресация -
В real mode, программы могут обращаться к константным адресам ,
через BX + BP , или SI + DI .
В mode programs, адрес может быть сформирован с помощью любого регистра .
Можно писать инструкции типа MOV EBX,[EDI][EAX*8]+2 .
Поддержка многозадачности
386 имеет специальную фичу - сохранение текущего состояния процессора
и переключение на новую задачу , известное как context switch.
Это переключение делает одна инструкция .
В 386 поддерживается также nested tasks.
Обратное возвращение можно сделать с помощью back-link.
Hardware debugging
В 386 можно реализовать пошаговый код и точки останова .
Что впрочем возможно и из real mode .
Адресация в Real Mode
В 8086 память организована по-байтно (8 bits=1 byte).
Если данных более 8 бит , они будут сохранены в низших адресах .
Например , слово B800H : сначала 00H , потом B8H.
Семейство Intel-процессоров используют принцип адресации памяти ,
известный как segmentation.
Сегмент - это область памяти. Их может быть много.
В real mode , все сегменты имеют одинаковую длину - 64K ,
и их может быть не более 65536 .
Каждый сегмент начинается через 16 байт после предыдущего .
Вот почему в DOS 65536 * 16 = 1048576 = 1MB.
Сегменты нумеруются с 0000H по FFFFH.
Поскольку каждый сегмент - 64KB , используется смещение - offset.
Полный 8086-й адрес состоит из смещения и сегмента .
Если например сегмент - 0040H и смещение - 0102H,
адрес = 0040:0102.
В общем случае адрес 0000:0010 идентичен адресу 0001:0000.
То же самое можно сказать про : 0040:0000 = 0000:0400 = 0020:0200.
Адреса хранятся "перевернутые" , т.е. 0040:1234 - это
34 12 40 00
Сконвертируем сегментные адреса в линейные :
Для этого умножим сегмент на 16 (10H) и добавим смещение :
В 16-ричной форме :
0040 * 10 + 0000 = 00400
0000 * 0 + 0400 = 00400
0020 * 10 + 0200 = 00400
Регистры в 80386
80386 имеет 4 основных регистра, флаговый регистр, 6 сегментных ,
2 индексных , стековый , указатель стека , базовый , регистр инструкций .
Еще:
GDTR (Global Descriptor Table Register)
IDTR (Interrupt Descriptor Table Register)
LDTR (Local Descriptor Table Register)
TR (Task Register)
CR0-CR3 (Control Registers)
DR0-DR7 (Debug Registers)
Real Mode Vector Table
8086 распознает 256 различных прерываний .
По адресу 0000:0000 располагается таблица адресов этих прерываний .
Каждый адрес в этой таблице имеет 4-байтную длину .
Т.е. у нас итого 1024 байт или килобайт на всю Real Mode Interrupt vector table.
Если например процессор получает прерывание 2 (INT 2),
он сохраняет флаги и текущее значение кодового сегмента CS и IP в стеке.
Затем берет адрес , который находится в таблице в ячейке 0000:0008
и выполняет процедуру прерывания ISR по взятому адресу.
В конце этой ISR стоит инструкция IRET ,
которая возвращает процессор в его предыдущее состояние .
Hardware Interrupts
hardware interrupt - это сигнал от железа - I/O device.
Процессор останавливается и обслуживает прерывание .
После завершения прерывания процессор продолжает работу
с того же места .
Hardware interrupts может приходить из различных источников :
клавиатура,часы,принтер,порт,диск и т.д.
Процессор может сам сгенерировать прерывание - например -
INT 0 - в случае деления на 0 .
8259 Programmable Interrupt Controller (PIC) управляет всеми этими
hardware interruptrs. Это как-бы посредник между железом и процессором .
Таблица ниже показывает hardware interrupts для real с PIC-эквивалентами -
IRQ . Номера тех и других могут не совпадать .
Например , клавиатурный IRQ1 может проинициировать INT 9 для CPU.
PIC может быть перепрограммирован на произвольные interrupt numbers для каждого IRQ.
PIC контролирует приоритет прерываний .
Например , часы (IRQ 0) имеют наивысший приоритет , чем клавиатура (IRQ 1).
Если процессор обслуживает часы , PIC не сможет сгенерировать прерывание для клавиатуры .
И наоборот , часы могут прервать обработку прерывания от клавиатуры .
Interrupt | IRQ Number | Description
| 00H | - | Divide by zero or divide overflow
| 02H | - | NMI (Non-maskable Interrupt)
| 04H | - | Overflow (generated by INTO)
| 08H | 0 | System timer
| 09H | 1 | Keyboard
| 0AH | 2 | Interrupt from second PIC
| 0BH | 3 | COM2
| 0CH | 4 | COM1
| 0DH | 5 | LPT2
| 0EH | 6 | Floppy Disk
| 0FH | 7 | LPT1
| 70H | 8 | Real Time Clock
| 71H | 9 | General I/O
| 72H | 10 | General I/O
| 73H | 11 | General I/O
| 74H | 12 | General I/O
| 75H | 13 | Coprocessor
| 76H | 14 | Hard Disk
| 77H | 15 | General I/O
|
Можно остановить прерывание программно.
Команда CLI запрещает все прерывания .
Клавиатура и A20
Контролер клавиатуры при загрузке блокирует шину A20 .
Специальной командой можно его заставить открыть/закрыть A20 .
Сегментные селекторы и дескрипторы
Понимание сегментов - ключ к пониманию protected mode.
Сегментный регистр хранит 16-битный сегментный селектор .
Этот селектор - индекс в таблице сегментных дескрипторов .
Каждый дескриптор определяет один сегмент , его тип и другие параметры .
Этот селектор состоит из 3-х полей .
Младшие 2 бита (RPL) - атрибуты protection mechanism .
Следующий бит - TI - определяет , из какой таблицы берется дескриптор .
Этих таблиц три :
- Global Descriptor Table (GDT)
- Interrupt Descriptor Table (IDT)
- Local Descriptor Table (LDT)
Если TI = 0 , это GDT. Если TI = 1, это LDT .
Селекторные таблицы в Protected Mode
Дескрипторная таблица может хранить не более 8192 адресов.
Бит INDEX определяет тип дескриптора.
Регистры GDTR и IDTR определяют расположение таблиц GDT и IDT.
Это 32-битный адрес + 16-битный размер в байтах.
GDTR и IDTR - 48-битные регистры .
GDT - это глобальная таблица.
IDT - это тоже глобальная таблица.
LDTR определяет локальную LDT.
В отличие от GDT, у каждого процесса своя LDT.
В отличие GDTR, LDTR - не 48-бит.
Адреса всех LDT хранятся в GDT .
IDT аналогична для real-mode interrupt vector table.
Каждый дескриптор отвечает за один из 256 возможных прерываний .
Дескрипторы
Интерес в сегментном демкрипторе представляют поля :
Бит P (47) : ось может обнулить этот бит для создания виртуального сегмента .
При этом биты с 0 по 39 и с 48 по 63 могут приобретать любые значения .
Например , в них может храниться дисковый адрес .
Бит A ( 40). Этот бит устанавливается в единицу тогда , когда программа
пишет в сегмент.
Бит G (55) : Если он равен нулю , размер сегмента не может быть более 1 метра.
Если единице - на размер сегмента нет ограничений .
Привилегии Protected Mode
Protected mode получил свое название от особенностей 386 protection.
Каждая программа имеет свой уровень привилегий , или PL, от 0 до 3.
Уровень PL0 дает право на всё.
На уровне PL3 не выполняются некоторые инструкции , а также нет доступа
к сегментам с более высокой привилегией .
Каждый сегментный дескриптор имеет Descriptor Privilege Level (DPL).
Иерархия привилегий важна для операционных систем.
Обычно ядро выполняется на уровне PL0.
Другие части OS могут выполняться на уровне PL1.
Драйвера устройств могут работать на PL2.
Пользовательские программы выполняются на PL3.
PL0 имеет право на выполнение следующих инструкций:
HLT
CLTS
LGDT
LIDT
LLDT
LTR
LMSW
MOV (to/from control/debug/test registers)
486
INVD
WBINVD
INVLPG
Pentium
RDMSR
WRMSR
RDTSC
Поле IOPL во флаговом регистре позволяет контролировать I/O.
Оно определяет право на выполнение инструкций I/O - CLI,STI, IN, INS, OUT и OUTS.
Если IOPL=0 , только PL0 имеет право на I/O .
Если IOPL = 3, все программы могут выполнять I/O инструкции.
Только PL0 может модифицировать IOPL .
Уровень привилегий эквивалентен полю RPL в селекторе в регистре CS .
Доступ к данным
При загрузке сегмента данных (DS, ES, FS или GS) происходит проверка
CPL и RPL. БОльшее значение становится effective
privilege level (EPL). Если DPL >= EPL, происходит загрузка сегментного регистра .
Регистр SS будет загружен в случае равенства DPL и CPL .
В 386 есть специальный стековый тип сегмента .
Для стека также можно использовать обычный data-сегмент
Многозадачность
386 поддерживает multitasking,
т.е. одновременное выполнение нескольких процессов .
386 использует Task State Segments (TSS) для этого.
Дескриптор TSS ограничен 104 байтами.
TSS также можно использовать для hardware interrupt handling.
TSS-селектор - на чтение .
При переключении задачи процессор сохраняет свое состояние
автоматически в буфере TSS. Это очень быстрый процесс .
Перед начальной инициализацией процесса ось извлекает информацию о нем
из буфера TSS.
386 поддерживает вложенность - nested tasks.
Для этого используется бит NT флагового регистра .
Когда процесс вызывает другой процесс , 386
сохраняет предыдущий TSS-селектор в специальном поле "back-link" вызванной TSS.
Бит NT устанавливается в 1.
При возврате срабатывает инструкция IRET.
При выполнении задачи бит BUSY устанавливается в 1 . Это предотвращает клонирование процесса при рекурсии.
Исключения 80386
386 поддерживает 256 прерываний (или исключений) .
Interrupt Descriptor table (IDT) включает определения для каждого из них.
Вообще IDT можно расширить до числа 8192 .
IDT могут включать trap gates, interrupt gates или task gates.
trap gate не запрещает прерываний перед входом в exception handler.
interrupt gates наоборот запрещает .
fault - одна из разновидностей исключений - т.н. корректируемая ошибка.
При генерации fault CS:EIP указывает на инструкцию , которая сгенерировала fault.
Большинство faults - это "General Protection Fault (GPF)".
Traps - генерится при software interrupt .
Это результат выполнения команд INT и INTO .
При генерации trap CS:EIP указывает на инструкцию , идущую следующей за той ,
которая вызвала trap. trap не может быть обработано .
Abort - серьезная ошибка уже в самой системе.
Double Fault и FPU Overrun - примеры aborts.
Таблица исключений :
Exception (#num) Type
| Divide Error (0) Fault
| Debug (1) F/Trap
| Breakpoint (3) Trap
| Overflow (4) Trap
| Bounds Check (5) Fault
| Bad Opcode (6) Fault
| No Coprocessor (7) Fault
| Double Fault (8) Abort
| Coprocessor Overrun (9) Abort
| Invalid TSS Segment (0A) Fault
| Segment not Present (0B) Fault
| Stack Fault (0C) Fault
| General Protection Fault (0D) Fault
| Page Fault (0E) Fault
| Coprocessor Error (10) Fault
|
Memory Management Unit (MMU)
386 имеет по крайней мере 2 фичи для облегчения поддержки памяти :
- Segmentation
- Paging
Paging обеспечивает механизм реализации virtual memory ,
который используется большинством операционных систем .
Для реализации paging нужно вначале установить бит 31 в CR0.
Далее - необходимо проинициализировать таблицы Page Directory Entries и Page
Table Entries, PDEs и PTEs.
Эти таблицы используются для конвертации логических адресов в физические .
Только на уровне PL0 можно проинициализировать MMU.
Пользовательские программы вообще ничего не знают о существовании MMU.
Страницы разбиты на 2 типа - User/System.
PL0 имеет доступ к любым страницам . PL3 имеет доступ только к "User" page.
Virtual 8086 (V86/VM86) Mode
80386 имеет 3-й режим , называемый V86 mode.
Это почти тот же 8086. В нем нет доступа к инструкциям
LGDT, LIDT, LLDT, SLDT, ARPL, LAR, LSL,LTR, VERR, VERW, INVD, WBINVD, WRMSR, RDMSR.
Но в отличие от real mode, MMU здесь работает.
Бит VM флагового регистра устанавливается в 1.
Это можно сделать только из PL0.
Переключение между protected mode и real mode - очень медленный процесс .
А выполнение программы в V86 - значительно быстрее.
Для V86 , как и в real mode , используются прямые значения сегментов ,
а не пара selector:offset.
Это дает доступ не более чем к 1MB .
Процесс в V86 всегда выполняется в PL3.
Он может делать I/O , если IOPL = 3 . Если IOPL < 3, генерится General Protection Fault.
Protected Mode
Для переключения в защищенный режим :
- Инициализируем Global Descriptor Table (GDT)
- Инициализируем Interrupt Descriptor Table (IDT)
- Перепрограммируем PIC
- Инициализируем TSS
- Инициализируем Page Tables и CR3 (опционально)
- Нулевой бит регистра CR0 устанавливаем в 1
- Загружаем Task Register (TR)
- Jump TSS selector
|
morik | по моему не совсем верно:
>Как вернуться в Real Mode?
>Для 386:
>
> * Отключаем прерывания
> * far jump в 16-битный кодовый сегмент (переход в 16-битный pmode)
> * грузим в SS селектор 16-битного стека
> * обнуляем PE bit,
если у нас в protected mode был включен paging и у нас виртуальные адреса
выполнения то, тут мы получим triple page fault и как следствие сброс процессора.
в этом месте наш виртуальный адрес должен соответствовать физическому.
> * far jump в real-mode адрес
> * грузим DS, ES, FS, GS, SS real-mode-значениями
> * грузим IDTR (base 0, limit 0xFFFF),
> * разрешаем прерывания
2007-03-22 12:04:35 | Яковлев Се� | В общем - да.
При корректном переключении из защищенного режима в реальный нужно кое-что подготовить.
Вначале мы отключаем прерывания , обнуляем бит PE , потом разрешаем прерывания.
Если выполнить только 3 этих команды подряд ,
то ни к чему хорошему это не приведет,скорее всего к ребуту.
В защищенном и реальном режимах разные форматы у таблицы прерываний :
в первом случае он 8-байтный , во втором случае - 4-х байтный.
1. При переходе PM -> RM нужно загрузить таблицу прерываний типа
lidt fword ptr RM_IDT
2. Затем нужно проинициализировать DS, ES, FS, GS, SS реальными адресами
3. Сделать far jmp для изменения значения кодового сегмента CS:
jmp far ptr L1
L1:
4. При переходе из защищенного режима в реальный
линейный адрес фактическими становится реальным,
поэтому лучше запретить пейджинг ,если он до этого был включен
При переходе PM -> RM нужно загружать сегментные регистры дважды :
1 грузим в них т.н. compatible значения
2 грузим реальные значения
2007-03-22 20:55:56 | Яковлев Се� | Если быть более точным :
Процессор возвращается в реальный режим при очистке бита PE в CR0 с помощью MOV.
Читаем интеловскую доку:
1. Если включен пэйджинг, то:
* Переходим к линейным адресам
* Обнуляем бит PG в CR0.
* Обнуляем CR3 для очистки paging cache.
2. Иначе : переходим к сегментной модели с ограничением в 64K (FFFFH).
Загружаем регистр CS это значение .
3. Загружаем регистры SS, DS, ES, FS, GS селектором,
который имеет следующие биты :
* Limit = 64K (FFFFH)
* Byte granular (G = 0)
* Expand up (E = 0)
* Writable (W = 1)
* Present (P = 1)
* Base = any value
4. Отключаем прерывания с помощью CLI .
5. Обнуляем PE бит.
6. Переходим в реальный режим с помощью far JMP.
Только так мы можем загрузить регистр CS.
7. Грузим таблицу прерываний реального режима с помощью LIDT.
8. Разрешаем прерывания.
9. Загружаем сегментные регистры реальными значениями.
2007-04-05 22:10:03 | |
|