Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

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:
BitsFunctionDescription
0-15Limit 0:15ограничение на размер сегмента
16-31Base 0:15стартовый адрес сегмента

Структура GDT - следующие 32 бита :

Double word:
BitsFunctionDescription
0-7Base 16:23базовый адрес
8-12TypeSegment type and attributes
13-14Privilege Level0 = Highest privilege (OS), 3 = Lowest privilege (User applications)
15Present flagSet to 1 if segment is present
16-19Limit 16:19Bits 16-19 in the segment limiter
20-22AttributesDifferent attributes, depending on the segment type
23GranularityUsed together with the limiter, to determine the size of the segment
24-31Base 24:31The 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 typeUse
0 - 3FFRAMReal mode, IVT (Interrupt Vector Table)
400 - 4FFRAMBDA (BIOS data area)
500 - 9FFFFRAMFree memory, 7C00 used for boot sector
A0000 - BFFFFVideo RAMVideo memory
C0000 - C7FFFVideo ROMVideo BIOS
C8000 - EFFFF?BIOS shadow area
F0000 - FFFFFROMSystem 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 индекса .


InterruptDescription
0Divide by Zero
1Debug: Trap
2Nonmaskable external interrupt
3Debug: Breakpoint
4Overflow
5BOUND range exceeded(?)
6Invalid opcode
7No Math Cooprocessor (when trying to use it)
8Double fault (something went terrably wrong!)
9Cooprocessor segment overrun(?)
10Invalid Task switch
11Memory: Segment not present
12Memory: Stack-segment fault
13Memory: General protection
14Memory: Page fault
15Reserved by Intel
16FPU error or WAIT/FWAIT instruction
17Memory: Alignment check
18Machine Check(?)
19SSE/SSE2-Exception
20-31Reserved 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 1PIC 2
BitInterrupt to mask
0System Timer
1Keyboard
2Redirect to IRQ9 (PIC2)
3Serial Port 1 (COM2/4)
4Serial Port 2 (COM1/3)
5Sound Card
6Floppy Disk
7Parallell Port
BitInterrupt to mask
0Real-Time Clock
1Redirect from IRQ2 (PIC1)
2Reserved
3Reserved
4PS/2 Mouse
5Math Co-Processor
6Hard Disk
7Reserved

Если бит в маске установлен , процессор его проигнорирует. Поскольку нам нужно только прерывание клавиатуры , нужно установить 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 - определяет , из какой таблицы берется дескриптор .

Этих таблиц три :

  1. Global Descriptor Table (GDT)
  2. Interrupt Descriptor Table (IDT)
  3. 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 фичи для облегчения поддержки памяти :

  1. Segmentation
  2. 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

Для переключения в защищенный режим :

  1. Инициализируем Global Descriptor Table (GDT)
  2. Инициализируем Interrupt Descriptor Table (IDT)
  3. Перепрограммируем PIC
  4. Инициализируем TSS
  5. Инициализируем Page Tables и CR3 (опционально)
  6. Нулевой бит регистра CR0 устанавливаем в 1
  7. Загружаем Task Register (TR)
  8. 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