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
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...2313 
 Trees...1445 
 William Gropp...1408 
 Ethreal 3...1396 
 Ethreal 4...1373 
 C++ Patterns 3...1371 
 Rodriguez 6...1361 
 Максвелл 3...1360 
 Go Web ...1359 
 Robert Love 5...1359 
 Httpd-> История Ap...1358 
 Максвелл 5...1356 
 K&R 1...1354 
 Ext4 FS...1353 
 Rubni-Corbet -> Глав...1352 
 Kamran Husain...1351 
 Perl OOP...1351 
 Сетунь...1349 
 OS ->Intel Manual 1...1349 
 Erlang...1349 
 
  01.01.2024 : 3621733 посещений 

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