Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 OS
 osjournal 
 Protected Mode 
 Hardware 
 Kernels
  Dark Fiber
  BOS
  QNX
=>  OS Dev
  Lecture notes
  MINIX
  OS
  Solaris
  История UNIX
  История FreeBSD
  Сетунь
NEWS
Последние статьи :
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
  SQL 30.07   
  JFS 10.06   
  B-trees 01.06   
 
TOP 20
 Go Web ...531 
 Steve Pate 3...430 
 Rodriguez 6...405 
 Trees...390 
 TCP 3...369 
 Rodriguez 2...362 
 Rubni-Corbet -> Глав...352 
 Стивенс 9...333 
 Daniel Bovet 3...333 
 Robert Love 3...329 
 B.Blunden 1...327 
 UML 3...327 
 Steve Pate 1...327 
 Robert Love 2...324 
 Максвелл 1...322 
 Максвелл 1...322 
 Robbins 1...321 
 Daniel Bovet 4...321 
 Mod_perl 2...320 
 Rubni-Corbet -> Глав...316 
 
  01.04.2017 : 2166403 посещений 

iakovlev.org

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

 

Включение A20

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



Setup Stack/GDT

Перед переходом в защищенный режим нужно проинициализировать стек.


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