Пишем ОС
Введение
Не давно мне стало интересно на сколько тяжело написать операционную систему, как она вообще устроенна внутри, что должна делать, ну или хотя бы какой минимальный набор функциональности иметь. Так же было интересно низкоуровневое программирование, там где нет помощников и готовых функций, только ты, железо и BIOS, без него было бы совсем туго :). И я решил написать простенькую ОС а попутно подучить уже совсем забытый мной ассемблер, и архитектуру x86.
Первое что я решил заделать это поискать статьи, типа «Пишем простую ОС». Таковых оказалось довольно много но лично я смог уловить по ним только основной принцип написания ОС, довольно поверхностно. Возможно просто мой уровень IQ маловат, хотя признаться я его не когда не измерял, боюсь результата :). Ну так вот открыв кучу вкладок в своём браузере, а также различных исходников ОС в notepad++ я принялся за дело. Ниже я опишу всё что я выяснил о создании собственной ОС.
Загрузка ОС с дискеты
Нашу ОС мы разместим на дискете, по этому давайте рассмотрим процесс загрузки операционной системы с дискеты. Он состоит из нескольких этапов, сначала загружается BIOS (англ. basic input/output system — «базовая система ввода-вывода»), затем BIOS определяет различные устройства в том числе и блочные устройства ввода-вывода, К блочным относятся такие устройства, которые хранят информацию в блоках фиксированной длины, у каждого из которых есть свой собственный адрес. Обычно размеры блоков варьируются от 512 до 32 768 байт. Вся передача данных ведется пакетами из одного или нескольких целых (последовательных) блоков. Важным свойством блочного устройства является то, что оно способно читать или записывать каждый блок независимо от всех других блоков. Среди наиболее распространенных блочных устройств жесткие диски, приводы гибких дисков а так же, приводы компакт-дисков и флэш-накопители USB. Нас же интересуют приводы гибких дисков а именно привод для дискет диаметром 3.5 дюйма и объёмом 1.4МБ(1 474 560 байт)
Немного об устройстве привода. Привод состоит из электронной части и механической, в механической части присутствуют два двигателя, один из них шаговый, шаговым его называют по тому что он вращается не непрерывно, как обычные двигатели, а маленькими точными шагами, шаг двигателя 3.5 дюймового дисковода равен 1,8°,что позволяет с его помощью довольно точно позиционировать головку записи-чтения над определённой областью магнитного диска дискеты. Также к механической части относятся головки чтения-записи они то и считывают-записывают данные на магнитный диск. Их в дисководе две, одна над магнитным диском другая под ним. Ниже показано размещение головок над диском.
Д
иск
имеет магнитное покрытие и разбит на
дорожки и сектора, на одной стороне
диска содержится 80 дорожек, всего сторон
две, нумерация дорожек начинается с 0
по 79, в одной дорожке содержится 18
секторов, емкость одного сектора
составляет 512байт. Нумерация секторов
начинается с первого. Первый сектор
является загрузочным
После определения всех устройств BIOS начинает загрузку с накопителя который выбран как загрузочный. На этом накопителе должен присутствовать загрузочный сектор в котором находится программа загрузки ядра ОС. После чего BIOS прочтёт первый сектор диска, который как правило является загрузочным и если найдёт на нём сигнатуру загрузочного сектора, это два последних байта сектора, имеющие вид в шестнадцатеричной системе AAh,55h, то загрузит этот сектор в оперативную память по адресу 07C00h, и передаст управление загруженному коду. Далее этот код должен загрузить ядро ОС в память и передать ему управление.
Инструментарий
У меня нет ни дискеты ни дисковода, да и писать любую программу куда приятнее если есть возможность запускать её по сто раз за час, дабы убедится в работе каждой написанной строчки кода :). По этому воспользуемся виртуальной машиной, я выбрал Virtual Box. Скачать Virtual Box можно тут https://www.virtualbox.org/ . Моя версия 5.0.0 на Windows 7 64 бит, сразу работать не захотела, порыскав на форумах, оказалось проблема заключается в том, что у многих для установки расширенных тем оформления пропатчен фал Windows\system32\uxtheme.dll. Обычно оригинальный файл находится в том же каталоге и имеет имя uxtheme.dll.backup. Просто поменяйте между собой их названия.
Далее нам понадобится компилятор FASM( flat assembler ) - это свободно распространяемый многопроходной ассемблер, написанный Томашем Грыштаром (польск. Tomasz Grysztar). Скачать его можно с официального сайта http://flatassembler.net/. Писать код мы будем в Notepad++ его скачать можно тут https://notepad-plus-plus.org/. Установите FASM в корневой каталог диска C, затем установите Notepad++.
Также нам понадобится программа способная создать виртуальный образ дискеты, для этих целей я написал на C# простенькую программку, которую назвал imgMaster. Принцып её действия довольно прост, после запуска программы, нажимаем кнопку Add File, и выбираем первый файл, его размер должен составлять 512байт не больше, не меньше, не больше так как он может не поместится в загрузочный сектор размер которого, как мы уже знаем 512байт. А не меньше по тому что 511 и 512 байты, это сигнатура загрузочного сектора, без них BIOS решит что программа загрузчик отсутствует, и не станет не чего загружать из сектора. Далее при необходимости добавляем остальные файлы, и нажимаем Create IMG, сохраняем образ. Программа запишет файлы один за другим, в той же очерёдности в которой они расположены в списке, затем оставшееся место заполнит нулями, чтобы размер образа был равен ёмкости дискеты 1.4МБ(1 474 560 байт).
Пишем Hello World
Можно приступать к написанию первой тестовой программы, которая без операционной системы, будет способна вывести на экран традиционную надпись «Hello World!».
И так первое что нам необходимо сделать это узнать где мы будем находится в ОЗУ. Ниже представлена модель адресного пространства ОЗУ, как я её понял.
Адресное пространство:
Размер |
Назначение |
Физ.Адрес |
1KB |
Векторы прерываний |
00000h |
256B |
Область данных BIOS |
00400h |
638KB |
Первая свободная область
(Суда BIOS помещает данные из загрузочного сектора) |
00500h
07С00h |
64KB |
Графический видео буфер |
A0000h |
32KB |
Вторая свободная область |
B0000h |
32KB |
Текстовый видео буфер |
B8000h |
64KB |
ПЗУ — Расширения BIOS |
C0000h |
128KB |
Третья свободная область |
D0000h |
64KB |
ПЗУ - BIOS |
F0000h |
64KB |
HMA |
100000h |
До 4GB |
XMS Четвёртая свободная область |
10FFF0h |
Первые 640 Кбайт (до графического видео буфера) называются стандартной (conventional) памятью. Начинается стандартная память с килобайта, который содержит векторы прерываний, их 256 на каждый отводится по 4 байта.
Затем идет область данных BIOS. Где находятся данные необходимые для корректной работы функций BIOS. Но также можно модифицировать эту область, тем самым мы влияем на ход выполнения системных функций, по сути дела меняя что либо в этой области мы передаем параметры BIOS и его функциям, которые становятся более гибкими. С адреса 500h начинается свободная область до 640КБ. За 640 килобайтами начинается старшая память или верхняя (upper) память, она располагается до 1 мегабайта (до HMA), т.е. она составляет 384 Кбайт. Тут располагаются ПЗУ (постоянно запоминающее устройство ) : текстовый видео буфер (его микросхема рассчитана на диапазон B8000h…BFFFFh) и графический видео буфер (A0000h…AFFFFh). Если требуется вывести текст то его ASCII коды требуется прописать в текстовый видео буфер и вы немедленно увидите нужные символы. F0000h…FFFFFh, а вот и сам BIOS. Так же есть еще одна ПЗУ – ПЗУ расширений BIOS (C0000h…CFFFFh), её задача обслуживание графических адаптеров и дисков. За первым мегабайтом, с адреса 100000h, располагается память именуемая как расширенная память, конец которой до 4 гигабайт. Расширенная память состоит из 2х подуровней: HMA и XMS. Высокая память (High MemoryArea, HMA) доступна в реальном режиме, а это еще плюс 64 Кбайт (точнее 64 Кбайт – 16 байт), но для этого надо разрешить линию A20 (открыв вентиль GateA20). Функционирование расширенной памяти подчиняется спецификации расширенной памяти (Expanded Memory Specification, XMS), поэтому саму память назвали XMS- памятью, но она доступна только в защищенном режиме.
Красным отмечен адрес начала нашей программы, теперь мы можем смело указать компилятору с какого адреса начинать размещать данные в оперативной памяти. Для этого служит директива org. Давайте приступим к написанию программы. Запустите Notepad++, затем создайте новый файл и сохраните его где вам удобно, назвав boot.asm. После чего Notepad++ начнёт подсвечивать ассемблерный синтаксис. Теперь набираем данный код, сообщая компилятору адрес начала нашей программы.
org 07C00h ;BIOS помещает нашу программу по адресу 07C00h, поэтому указываем что все ;данные программы мы будем размещать начиная с этого адреса
Далее необходимо провести инициализацию регистров данных и стека.
;Инициализация
;-------------
cli ;Запретить прерывания, чтобы не чего не отвлекало :)
xor ax,ax ;Обнуляем регистр ax
mov ds,ax ;Сегмент данных. Наши данные размещены от начала программы
mov ss,ax ;Сегмент стека.
mov sp,07C00h ;Стек начинается в начале программы и растёт в ;противоположную сторону, в начало памяти
sti ;Разрешить прерывания
Давайте узнаем какой размер стека у нас получится при такой инициализации. Для этого выполним следующие действия: 7C00h – 0500h = 7700h. Где 7C00h – адрес начала нашей программы, 0500h – адрес начала свободного пространства. Полученное шестнадцатеричное значение 7700h, это размер стека в байта, переведём его в десятичное получим 30464байт, разделим на 1024 и получим 29КБ. Для наших целей этого более чем достаточно.
Теперь для удобства работы давайте создадим несколько макросов, в дальнейшем мы можем вынести их в отдельный файл.
macro displayClean ;Очистка дисплея
{
mov ax, 02h ;очищаем экран - функция 02h прерывания 10h
int 10h
}
Макрос displayClean служит для очистки экрана от всего лишнего.
Далее идёт макрос setCursor служащий для установки курсора в нужную нам позицию, имеющий два аргумента, координату по оси X, и координату по оси Y. От местоположения курсора зависит, место куда будет выведен следующий символ выводимой нами строки.
macro setCursor x, y ;Установить курсор
{
mov dx,x
mov dh,y
;установка курсора : функция 02h прерывания 10h
mov ah,02h
xor bh,bh
int 10h
}
И последний необходимый нам макрос Print будет выводить нашу строку, у него есть три аргумента, string – адрес первого символа строки, length — количество символов строки, и color — цвет строки.
macro print string, lenght, color ;Вывод строки
{
mov bp,string ;Адрес строки
mov cx,lenght ;Длина строки
mov bl,color ;в регистре bl- атрибут цвета
mov ah,13h ;функция 13h прерывания 10h
mov al,01h ;строка только с символами + смещение курсора
;xor dx,dx ;dh-номер строки на экране, dl - номер столбца на экране
int 10h
}
После определения макросов можно разместить нашу строку в памяти
msg0 db "Hello World!" ;Текст сообщения
msg0_len = $ - msg0 ;Длина строки, $ текущий адрес минус адрес начала строки msg0
Далее следует этот код:
displayClean ;Очистим экран с помощью подготовленного нами макроса
setCursor 0, 0 ;Утановим курсор в верхний левый угол
println msg0, msg0_len, 03h ;Выведим приветствие
На этом наша программа закончена осталось забить оставшееся место ( минус два байта сигнатуры ) нулями, чтобы файл занимал равно 512байт.
times (512-2)-($-$$) db 0 ;Цикл выполняется (512-2)-($-$$) раз. Где
;512 - размер загрузочного сектора
;2 - два последних байта
;$ - текущий адрес в памяти
;$$ - Начальный адрес в памяти
И записать два байта сигнатуры, указывающие BIOS-у что это кот загрузчика
;Конец загрузочного сектора
;---------------
db 0AAh,55h ;сигнатура, символизирующая о завершении загрузочного сектора
;последние два байта - признак конца таблицы MBR - код 0xAA55.
;──────────────────────────────END──────────────────────────
Давайте теперь скомпилируем наш код. Для этого нажмите F5, в появившемся окне введите, C:\fasm\FASM.EXE(FULL_CURRENT_PATH), нажмите сохранить, и выберите комбинацию клавиш для быстрого вызова. Теперь после нажатия выбранной комбинации, компилятор FASM скомпилирует нашу программу, и рядом с исходным кодом появится бинарный файл, этот файл и будет нашим загрузчиком, который мы должны будем записать на первый сектор дискеты.
Теперь давайте проверим работу нашей программы. Запустите imgMaster и добавте в список скомпилированный файл, он должен называется boot.bin, затем нажмите Create IMG и сохраните образ дискеты. Далее запустите Virtual Box и нажмите Создать, затем заполните поля как на рисунке ниже:
Д
алее
жмём создать и выставляем:
И
снова жмём создать
После чего в списке появится наша новенькая виртуальная машина. Выбираем её и жмём Настроить, выбираем вкладку Носители, мы видим что к контроллеру IDE у нас подключен один жёсткий диск My_OS.vdi и один CD-ROM, поскольку CD-ROM мы использовать не будем мы можем его удалить, что бы не мешал. После того как мы удалили CD-ROM, жмём зелёную кнопку Добавить новый контроллер и выбираем Добавить Floppy контроллер. Затем на появившемся в списке контроллере жмём добавить привод гибких дисков, откроется окно с предложением выбрать образ, вбираем сохранённый ранее образ и жмём ОК. Теперь запускаем нашу виртуальную машину. После запуска на экране у нас должно отобразится приветствие Hello World!.
Ну вот наша первая программа которая работает без ОС готова, в дальнейшем она научится сама читать файлы с диска в ОЗУ и превратится в полноценный загрузчик ядра ОС.