OS Development
|
При написании операционной системы все обычно начинают с загрузчика.
Что такое загрузчик?
Это код , который загружает систему при старте.
Он должен быть размещен на 1-м секторе (512 байт) загрузочного устройства.
Этот сектор загружается биосом в память по физическому адресу 0x0:0x7C0,
он же линейный адрес 0x7C00.
После загрузки в память бут-сектор запускается.
Первым делом нужно загрузить ядро.
Далее :
*включение A20 (доступ ко всей памяти)
*вход в Protected mode (это даст нам 32-битную адресацию)
*инициализация GDT
*инициализация IDT
*инициализация 32-битного стека
*jmp -> kernel
Задачи:
1-enable A20
2-load kernel
3-enter Pmode
4-setup temp GDT
5-setup temp IDT(optional)
6-setup 32bit stack
7-refresh registers
8-jump to kernel
Optional:
-можно скопировать GDT & IDT в память куда подальше
Бутсектор по размеру должен быть в точности 521 байт и его последние 2 байта должны быть 0x55AA.
Asm:
TIMES 510-($-$$) DB 0
SIGNATURE DW 0xAA55
Мы обнулили превые 510 байт.
Этот код должен стоять в самом конце бутсектора.
Во время загрузки бутсектора можно использовать стандартные функции биоса для вывода мессаг на экран.
20-й гейт - это всего лишь бит в контроллере клавиатуры.
Этот бит дает разрешение процессору получить доступ к памяти через 20-битную шину данных.
Для включения нужен доступ к портам 0x64 и 0x60(kbd). Например так :
Список портов клавиатуры:
8042 kbd controller ports
PORT |
Действие |
Назначение |
0x60 |
READ |
Output register для чтения данных с клавиатуры |
0x60 |
WRITE |
Data register for sending kbd controller commands |
0x64 |
READ |
Status register для получения статуса клавиатуры в любой момент |
0x64 |
WRITE |
Commmand register used to set kbd controller options(like the A20 gate) |
Итак:
1-запрещаем прерывания
2-ждем пока порт станет доступным
3-говорим контролеру что хотим записать в A20
4-просим у контролера output port
5-ждем
6-получаем статус и выполняем операцию OR на 2(бит 2[00000010] для A20)
7-пишем данные в порт
8-пишем 'nop' в контролер
9-ждем
10-разрешаем прерывание
Итак сделано еще 10 шагов :)
Нужно заполнить регистры следующими значениями и выполнить .
ah |
BIOS function(2) |
al |
число секторов для загрузки в память |
es:bx |
segment:offset памяти |
ch |
track number |
cl |
starting sector |
dh |
head number |
dl |
drive number |
*ah |
(on error)sectors that were read in* |
Теперь вызываем прерывание int 13h.
Перед тем как читать ядро , надо знать его размер.
Пусть например ядро равно 1 килобайту.
Это значит , что нужно прочитать 2 сектора ,
поскольку 1 сектор - 512 байт.
Нужно помнить о том , что если вы за раз читаете более 18 секторов ,
биос может изменять значения некоторых регистров.
Поэтому при каждом чтении порции секторов нужно обнулять все регистры.
Перед переходом в защищенный режим нужно проинициализировать стек.
SS |
SP |
Linear Address |
0x0100: |
0x0200 = |
0x1200 |
Asm:
mov ax,0x100
mov ss,ax
mov sp,0x200
Здесь значение SP=0x200 указывает на то , что размер стека 512 байт.
Значение в SS - значение самого стекового сегмента.
GDT - таблица для инициализации памяти.
Структура GDT :
GDT
Lowest Byte |
Byte 1 |
Byte 2 |
Byte 3 |
Byte 4 |
Byte 5 |
Byte 6 |
Highest Byte |
Limit |
Limit |
Base |
Base |
Base |
Type |
Flags & Limit |
Base |
[0-7] |
[8-15] |
[0-7] |
[8-15] |
[16-23] |
|
|
[24-31] |
Limit:
Ограничение размера сегмента в PMode - т.е 0xFFFFF
Base: Базовый адрес сегмента (т.е.-0x0)
Type: Тип сегмента (т.е.-code/data/writable/readable/stack/ring3..0)
Flags: Дополнительные опции для 32bit или 16bit
Type Byte Table - 5-й байт
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
P |
DPL |
S |
C/D |
E |
W |
A |
|
2 bits |
|
The Type Nibble |
Present: 7-й бит - есть сегмент - нет сегмента
DPL: Descriptor Privilege Levevl (ring3..0)
Segment Type: Системный - не - системный (т.е.-System=1)
C/D: Код или данные
Expand-Down: Куда растет сегмент - вниз или вверх
Write: На запись или на чтение?
Accessed: Доступ к сегменту
Проинициализируем GDT и загрузим информацию о GDT в специальный регистр :
Asm:
GDTR:
GDTsize DW 0x10 ; limit
GDTbase DD 0x50 ; base address
GDT начинается с адреса 0x500 и занимает 0x10 байт. Загрузим ее:
Asm:
lgdt[GDTR]
Пока это только временная GDT. Начинаться она должна с нулевого дескриптора :
In Asm:
NULL_SEL
DD 0
DD 0
Все , что нужно - изменить один бит в регистре cr3(control register 3).
Enabling Pmode:
1- прочитать cr0
2- сделать для cr0 операцию OR на 1(bit 1[00000001] )
3- полученное значение пишем в cr3
После разрешения защищенного режима и загрузки GDTR ,
нужно переходить в область памяти , куда мы загрузили собственно ядро.
Например , если ядро загружено по адресу 0x5000 :
In Asm:
jmp CODESEL:0x5000
In Asm(usually CODESEL=0x08):
jmp 0x08:0x5000
0x08 - это смещение селектора внутри таблицы GDT.
Для загрузки ядра также необходим специальный линковочный скрипт.
Секция защищенного режима обычно начинается примерно так :
In Asm:
[bits 32] ; 32bit code here
[global start] ; start is a global function
[extern _k_main] ; this is the kernel function
start:
call _k_main ; jump to k_main() in kernel.c
hlt ; halt the cpu
Компиляция :
nasm -f bin boot.asm
Или так :
nasm -f bin boot.asm -o boot.bin
|
Итак мы загрузили ядро.
Стартовая функция ядра может выглядеть так :
void k_main()
{
int num;
char ch;
char *str="Kernel Loaded";
return;
}
Чтобы вывести сообщение на экран , нам понадобится Screen I/O.
Для этого нужного знать ascii-код выводимого символа и его цвет.
Для этого есть область памяти начиная с адреса 0xB8000.
Для вывода каждого последующего сообщения указатель увеличивается на 2 байта -
символ+атрибут.
void _k_main()
{
int num;
char ch;
char *text_video = (char*)0xB8000;
char attrib = 0x07;
char *str="Kernel Loaded";
while(*str!=0)
{
*text_video = *str;
*text_video++;
*text_video = attrib;
*text_video++;
*str++;
}
return;
}
Очистка экрана - другая простая задача для текстового указателя.
Для этого просто каждому текстовому символу нужно присвоить нулевое значение.
void clear_screen(char clear_to, char attrib)
{
char *text_video = (char*)0xB8000;
int pos=0;
while(pos<(80*25*2))
{
*text_video = clear_to;
*text_video++;
*text_video = attrib;
*str++;
pos++;
}
}
Цветовые атрибуты :
-{TEXT COLORS}-
FG AND BG
0 = black
1 = blue
2 = green
3 = cyan
4 = red
5 = magenta
6 = brown
7 = white (standard text color)
FG ONLY
8 = dark grey
9 = bright blue
10 = bright green
11 = bright cyan
12 = pink
13 = bright magenta
14 = yellow
15 = bright white
[IBBBFFFF] binary
I = Intensity (blink)
B = Background
F = Foreground
Рассмотрим пример :
0x07 - белый текст на черном фоне .
Если нам допустим нужен красный цвет на белом фоне , то :
red = 4
white = 7
Итого получаем - 0x74.
Компиляция ядра - сводится к нескольким шагам :
-{Step by Step}-
1- компилируем все файлы с расширением *.c
>gcc *.c
2- компилируем все asm-файлы в формат aout (не бинарный)
>nasm *.asm -f aout
3- линкуем полученные обьектные файлы в один файл - например в kernel.o
>ld -T linkscript.ld -o kernel.o a.o b.o c.o
4- компилируем отдельно загрузчик boot.asm и сливаем его в ядро
>nasm boot.asm
>copy /b boot.bin+kernel.o kernel.img
5- пишем полученный образ на загрузочное устройство
>floppyout kernel.img a: -sector 0 -head 0 -track 0
6- ложим загрузочное устройство в микроволновку , 30 секунд - и все готово!
+-------------------------+
| ||===========|| [00:30] |
| || || 7 8 9 |
| || || 4 5 6 |
| || || 1 2 3 |
| || || X 0 X |
| ||===========|| (start) |
+-------------------------+
\_/ \_/
Надеюсь , у вас хватит юмора опустить 6-й шаг.
Если нужна дополнительная помощь - пишите.
|
Михаил | Компиляция не пройдет 2012-09-14 15:58:17 | |
|
|