sonyps4.ru

Разработка операционной системы с нуля. Что нужно знать, чтобы написать операционную систему

Оригинал: AsmSchool: Make an operating system
Автор: Mike Saunders
Дата публикации: 15 апреля 2016 г.
Перевод: А. Панин
Дата перевода: 16 апреля 2016 г.

Часть 4: Располагая навыками, полученными в ходе чтения предыдущих статей серии, вы можете приступить к разработке своей собственной операционной системы!

Для чего это нужно?

  • Для понимания принципов работы компиляторов.
  • Для понимания инструкций центрального процессора.
  • Для оптимизации вашего кода в плане производительности.

В течение нескольких месяцев мы прошли сложный путь, который начался с разработки простых программ на языке ассемблера для Linux и закончился в прошлом статье серии разработкой самодостаточного кода, исполняющегося на персональном компьютере без операционной системы. Ну а сейчас мы попытаемся собрать всю информацию воедино и создать самую настоящую операционную систему. Да, мы пойдем по стопам Линуса Торвальдса, но для начала стоит ответить на следующие вопросы: "Что же представляет собой операционная система? Какие из ее функций нам придется воссоздать?".

В данной статье мы сфокусируемся лишь на основных функциях операционной системы: загрузке и исполнении программ. Сложные операционные системы выполняют гораздо большее количество функций, таких, как управление виртуальной памятью и обработка сетевых пакетов, но для их корректной реализации требуются годы непрерывной работы, поэтому в данной статье мы рассмотрим лишь основные функции, присутствующие в любой операционной системе. В прошлом месяце мы разработали небольшую программу, которая умещалась в 512-байтовом секторе флоппи-диска (его первом секторе), а сейчас мы немного доработаем ее с целью добавления функции загрузки дополнительных данных с диска.

Разработка системного загрузчика

Мы могли бы попытаться максимально сократить объем бинарного кода нашей операционной системы с целью его размещения в первом 512-байтовом секторе флоппи-диска, том самом, который загружается средствами BIOS, но в таком случае у нас не будет возможности реализовать какие-либо интересные функции. Поэтому мы будем использовать эти 512 байт для размещения бинарного кода простого системного загрузчика, который будет загружать бинарный код ядра ОС в оперативную память и исполнять его. (После этого мы разработаем само ядро ОС, которое будет загружать бинарный код других программ с диска и также исполнять его, но об этом будет сказано чуть позже.)

Вы можете загрузить исходный код рассмотренных в статье примеров по ссылке www.linuxvoice.com/code/lv015/asmschool.zip . А это код нашего системного загрузчика из файла с именем boot.asm:

BITS 16 jmp short start ; Переход к метке с пропуском описания диска nop ; Дополнение перед описанием диска %include "bpb.asm" start: mov ax, 07C0h ; Адрес загрузки mov ds, ax ; Сегмент данных mov ax, 9000h ; Подготовка стека mov ss, ax mov sp, 0FFFFh ; Стек растет вниз! cld ; Установка флага направления mov si, kern_filename call load_file jmp 2000h:0000h ; Переход к загруженному из файла бинарному коду ядра ОС kern_filename db "MYKERNELBIN" %include "disk.asm" times 510-($-$$) db 0 ; Дополнение бинарного кода нулями до 510 байт dw 0AA55h ; Метка окончания бинарного кода системного загрузчика buffer: ; Начало буфера для содержимого диска

В данном коде первой инструкцией центрального процессора является инструкция jmp , которая расположена после директивы BITS , сообщающей ассемблеру NASM о том, что используется 16-битный режим. Как вы наверняка помните из предыдущей статьи серии, исполнение загружаемого средствами BIOS с диска 512-байтного бинарного кода начинается с самого начала, но нам приходится осуществлять переход к метке для пропуска специального набора данных. Очевидно, что в прошлом месяце мы просто записывали код в начало диска (с помощью утилиты dd), а остальное пространство диска оставляли пустым.

Сейчас же нам придется использовать флоппи-диск с подходящей файловой системой MS-DOS (FAT12), а для того, чтобы корректно работать с данной файловой системой, нужно добавить набор специальных данных рядом с началом сектора. Этот набор называется "блоком параметров BIOS" (BIOS Parameter Block - BPB) и содержит такие данные, как метка диска, количество секторов и так далее. Он не должен интересовать нас на данном этапе, так как подобным темам можно посвятить не одну серию статей, именно поэтому мы разместили все связанные с ним инструкции и данные в отдельном файле исходного кода с именем bpb.asm .

Исходя из вышесказанного, данная директива из нашего кода крайне важна:

%include "bpb.asm"

Это директива NASM, позволяющая включить содержимое указанного файла исходного кода в текущий файл исходного кода в процессе ассемблирования. Таким образом мы сможем сделать код нашего системного загрузчика максимально коротким и понятным, вынеся все подробности реализации блока параметров BIOS в отдельный файл. Блок параметров BIOS должен располагаться через три байта после начала сектора, а так как инструкция jmp занимает лишь два байта, нам приходится использовать инструкцию nop (ее название расшифровывается как "no operation" - это инструкция, которая не делает ничего, кроме траты циклов центрального процессора) с целью заполнения оставшегося байта.

Работа со стеком

Далее нам придется использовать инструкции, аналогичные рассмотренным в прошлой статье, для подготовки регистров и стека, а также инструкцию cld (расшифровывается как "clear direction"), позволяющую установить флаг направления для определенных инструкций, таких, как инструкция lodsb , которая после ее исполнения будет увеличивать значение в регистре SI , а не уменьшать его.

После этого мы помещаем адрес строки в регистр SI и вызываем нашу функцию load_file . Но задумайтесь на минуту - мы ведь еще не разработали эту функцию! Да, это правда, но ее реализацию можно найти в другом подключаемом нами файле исходного кода с именем disk.asm .

Файловая система FAT12, используемая на флоппи-дисках, которые форматируются в MS-DOS, является одной простейших существующих файловых систем, но для работы с ее содержимым также требуется немалый объем кода. Подпрограмма load_file имеет длину около 200 строк и не будет приведена в данной статье, так как мы рассматриваем процесс разработки операционной системы, а не драйвера для определенной файловой системы, следовательно, не очень разумно тратить таким образом место на страницах журнала. В общем, мы подключили файл исходного кода disk.asm практически перед окончанием текущего файла исходного кода и можем забыть про него. (Если же вас все-таки заинтересовала структура файловой системы FAT12, вы можете ознакомиться с отличным обзором по адресу http://tinyurl.com/fat12spec , после чего заглянуть в файл исходного кода disk.asm - код, содержащийся в нем, хорошо прокомментирован.)

В любом случае, подпрограмма load_file загружает бинарный код из файла с именем, заданном в регистре SI , в сегмент 2000 со сдвигом 0, после чего мы осуществляем переход к его началу для исполнения. И это все - ядро операционной системы загружено и системный загрузчик выполнил свою задачу!

Вы наверняка заметили, что в качестве имени файла ядра операционной системы в нашем коде используется MYKERNELBIN вместо MYKERNEL.BIN , которое вполне вписывается в схему имен 8+3, используемую на флоппи-дисках в DOS. На самом деле, в файловой системе FAT12 используется внутреннее представление имен файлов, а мы экономим место, используя имя файла, которое гарантированно не потребует реализации в рамках нашей подпрограммы load_file механизма поиска символа точки и преобразования имени файла во внутреннее представление файловой системы.

После строки с директивой подключения файла исходного кода disk.asm расположены две строки, предназначенные для дополнения бинарного кода системного загрузчика нулями до 512 байт и включения метки окончания его бинарного кода (об этом говорилось в прошлой статье). Наконец, в самом конце кода расположена метка "buffer" , которая используется подпрограммой load_file . В общем, подпрограмме load_file требуется свободное пространство в оперативной памяти для выполнения некоторых промежуточных действий в процессе поиска файла на диске, а у нас есть достаточно свободного пространства после загрузки системного загрузчика, поэтому мы размещаем буфер именно здесь.

Для ассемблирования системного загрузчика следует использовать следующую команду:

Nasm -f bin -o boot.bin boot.asm

Теперь нам нужно создать образ виртуального флоппи-диска в формате MS-DOS и добавить бинарный код нашего системного загрузчика в его первые 512 байт с помощью следующих команд:

Mkdosfs -C floppy.img 1440 dd conv=notrunc if=boot.bin of=floppy.img

На этом процесс разработки системного загрузчика можно считать оконченным! Теперь у нас есть образ загрузочного флоппи-диска, который позволяет загрузить бинарный код ядра операционной системы из файла с именем mykernel.bin и исполнить его. Далее нас ждет более интересная часть работы - разработка самого ядра операционной системы

Ядро операционной системы

Мы хотим, чтобы наше ядро операционной системы выполняло множество важных задач: выводило приветствие, принимало ввод от пользователя, устанавливало, является ли ввод поддерживаемой командой, а также исполняло программы с диска после указания пользователем их имен. Это код ядра операционной системы из файла mykernel.asm:

Mov ax, 2000h mov ds, ax mov es, ax loop: mov si, prompt call lib_print_string mov si, user_input call lib_input_string cmp byte , 0 je loop cmp word , "ls" je list_files mov ax, si mov cx, 32768 call lib_load_file jc load_fail call 32768 jmp loop load_fail: mov si, load_fail_msg call lib_print_string jmp loop list_files: mov si, file_list call lib_get_file_list call lib_print_string jmp loop prompt db 13, 10, "MyOS > ", 0 load_fail_msg db 13, 10, "Not found!", 0 user_input times 256 db 0 file_list times 1024 db 0 %include "lib.asm"

Перед рассмотрением кода следует обратить внимание на последнюю строку с директивой подключения файла исходного кода lib.asm , который также находится в архиве asmschool.zip с нашего веб-сайта. Это библиотека полезных подпрограмм для работы с экраном, клавиатурой, строками и дисками, которые вы также можете использовать - в данном случае мы подключаем этот файл исходного кода в самом конце основного файла исходного кода ядра операционной системы для того, чтобы сделать последний максимально компактным и красивым. Обратитесь к разделу "Подпрограммы библиотеки lib.asm" для получения дополнительной информации обо всех доступных подпрограммах.

В первых трех строках кода ядра операционной системы мы осуществляем заполнение регистров сегментов данными для указания на сегмент 2000, в который была осуществлена загрузка бинарного кода. Это важно для гарантированной корректной работы таких инструкций, как lodsb , которые должны читать данные из текущего сегмента, а не из какого-либо другого. После этого мы не будем выполнять каких-либо дополнительных операций с сегментами; наша операционная система будет работать с 64 Кб оперативной памяти!

Далее в коде расположена метка, соответствующая началу цикла. В первую очередь мы используем одну из подпрограмм из библиотеки lib.asm , а именно lib_print_string , для вывода приветствия. Байты 13 и 10 перед строкой приветствия являются символами перехода на новую строку, благодаря которым приветствие будет выводиться не сразу же после вывода какой-либо программы, а всегда на новой строке.

После этого мы используем другую подпрограмму из библиотеки lib.asm под названием lib_input_string , которая принимает введенные пользователем с помощью клавиатуры символы и сохраняет их в буфере, указатель на который находится в регистре SI. В нашем случае буфер объявляется ближе к концу кода ядра операционной системы следующим образом:

User_input times 256 db 0

Данное объявление позволяет создать буфер длиной в 256 символов, заполненный нулями - его длины должно быть достаточно для хранения команд такой простой операционной системы, как наша!

Далее мы выполняем проверку пользовательского ввода. Если первый байт буфера user_input является нулевым, то пользователь просто нажал клавишу Enter, не вводя какой-либо команды; не забывайте о том, что все строки оканчиваются нулевыми символами. Таким образом, в данном случае мы должны просто перейти к началу цикла и снова вывести приветствие. Однако, в том случае, если пользователь вводит какую-либо команду, нам придется сначала проверить, не ввел ли он команду ls . До текущего момента вы могли наблюдать в наших программах на языке ассемблера лишь сравнения отдельных байт, но не стоит забывать о том, что также имеется возможность осуществления сравнения двухбайтовых значений или машинных слов. В данном коде мы сравниваем первое машинное слово из буфера user_input с машинным словом, соответствующим строке ls и в том случае, если они идентичны, перемещаемся к расположенному ниже блоку кода. В рамках этого блока кода мы используем другую подпрограмму из библиотеки lib.asm для получения разделенного запятыми списка расположенных на диске файлов (для хранения которого должен использоваться буфер file_list), выводим этот список на экран и перемещаемся назад в цикл для обработки пользовательского ввода.

Исполнение сторонних программ

Если пользователь не вводит команду ls , мы предполагаем, что он ввел имя программы с диска, поэтому имеет смысл попытаться загрузить ее. Наша библиотека lib.asm содержит реализацию полезной подпрограммы lib_load_file , которая осуществляет разбор таблиц файловой системы FAT12 диска: она принимает указатель на начало строки с именем файла посредством регистра AX , а также значение смещения для загрузки бинарного кода из файла программы посредством регистра CX . Мы уже используем регистр SI для хранения указателя на строку с пользовательским вводом, поэтому мы копируем этот указатель в регистр AX , после чего помещаем значение 32768, используемое в качестве смещения для загрузки бинарного кода из файла программы, в регистр CX .

Но почему мы используем именно это значение в качестве смещения для загрузки бинарного кода из файла программы? Ну, это просто один из вариантов карты распределения памяти для нашей операционной системы. Из-за того, что мы работаем в одном сегменте размером в 64 Кб, а бинарный код нашего ядра загружен со смещением 0, нам приходится использовать первые 32 Кб памяти для данных ядра, а остальные 32 Кб - для данных загружаемых программ. Таким образом, смещение 32768 является серединой нашего сегмента и позволяет предоставить достаточный объем оперативной памяти как ядру операционной системы, так и загружаемым программам.

После этого подпрограмма lib_load_file выполняет крайне важную операцию: если она не может найти файл с заданным именем на диске или по какой-то причине не может считать его с диска, она просто завершает работу и устанавливает специальный флаг переноса (carry flag). Это флаг состояния центрального процессора, который устанавливается в процессе выполнения некоторых математических операций и в данный момент не должен нас интересовать, но при этом мы можем определять наличие этого флага для принятия быстрых решений. Если подпрограмма lib_load_asm устанавливает флаг переноса, мы задействуем инструкцию jc (переход при наличии флага переноса - jump if carry) для перехода к блоку кода, в рамках которого осуществляется вывод сообщения об ошибке и возврат в начало цикла обработки пользовательского ввода.

В том же случае, если флаг переноса не установлен, можно сделать вывод, что подпрограмма lib_load_asm успешно загрузила бинарный код из файла программы в оперативную память по адресу 32768. Все что нам нужно в этом случае - это инициировать исполнение бинарного кода, загруженного по этому адресу, то есть начать исполнение указанной пользователем программы! А после того, как в этой программе будет использована инструкция ret (для возврата в вызывающий код), мы должны будем просто вернуться в цикл обработки пользовательского ввода. Таким образом мы создали операционную систему: она состоит из простейших механизмов разбора команд и загрузки программ, реализованных в рамках примерно 40 строк ассемблерного кода, хотя и с большой помощью со стороны подпрограмм из библиотеки lib.asm .

Для ассемблирования кода ядра операционной системы следует использовать следующую команду:

Nasm -f bin -o mykernel.bin mykernel.asm

После этого нам придется каким-то образом добавить файл mykernel.bin в файл образа флоппи-диска. Если вы знакомы с приемом монтирования образов дисков с помощью loopback-устройств, вы можете получить доступ к содержимому образа диска floppy.img , воспользовавшись им, но существует и более простой способ, заключающийся в использовании инструментария GNU Mtools (www.gnu.org/software/mtools). Это набор программ для работы с флоппи-дисками, на которых используются файловые системы MS-DOS/FAT12, доступный из репозиториев пакетов программного обеспечения всех популярных дистрибутивов Linux, поэтому вам придется лишь воспользоваться утилитой apt-get , yum , pacman или любой другой утилитой, используемой для установки пакетов программного обеспечения в вашем дистрибутиве.

После установки соответствующего пакета программного обеспечения для добавления файла mykernel.bin в файл образа диска floppy.img вам придется выполнить следующую команду:

Mcopy -i floppy.img mykernel.bin::/

Обратите внимание на забавные символы в конце команды: двоеточие, двоеточие и слэш. Теперь мы почти готовы запуску нашей операционной системы, но какой в этом смысл, пока для нее не существует приложений? Давайте исправим это недоразумение, разработав крайне простое приложение. Да, сейчас вы будете разрабатывать приложение для своей собственной операционной системы - просто представьте, насколько поднимется ваш авторитет в рядах гиков. Сохраните следующий код в файле с именем test.asm:

Org 32768 mov ah, 0Eh mov al, "X" int 10h ret

Данный код просто использует функцию BIOS для вывода символа "X" на экран, после чего возвращает управление вызвавшему его коду - в нашем случае этим кодом является код операционной системы. Строка org , с которой начинается исходный код приложения, является не инструкцией центрального процессора, а директивой ассемблера NASM, сообщающей ему о том, что бинарный код будет загружен в оперативную память со смещением 32768, следовательно, необходимо пересчитать все смещения с учетом данного обстоятельства.

Данный код также нуждается в ассемблировании, а получившийся в итоге бинарный файл - в добавлении в файл образа флоппи-диска:

Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin::/

Теперь глубоко вздохните, приготовьтесь к созерцанию непревзойденных результатов собственной работы и загрузите образ флоппи-диска с помощью эмулятора ПК, такого, как Qemu или VirtualBox. Например, для этой цели может использоваться следующая команда:

Qemu-system-i386 -fda floppy.img

Вуаля: системный загрузчик boot.img , который мы интегрировали в первый сектор образа диска, загружает ядро операционной системы mykernel.bin , которое выводит приветствие. Введите команду ls для получения имен двух файлов, расположенных на диске (mykernel.bin и test.bin), после чего введите имя последнего файла для его исполнения и вывода символа X на экран.

Это круто, не правда ли? Теперь вы можете начать дорабатывать командную оболочку вашей операционной системы, добавлять реализации новых команд, а также добавлять файлы дополнительных программ на диск. Если вы желаете запустить данную операционную систему на реальном ПК, вам стоит обратиться к разделу "Запуск системного загрузчика на реальной аппаратной платформе" из предыдущей статьи серии - вам понадобятся точно такие же команды. В следующем месяце мы сделаем нашу операционную систему более мощной, позволив загружаемым программам использовать системные функции и реализовав таким образом концепцию разделения кода, направленную на сокращение его дублирования. Большая часть работы все еще впереди.

Подпрограммы библиотеки lib.asm

Как говорилось ранее, библиотека lib.asm предоставляет большой набор полезных подпрограмм для использования в рамках ваших ядер операционных систем и отдельных программ. Некоторые из них используют инструкции и концепции, которые пока не затрагивались в статьях данной серии, другие (такие, как подпрограммы для работы с дисками) тесно связаны с особенностями устройства файловых систем, но если вы считаете себя компетентным в данных вопросах, вы можете самостоятельно ознакомиться с их реализациями и разобраться в принципе работы. При этом более важно разобраться с тем, как вызывать их из собственного кода:

  • lib_print_string - принимает указатель на завершающуюся нулевым символом строку посредством регистра SI и выводит эту строку на экран.
  • lib_input_string - принимает указатель на буфер посредством регистра SI и заполняет этот буфер символами, введенными пользователем с помощью клавиатуры. После того, как пользователь нажимает клавишу Enter, строка в буфере завершается нулевым символом и управление возвращается коду вызывающей программы.
  • lib_move_cursor - перемещает курсор на экране в позицию с координатами, передаваемыми посредством регистров DH (номер строки) и DL (номер столбца).
  • lib_get_cursor_pos - следует вызывать данную подпрограмму для получения номеров текущей строки и столбца посредством регистров DH и DL соответственно.
  • lib_string_uppercase - принимает указатель на начало завершающейся нулевым символом строки посредством регистра AX и переводит символы строки в верхний регистр.
  • lib_string_length - принимает указатель на начало завершающейся нулевым символом строки посредством регистра AX и возвращает ее длину посредством регистра AX .
  • lib_string_compare - принимает указатели на начала двух завершающихся нулевыми символами строк посредством регистров SI и DI и сравнивает эти строки. Устанавливает флаг переноса в том случае, если строки идентичны (для использования инструкции перехода в зависимости от флага переноса jc) или убирает этот флаг, если строки различаются (для использования инструкции jnc).
  • lib_get_file_list - принимает указатель на начало буфера посредством регистра SI и помещает в этот буфер завершающуюся нулевым символом строку, содержащую разделенный запятыми список имен файлов с диска.
  • lib_load_file - принимает указатель на начало строки, содержащей имя файла, посредством регистра AX и загружает содержимое файла по смещению, переданному посредством регистра CX . Возвращает количество скопированных в память байт (то есть, размер файла) посредством регистра BX или устанавливает флаг переноса, если файл с заданным именем не найден.

Что нужно знать, чтобы написать операционную систему

Создание операционной системы - одна из сложнейших задач в программировании, поскольку требует обширных и комплексных знаний о работе компьютера. Каких именно? Разбираемся ниже.

Что такое ОС

Операционная система (ОС) - это программное обеспечение, которое работает с компьютерным железом и его ресурсами и является мостом между аппаратной и программной частью компьютера.

Компьютеры первого поколения не имели операционных систем. Программы на первых ЭВМ включали в себя код для непосредственной работы системы, связи с периферийными устройствами и вычислений, для выполнения которых эта программа и писалась. Из-за такого расклада даже простые по логике работы программы были сложны в программной реализации.

По мере того как компьютеры становились более разнообразными и сложными, писать программы, которые работали и как ОС, и как приложение, стало попросту неудобно. Поэтому, чтобы программы было легче писать, владельцы компьютеров начали разрабатывать программное обеспечение. Так и появились операционные системы.

ОС предоставляет всё необходимое для работы пользовательских программ. Их появление означало, что теперь программам не нужно контролировать весь объём работ компьютера (это отличный пример инкапсуляции). Теперь программам нужно было работать именно с операционной системой, а система уже сама заботилась о ресурсах и работе с периферией (клавиатура, принтер).

Кратко об истории операционных систем

Язык Cи

Как уже упоминалось выше, для написания ОС есть несколько высокоуровневых языков программирования. Однако самый популярный из них - Си.

Начать изучать этот язык можно отсюда . Этот ресурс ознакомит вас с базовыми понятиями и подготовит к более сложным задачам.

«Learn C the Hard Way » - название ещё одной книги. Кроме привычной теории в ней собрано много практических решений. Этот учебник расскажет обо всех аспектах языка.

Либо же можете выбрать одну из этих книг:

  • «The C Programming Language » Кернигхана и Ритчи;
  • «C Programming Absolute Beginner’s Guide » Пэрри и Миллера.

Разработка ОС

После освоения всего необходимого, что касается информатики, языка ассемблера и Cи, вам стоит прочесть хотя бы одну или две книги про непосредственную разработку ОС. Вот несколько ресурсов для этого:

«Linux From Scratch ». Здесь рассматривается процесс сборки операционной системы Linux (учебник переведён на много языков, в том числе и на русский). Тут, как и в остальных учебниках, вам предоставят все необходимые базовые знания. Полагаясь на них можно попробовать себя в создании ОС. Чтобы сделать программную часть ОС более профессиональной, присутствуют дополнения к учебнику: «

рТЙЧЕФУФЧХА ЧУЕИ УЧПЙИ ЮЙФБФЕМЕК!

рТЕДЩДХЭЙЕ ЧЩРХУЛЙ НПЗМЙ ВЩФШ ОЕУЛПМШЛП ЪБРХФБООЩНЙ. оБЮБМШОБС ЪБЗТХЪЛБ, Assembler, BIOS. уЕЗПДОС НЩ ОБЛПОЕГ РЕТЕИПДЙН Л ВПМЕЕ ЙОФЕТЕУОПК Й РПОСФОПК ЮБУФЙ - НЩ ОБЮЙОБЕН РЙУБФШ СДТП. й РЙУБФШ НЩ ЕЗП ВХДЕН ОБ СЪЩЛЕ ЧЩУПЛПЗП ХТПЧОС уЙ.

ч ОБЮБМШОЩК ЪБЗТХЪЮЙЛ ПУФБМПУШ ЧОЕУФЙ ЧУЕЗП РБТХ ДПРПМОЕОЙК Й ПО ВХДЕФ РПМОПУФША ЗПФПЧ ЗТХЪЙФШ МАВЩЕ 32-ВЙФОЩЕ СДТБ.

пРТЕДЕМЕОЙЕ ПВЯЈНБ ПРЕТБФЙЧОПК РБНСФЙ

лПОЕЮОП, НПЦОП РПДУЮЙФБФШ ПВЯЈН РБНСФЙ ЧТХЮОХА Ч СДТЕ - РЕТЕВЙТБФШ БДТЕУБ ПФ 0x100000 Й РЩФБФШУС ЪБРЙУБФШ ФХДБ ЪОБЮЕОЙЕ ПФМЙЮОПЕ ПФ ОХМС Й 0xFF. еУМЙ РТЙ ЮФЕОЙЙ НЩ РПМХЮБЕН РПМХЮЕООПЕ ЪОБЮЕОЙЕ, ФП ЧУЈ ИПТПЫП, ЙОБЮЕ РБНСФШ ЛПОЮЙМБУШ - ЪБРПНЙОБЕН БДТЕУ РПУМЕДОЕЗП ХДБЮОПЗП ЮФЕОЙС, ЬФП Й ВХДЕФ ПВЯЈНПН ПРЕТБФЙЧОПК РБНСФЙ. пДОБЛП ФБЛПК УРПУПВ ЙНЕЕФ ДЧБ ОЕДПУФБФЛБ:

1) еЗП УМЕДХЕФ ЙУРПМШЪПЧБФШ ДП ЧЛМАЮЕОЙС УФТБОЙЮОПК БДТЕУБГЙЙ, ЮФПВЩ ЙНЕФШ ДПУФХР ЛП ЧУЕК ЖЙЪЙЮЕУЛПК РБНСФЙ, МЙВП ХУФТБЙЧБФШ ЪБРЙУШ ЮЕТЕЪ "ПЛОП" ЧТЕНЕООПК УФТБОЙГЩ. мЙЫОСС ФТБФБ ЧТЕНЕОЙ, РТЙ ХУМПЧЙЙ, ЮФП ФЕУФЙТПЧБОЙЕ РБНСФЙ BIOS Й ФБЛ ЧЩРПМОСЕФ РТЙ ОБЮБМШОПК ЙОЙГЙБМЙЪБГЙЙ, Б НЩ ДЕМБЕН ДЧПКОХА ТБВПФХ.

2) чУЈ ИПТПЫП РПЛБ РБНСФШ РТЕДУФБЧМСЕФ УПВПК ОЕРТЕТЩЧОЩК ХЮБУФПЛ БДТЕУПЧ, ОП ОБ УПЧТЕНЕООЩИ УЙУФЕНБИ У ВПМШЫЙН ПВЯЈНПН РБНСФЙ ЬФП РТБЧЙМП НПЦЕФ ВЩФШ ОБТХЫЕОП. л ФПНХ ЦЕ BIOS РЙЫЕФ Ч УБНХА ПВЩЮОХА РБНСФШ ФБВМЙГЩ ACPI, ЛПФПТЩЕ РТЙЗПДСФУС ПРЕТБГЙПООПК УЙУФЕНЕ Й ОЕ УФПЙФ ЙИ ЪБФЙТБФШ ДП РТПЮФЕОЙС.

йЪ ЬФПЗП УМЕДХЕФ, ЮФП МХЮЫЕ УРТПУЙФШ РТП ПВЯЈН ПРЕТБФЙЧОПК РБНСФЙ Х BIOS, ВМБЗП ПО РТЕДПУФБЧМСЕФ ЧУЕ ОЕПВИПДЙНЩЕ ЖХОЛГЙЙ.

йУФПТЙЮЕУЛЙ РЕТЧПК ЖХОЛГЙЕК ПРТЕДЕМЕОЙС ПВЯЈНБ ПРЕТБФЙЧОПК РБНСФЙ ВЩМП РТЕТЩЧБОЙЕ 0x12. пОП ОЕ РТЙОЙНБЕФ ОЙЛБЛЙИ ЧИПДОЩИ РБТБНЕФТПЧ, Ч ОБ ЧЩИПДЕ Ч ТЕЗЙУФТЕ AX УПДЕТЦЙФУС ТБЪНЕТ ВБЪПЧПК РБНСФЙ Ч ЛЙМПВБКФБИ. вБЪПЧБС РБНСФШ - ФЕ УБНЩЕ 640 лв ДПУФХРОЩЕ Ч ТЕБМШОПН ТЕЦЙНЕ. уЕКЮБУ ЧЩ ХЦЕ ОЕ УНПЦЕФЕ ОБКФЙ ЛПНРШАФЕТ, ЗДЕ ВЩ ВЩМП НЕОЕЕ 640 лв РБНСФЙ, ОП НБМП МЙ. йУРПМШЪПЧБФШ ЕЈ ОБН УНЩУМБ ОЕФ - ЕУМЙ РТПГЕУУПТ РПДДЕТЦЙЧБЕФ ЪБЭЙЭЈООЩК ТЕЦЙН, ФП ЧТСД МЙ Х ОЕЗП ВХДЕФ НЕОШЫЕ ОЕУЛПМШЛЙИ НЕЗБВБКФ РБНСФЙ.

пВЯЈНЩ РБНСФЙ ТПУМЙ Й 640 лв УФБМП НБМП. фПЗДБ РПСЧЙМБУШ ОПЧБС ЖХОЛГЙС - РТЕТЩЧБОЙЕ 0x15 AH=0x88. пОБ ЧПЪЧТБЭБЕФ Ч AX ТБЪНЕТ ТБУЫЙТЕООПК РБНСФЙ (УЧЩЫЕ 1 нв) Ч ЛЙМПВБКФБИ Ч AX. ьФБ ЖХОЛГЙС ОЕ НПЦЕФ ЧПЪЧТБЭБФШ ЪОБЮЕОЙС ВПМШЫЕ 15 нв (15 + 1 ЙФПЗП 16 нв).

лПЗДБ Й 16 нв УФБМП ОЕДПУФБФПЮОП РПСЧЙМБУШ ОПЧБС ЖХОЛГЙС - РТЕТЩЧБОЙЕ 0x15, AX=0xE801. пОБ ЧПЪЧТБЭБЕФ ТЕЪХМШФБФЩ БЦ Ч 4 ТЕЗЙУФТБИ:

AX - ТБЪНЕТ ТБУЫЙТЕООПК РБНСФЙ ДП 16 нв Ч ЛЙМПВБКФБИ
BX - ТБЪНЕТ ТБУЫЙТЕООПК РБНСФЙ УЧЕТИ 16 нв Л ВМПЛБИ РП 64 лв
CX - ТБЪНЕТ УЛПОЖЙЗХТЙТПЧБООПК ТБУЫЙТЕООПК РБНСФЙ ДП 16 нв Ч ЛЙМПВБКФБИ
DX - ТБЪНЕТ УЛПОЖЙЗХТЙТПЧБООПК ТБУЫЙТЕООПК РБНСФЙ УЧЕТИ 16 нв Ч ВМПЛБИ РП 64 лв

юФП ФБЛПЕ "УЛПОЖЙЗХТЙТПЧБООБС" РБНСФШ РТПЙЪЧПДЙФЕМЙ BIOS УХДС РП ЧУЕНХ ОЕ ДПЗПЧПТЙМЙУШ, РПЬФПНХ ОБДП РТПУФП, ЕУМЙ Ч AX Й BX ОХМЙ, ВТБФШ ЪОБЮЕОЙЕ ЙЪ CX Й DX.

оП Й ЬФПЗП ПЛБЪБМПУШ НБМП. чЕДШ ЧУЕ РЕТЕЮЙУМЕООЩЕ ЧЩЫЕ ЖХОЛГЙЙ ЙНЕАФ ПЗТБОЙЮЕОЙЕ ПВЯЈНБ РБНСФЙ Ч 4 зв, Л ФПНХ ЦЕ ОЕ ХЮЙФЩЧБАФ ФП, ЮФП РБНСФШ НПЦЕФ ВЩФШ ОЕ ОЕРТЕТЩЧОЩН ВМПЛПН. рПЬФПНХ Ч ОПЧЩИ BIOS РПСЧЙМБУШ ЕЭЈ ПДОБ ЖХОЛГЙС - РТЕТЩЧБОЙЕ 0x15, AX=0xE820. пОБ ЧПЪЧТБЭБЕФ ОЕ РТПУФП ЮЙУМП, Б ЛБТФХ РБНСФЙ. чИПДОЩЕ РБТБНЕФТЩ:

EAX=0xE820
EDX=0x534D4150 ("SMAP")
EBX - УНЕЭЕОЙЕ ПФ ОБЮБМБ ЛБТФЩ РБНСФЙ (ДМС ОБЮБМБ 0)
ECX - ТБЪНЕТ ВХЖЕТБ (ЛБЛ РТБЧЙМП 24 ВБКФБ - ТБЪНЕТ ПДОПЗП ЬМЕНЕОФБ)
ES:DI - БДТЕУ ВХЖЕТБ, ЛХДБ ОБДП ЪБРЙУБФШ ПЮЕТЕДОПК ЬМЕНЕОФ

чЩИПДОЩЕ РБТБНЕФТЩ:

EAX=0x534D4150 ("SMAP")
EBX - ОПЧПЕ УНЕЭЕОЙЕ ДМС УМЕДХАЭЕЗП ЧЩЪПЧБ ЖХОЛГЙЙ. еУМЙ 0, ФП ЧУС ЛБТФБ РБНСФЙ РТПЮЙФБОБ
ECX - ЛПМЙЮЕУФЧП ТЕБМШОП ЧПЪЧТБЭЈООЩИ ВБКФ (20 ЙМЙ 24 ВБКФБ)
ч ХЛБЪБООПН ВХЖЕТЕ УПДЕТЦЙФУС ПЮЕТЕДОПК ЬМЕНЕОФ ЛБТФЩ РБНСФЙ.

лБЦДЩК ЬМЕНЕОФ ЛБТФЩ РБНСФЙ ЙНЕЕФ УМЕДХАЭХА УФТХЛФХТХ (ОБРЙЫХ Ч УЙОФБЛУЙУЕ уЙ, РПФПНХ ЮФП ТБЪВПТ ДБООЩИ НЩ ВХДЕН ДЕМБФШ ХЦЕ Ч СДТЕ):

Struct { unsigned long long base; //вБЪПЧЩК ЖЙЪЙЮЕУЛЙК БДТЕУ ТЕЗЙПОБ unsigned long long length; //тБЪНЕТ ТЕЗЙПОБ Ч ВБКФБИ unsigned long type; // фЙР ТЕЗЙПОБ unsigned long acpi_attrs; //тБУЫЙТЕООЩЕ БФТЙВХФЩ ACPI };

рПУМЕДОЙК ЬМЕНЕОФ УФТХЛФХТЩ ОЕ ПВСЪБФЕМЕО. еЭЈ Ч ПДОПН ЙУФПЮОЙЛЕ ЧЙДЕМ, ЮФП РЕТЕД ЪБРТПУПН ЬМЕНЕОФБ УФПЙФ РПНЕУФЙФШ ФХДБ ЕДЙОЙЮЛХ. лПОЕЮОП, УЕКЮБУ НЩ ОЕ РПДДЕТЦЙЧБЕН ACPI, ОП МХЮЫЕ ЪБТБОЕЕ РПЪБВПФЙФУС П ФПН, ЮФПВЩ РПМХЮЙФШ ЛБЛ НПЦОП ВПМШЫЕ ДБООЩИ. ч ПФМЙЮЙЙ ПФ РБТБНЕФТПЧ РБНСФЙ, ЧУЈ ПУФБМШОПЕ НПЦОП МЕЗЛП ХЪОБФШ Й ЙЪ ЪБЭЙЭЈООПЗП ТЕЦЙНБ ОБРТСНХА, ВЕЪ BIOS.

тЕЗЙПОЩ РБНСФЙ, ПРЙУЩЧБЕНЩЕ ЛБТФПК, НПЗХФ ВЩФШ ОЕУЛПМШЛЙИ ФЙРПЧ:

1 - пВЩЮОБС РБНСФШ. нПЦЕФ ВЩФШ УЧПВПДОП ЙУРПМШЪПЧБОБ пу ДМС УЧПЙИ ГЕМЕК. рПЛБ НЩ ФПМШЛП Л ОЕК Й ВХДЕН ПВТБЭБФШУС, Б ЧУЈ ПУФБМШОПЕ РТПРХУЛБФШ.
2 - ъБТЕЪЕТЧЙТПЧБОП (ОБРТЙНЕТ, ЛПД BIOS). ьФБ РБНСФШ НПЦЕФ ВЩФШ ЛБЛ ЖЙЪЙЮЕУЛЙ ОЕДПУФХРОБ ДМС ЪБРЙУЙ, ФБЛ Й РТПУФП ЪБРЙУШ ФХДБ ОЕЦЕМБФЕМШОБ. фБЛХА РБНСФШ МХЮЫЕ ОЕ ФТПЗБФШ.
3 - дПУФХРОП РПУМЕ РТПЮФЕОЙС ФБВМЙГ ACPI. чЕТПСФОП, ЙНЕООП Ч ЬФЙИ ВМПЛБИ ЬФЙ ФБВМЙГЩ Й ИТБОСФУС. рПЛБ ДТБКЧЕТ ACPI ОЕ РТПЮЙФБЕФ ФБВМЙГЩ, ЬФХ РБНСФШ МХЮЫЕ ОЕ ФТПЗБФШ. рПФПН НПЦОП ЙУРПМШЪПЧБФШ ФБЛ ЦЕ, ЛБЛ Й РБНСФШ ФЙРБ 1.
4 - ьФХ РБНСФШ УМЕДХЕФ УПИТБОСФШ НЕЦДХ NVS УЕУУЙСНЙ. фБЛХА РБНСФШ НЩ ФТПЗБФШ ОЕ ВХДЕН, РПЛБ ОЕ ХЪОБЕН, ЮФП ФБЛПЕ NVS УЕУУЙЙ:-)

оЕ ЧУЕ BIOS НПЗХФ РПДДЕТЦЙЧБФШ ЬФХ ЖХОЛГЙА. еУМЙ ЛБЛБС-ФП ЖХОЛГЙС ОЕ РПДДЕТЦЙЧБЕФУС, ФП РТЙ ЧЩИПДЕ ЙЪ ОЕЈ ХУФБОПЧМЕО ЖМБЗ РЕТЕРПМОЕОЙС Й УМЕДХЕФ ПВТБЭБФШУС Л ВПМЕЕ УФБТПК. нЩ ВХДЕН ЙУРПМШЪПЧБФШ ЖПТНБФ ЛБТФЩ РБНСФЙ ЖХОЛГЙЙ 0xE820. еУМЙ УБНХ ЬФХ ЖХОЛГЙА ЧЩЪЧБФШ ОЕ РПМХЮЙМПУШ - РПМХЮБФШ ПВЯЈН РБНСФЙ ПВЩЮОЩНЙ УТЕДУФЧБНЙ Й УПЪДБЧБФШ УЧПА УПВУФЧЕООХА ЛБТФХ РБНСФЙ ЙЪ ПДОПЗП ЬМЕНЕОФБ. рПУЛПМШЛХ ПРТЕДЕМЕОЙЕ ПВЯЈНБ РБНСФЙ ЪБДБЮБ ОХЦОБС Й ДМС ЪБРХУЛБ 32-ВЙФОПЗП Й ДМС ЪБРХУЛБ 64-ВЙФОПЗП СДТБ, МХЮЫЕ ПЖПТНЙФШ ЕЈ Ч ЧЙДЕ РПДРТПЗТБННЩ. лБТФХ РБНСФЙ ТБЪНЕУФЙН РП БДТЕУХ 0x7000. оЕ ДХНБА, ЮФП ПОБ НПЦЕФ ВЩФШ ВПМШЫЕ РБТЩ ЛЙМПВБКФ. рПУМЕДОЙК ЬМЕНЕОФ ЧТХЮОХА УДЕМБЕН ФЙРБ 0 - ФБЛПЗП ФЙРБ ОЕ ЧПЪЧТБЭБЕФ BIOS Й ЬФП Й ВХДЕФ РТЙЪОБЛПН ЛПОГБ.

; рПМХЮЕОЙЕ ЛБТФЩ РБНСФЙ get_memory_map: mov di, 0x7000 xor ebx, ebx @: mov eax, 0xE820 mov edx, 0x534D4150 mov ecx, 24 mov dword, 1 int 0x15 jc @f add di, 24 test ebx, ebx jnz @b @: cmp di, 0x7000 ja .ok mov dword, 0x100000 mov dword, 0 mov dword, 0 mov dword, 1 mov dword, 0 mov ax, 0xE801 int 0x15 jnc @f mov ah, 0x88 int 0x15 jc .ok mov cx, ax xor dx, dx @: test cx, cx jz @f mov ax, cx mov bx, dx @: movzx eax, ax movzx ebx, bx mov ecx, 1024 mul ecx push eax mov eax, ebx mov ecx, 65536 mul ecx pop edx add eax, edx mov , eax add di, 24 jmp .ok .ok: xor ax, ax mov cx, 24 / 2 rep stosw ret

оХ ЧПФ Й ЗПФПЧ ОБЫ ОБЮБМШОЩК ЪБЗТХЪЮЙЛ ДМС 32-ВЙФОЩИ СДЕТ. ч ЪБЛМАЮЕОЙЕ РТЙЧПЦХ ЕЗП РПМОЩК ЛПД Й НЩ РЕТЕКДЈН Л СДТХ.

; оБЮБМШОЩК ЪБЗТХЪЮЙЛ СДТБ ДМС БТИЙФЕЛФХТЩ x86 format Binary as "bin" org 0x7C00 jmp boot ; ъБЗПМПЧПЛ ListFS align 4 fs_magic dd ? fs_version dd ? fs_flags dd ? fs_base dq ? fs_size dq ? fs_map_base dq ? fs_map_size dq ? fs_first_file dq ? fs_uid dq ? fs_block_size dd ? ; ъБЗПМПЧПЛ ЖБКМБ virtual at 0x800 f_info: f_name rb 256 f_next dq ? f_prev dq ? f_parent dq ? f_flags dq ? f_data dq ? f_size dq ? f_ctime dq ? f_mtime dq ? f_atime dq ? end virtual ; дБООЩЕ ОБЮБМШОПЗП ЪБЗТХЪЮЙЛБ label sector_per_track word at $$ label head_count byte at $$ + 2 label disk_id byte at $$ + 3 reboot_msg db "Press any key...",13,10,0 boot_file_name db "boot.bin",0 ; чЩЧПД УФТПЛЙ DS:SI ОБ ЬЛТБО write_str: push si mov ah, 0x0E @: lodsb test al, al jz @f int 0x10 jmp @b @: pop si ret ; лТЙФЙЮЕУЛБС ПЫЙВЛБ error: pop si call write_str ; рЕТЕЪБЗТХЪЛБ reboot: mov si, reboot_msg call write_str xor ah, ah int 0x16 jmp 0xFFFF:0 ; ъБЗТХЪЛБ УЕЛФПТБ DX:AX Ч ВХЖЕТ ES:DI load_sector: push dx add ax, word adc dx, word cmp byte, 0xFF je .use_EDD push bx cx si div mov cl, dl inc cl div mov dh, ah mov ch, al mov dl, mov bx, di mov al, 1 mov si, 3 @: mov ah, 2 int 0x13 jnc @f xor ah, ah int 0x13 dec si jnz @b .error: call error db "DISK ERROR",13,10,0 @: pop si cx bx dx ret .use_EDD: push si mov byte, 0x10 mov byte, 0 mov word, 1 mov , di push es pop word mov , ax mov , dx mov word, 0 mov word, 0 mov ah, 0x42 mov dl, mov si, 0x600 int 0x13 jc .error pop si dx ret ; рПЙУЛ ЖБКМБ У ЙНЕОЕН DS:SI Ч ЛБФБМПЗЕ DX:AX find_file: push cx dx di .find: cmp ax, -1 jne @f cmp dx, -1 jne @f .not_found: call error db "NOT FOUND",13,10,0 @: mov di, f_info call load_sector push di mov cx, 0xFFFF xor al, al repne scasb neg cx dec cx pop di push si repe cmpsb pop si je .found mov ax, word mov dx, word jmp .find .found: pop di dx cx ret ; ъБЗТХЪЛБ ФЕЛХЭЕЗП ЖБКМБ Ч РБНСФШ РП БДТЕУХ BX:0. лПМЙЮЕУФЧП ЪБЗТХЦЕООЩИ УЕЛФПТПЧ ЧПЪЧТБЭБЕФУС Ч AX load_file_data: push bx cx dx si di mov ax, word mov dx, word .load_list: cmp ax, -1 jne @f cmp dx, -1 jne @f .file_end: pop di si dx cx mov ax, bx pop bx sub ax, bx shr ax, 9 - 4 ret @: mov di, 0x8000 / 16 call load_sector mov si, di mov cx, 512 / 8 - 1 .load_sector: lodsw mov dx, add si, 6 cmp ax, -1 jne @f cmp dx, -1 je .file_end @: push es mov es, bx xor di, di call load_sector add bx, 0x200 / 16 pop es loop .load_sector lodsw mov dx, jmp .load_list ; фПЮЛБ ЧИПДБ Ч ОБЮБМШОЩК ЪБЗТХЪЮЙЛ boot: ; оБУФТПЙН УЕЗНЕОФОЩЕ ТЕЗЙУФТЩ jmp 0:@f @: mov ax, cs mov ds, ax mov es, ax ; оБУФТПЙН УФЕЛ mov ss, ax mov sp, $$ ; тБЪТЕЫЙН РТЕТЩЧБОЙС sti ; ъБРПНОЙН ОПНЕТ ЪБЗТХЪПЮОПЗП ДЙУЛБ mov , dl ; пРТЕДЕМЙН РБТБНЕФТЩ ЪБЗТХЪПЮОПЗП ДЙУЛБ mov ah, 0x41 mov bx, 0x55AA int 0x13 jc @f mov byte, 0xFF jmp .disk_detected @: mov ah, 0x08 xor di, di push es int 0x13 pop es jc load_sector.error inc dh mov , dh and cx, 111111b mov , cx .disk_detected: ; ъБЗТХЪЙН РТПДПМЦЕОЙЕ ОБЮБМШОПЗП ЪБЗТХЪЮЙЛБ mov si, boot_file_name mov ax, word mov dx, word call find_file mov bx, 0x7E00 / 16 call load_file_data ; рЕТЕИПДЙН ОБ РТПДПМЦЕОЙЕ jmp boot2 ; рХУФПЕ РТПУФТБОУФЧП Й УЙЗОБФХТБ rb 510 - ($ - $$) db 0x55,0xAA ; дПРПМОЙФЕМШОЩЕ ДБООЩЕ ЪБЗТХЪЮЙЛБ load_msg_preffix db "Loading "",0 load_msg_suffix db ""...",0 ok_msg db "OK",13,10,0 config_file_name db "boot.cfg",0 start16_msg db "Starting 16 bit kernel...",13,10,0 start32_msg db "Starting 32 bit kernel...",13,10,0 label module_list at 0x6000 label memory_map at 0x7000 ; тБЪВЙЕОЙЕ УФТПЛЙ DS:SI РП УЙНЧПМХ УМЕЫБ split_file_name: push si @: lodsb cmp al, "/" je @f test al, al jz @f jmp @b @: mov byte, 0 mov ax, si pop si ret ; ъБЗТХЪЛБ ЖБКМБ У ЙНЕОЕН DS:SI Ч ВХЖЕТ BX:0. тБЪНЕТ ЖБКМБ Ч УЕЛФПТБИ ЧПЪЧТБЭБЕФУС Ч AX load_file: push si mov si, load_msg_preffix call write_str pop si call write_str push si mov si, load_msg_suffix call write_str pop si push si bp mov dx, word mov ax, word @: push ax call split_file_name mov bp, ax pop ax call find_file test byte, 1 jz @f mov si, bp mov dx, word mov ax, word jmp @b @: call load_file_data mov si, ok_msg call write_str pop bp si ret ; рПМХЮЕОЙЕ ЛБТФЩ РБНСФЙ get_memory_map: mov di, memory_map xor ebx, ebx @: mov eax, 0xE820 mov edx, 0x534D4150 mov ecx, 24 mov dword, 1 int 0x15 jc @f add di, 24 test ebx, ebx jnz @b @: cmp di, 0x7000 ja .ok mov dword, 0x100000 mov dword, 0 mov dword, 0 mov dword, 1 mov dword, 0 mov ax, 0xE801 int 0x15 jnc @f mov ah, 0x88 int 0x15 jc .ok mov cx, ax xor dx, dx @: test cx, cx jz @f mov ax, cx mov bx, dx @: movzx eax, ax movzx ebx, bx mov ecx, 1024 mul ecx push eax mov eax, ebx mov ecx, 65536 mul ecx pop edx add eax, edx mov , eax add di, 24 jmp .ok .ok: xor ax, ax mov cx, 24 / 2 rep stosw ret ; рТПДПМЦЕОЙЕ ОБЮБМШОПЗП ЪБЗТХЪЮЙЛБ boot2: ; ъБЗТХЪЙН ЛПОЖЙЗХТБГЙПООЩК ЖБКМ ЪБЗТХЪЮЙЛБ mov si, config_file_name mov bx, 0x1000 / 16 call load_file ; чЩРПМОЙН ЪБЗТХЪПЮОЩК УЛТЙРФ mov bx, 0x9000 / 16 mov bp, module_list mov dx, 0x1000 .parse_line: mov si, dx .parse_char: lodsb test al, al jz .config_end cmp al, 10 je .run_command cmp al, 13 je .run_command jmp .parse_char .run_command: mov byte, 0 xchg dx, si cmp byte, 0 je .parse_line ; рХУФБС УФТПЛБ cmp byte, "#" je .parse_line ; лПННЕОФБТЙК cmp byte, "L" je .load_file ; ъБЗТХЪЛБ ЖБКМБ cmp byte, "S" je .start ; ъБРХУЛ СДТБ; оЕЙЪЧЕУФОБС ЛПНБОДБ mov al, mov [.cmd], al call error db "Unknown boot script command "" .cmd db ? db ""!",13,10,0 .config_end: ; рТЙ РТБЧЙМШОПН ЛПОЖЙЗХТБГЙПООПН ЖБКМЕ НЩ ОЕ ДПМЦОЩ УАДБ РПРБУФШ; ъБЧЕТЫЕОЙЕ jmp reboot ; ъБЗТХЪЛБ ЖБКМБ.load_file: push dx inc si call load_file push ax mov cx, 512 mul cx mov word, ax mov word, dx mov word, 0 mov word, 0 mov ax, bx mov cx, 16 mul cx mov word, ax mov word, dx mov word, 0 mov word, 0 pop ax shr ax, 9 - 4 add bx, ax add bp, 16 pop dx jmp .parse_line ; ъБРХУЛ СДТБ.start: ; рТПЧЕТЙН, ЮФП ЪБЗТХЦЕО ИПФС ВЩ ПДЙО ЖБКМ cmp bx, 0x9000 / 16 ja @f call error db "NO KERNEL LOADED",13,10,0 @: ; ъБРПМОСЕН РПУМЕДОЙК ЬМЕНЕОФ УРЙУЛБ ЖБКМПЧ xor ax, ax mov cx, 16 mov di, bp rep stosw ; рЕТЕИПДЙН Л РТПГЕДХТЕ ЙОЙГЙБМЙЪБГЙЙ СДТБ ДМС ОХЦОПК ТБЪТСДОПУФЙ inc si cmp word, "16" je .start16 cmp word, "32" je .start32 ;cmp word, "64" ;je .start64 ; оЕЙЪЧЕУФОБС ТСЪТСДОПУФШ СДТБ call error db "Invalid start command argument",13,10,0 ; ъБРХУЛ 16-ТБЪТСДОПЗП СДТБ.start16: mov si, start16_msg mov bx, module_list mov dl, jmp 0x9000 ; ъБРХУЛ 32-ТБЪТСДОПЗП СДТБ.start32: ; чЩЧПДЙН ХЧЕДПНМЕОЙЕ П ЪБРХУЛЕ 32-ВЙФОПЗП СДТБ mov si, start32_msg call write_str ; рТПЧЕТЙН, ЮФП РТПГЕУУПТ ОЕ ИХЦЕ i386 mov ax, 0x7202 push ax popf pushf pop bx cmp ax, bx je @f call error db "Required i386 or better",13,10,0 @: ; рПМХЮЙН ЛБТФХ РБНСФЙ call get_memory_map ; пЮЙУФЙН ФБВМЙГЩ УФТБОЙГ xor ax, ax mov cx, 3 * 4096 / 2 mov di, 0x1000 rep stosw ; ъБРПМОЙН ЛБФБМПЗ УФТБОЙГ mov word, 0x2000 + 111b mov word, 0x3000 + 111b ; ъБРПМОЙН РЕТЧХА ФБВМЙГХ УФТБОЙГ mov eax, 11b mov cx, 0x100000 / 4096 mov di, 0x2000 @: stosd add eax, 0x1000 loop @b ; ъБРПМОЙН РПУМЕДОАА ФБВМЙГХ УФТБОЙГ mov di, 0x3000 mov eax, dword or eax, 11b mov ecx, dword shr ecx, 12 @: stosd add eax, 0x1000 loop @b mov word, 0x4000 + 11b ; Kernel stack mov word, 0x3000 + 11b ; Kernel page table ; ъБЗТХЪЙН ЪОБЮЕОЙЕ Ч CR3 mov eax, 0x1000 mov cr3, eax ; ъБЗТХЪЙН ЪОБЮЕОЙЕ Ч GDTR lgdt ; ъБРТЕФЙН РТЕТЩЧБОЙС cli ; рЕТЕКДЈН Ч ЪБЭЙЭЈООЩК ТЕЦЙН mov eax, cr0 or eax, 0x80000001 mov cr0, eax ; рЕТЕКДЈН ОБ 32-ВЙФОЩК ЛПД jmp 8:start32 ; фБВМЙГБ ДЕУЛТЙРФПТПЧ УЕЗНЕОФПЧ ДМС 32-ВЙФОПЗП СДТБ align 16 gdt32: dq 0 ; NULL - 0 dq 0x00CF9A000000FFFF ; CODE - 8 dq 0x00CF92000000FFFF ; DATA - 16 gdtr32: dw $ - gdt32 - 1 dd gdt32 ; 32-ВЙФОЩК ЛПД use32 start32: ; оБУФТПЙН УЕЗНЕОФОЩЕ ТЕЗЙУФТЩ Й УФЕЛ mov eax, 16 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0xFFFFDFFC ; рПНЕУФЙН Ч DL ОПНЕТ ЪБЗТХЪПЮОПЗП ДЙУЛБ mov dl, ; рПНЕУФЙН Ч EBX БДТЕУ УРЙУЛБ ЪБЗТХЦЕООЩИ ЖБКМПЧ mov ebx, module_list ; рПНЕУФЙН Ч ESI БДТЕУ ЛБТФЩ РБНСФЙ mov esi, memory_map ; рЕТЕИПДЙН ОБ СДТП jmp 0xFFC00000

рЕТЧПЕ СДТП

сДТП РПЛБ Х ОБУ ВХДЕФ УПУФПСФШ ЙЪ ДЧХИ ЖБКМПЧ - startup.asm Й main.c. startup.asm ОХЦЕО ДМС ФПЗП, ЮФПВЩ ВЩФШ ХЧЕТЕООЩНЙ, ЮФП ХРТБЧМЕОЙЕ РПРБДЈФ ОБ ЖХОЛГЙА kernel_main. чЕДШ ПОБ НПЦЕФ ВЩФШ ОЕ Ч ОБЮБМЕ ЖБКМБ, Б УПДЕТЦЙНПЕ startup.o НЩ РПМОПУФША ЛПОФТПМЙТХЕН Й ЕУМЙ ХЛБЦЕН ЕЗП РЕТЧЩН МЙОЛЕТХ, ФП ВХДЕН ХРТБЧМСФШ Й РЕТЧЩНЙ ВБКФБНЙ ДЧПЙЮОПЗП ЖБКМБ.

Format ELF public _start extrn kernel_main section ".text" executable _start: movzx edx, dl push edx push esi push ebx lgdt call kernel_main @: ;cli ;hlt jmp @b section ".data" writable gdt: dq 0 dq 0x00CF9A000000FFFF dq 0x00CF92000000FFFF gdtr: dw $ - gdt dd gdt

оХ ЧПФ Й РПУМЕДОЙК ОБЫ ЛПД ОБ ЮЙУФПН Assembler:-). пО ЧЩРПМОСЕФ РТПУФЕКЫХА ЪБДБЮХ - ХМПЦЙФШ Ч УФЕЛ ФТЙ БТЗХНЕОФБ ДМС ЖХОЛГЙЙ kernel_main Й РЕТЕДБФШ ОБ ОЕЈ ХРТБЧМЕОЙЕ. рПУМЕ ЧПЪЧТБФБ ЙЪ ОЕЈ СДТП ХИПДЙФ Ч ВЕУЛПОЕЮОЩК ГЙЛМ. рП УПЗМБЫЕОЙА ЧЩЪПЧБ ЖХОЛГЙК уЙ РБТБНЕФТЩ УМЕДХЕФ РЙИБФШ Ч УФЕЛ Ч ПВТБЪПН РПТСДЛЕ. фБЛЦЕ ЬФПФ ЛПД ЙОЙГЙБМЙЪБГЙЙ ЪБЗТХЦБЕФ ОПЧПЕ ЪОБЮЕОЙЕ Ч GDTR - ФЕРЕТШ ФБВМЙГБ ДЕУЛТЙРФПТПЧ УЕЗНЕОФПЧ ОБИПДЙФУС Ч РТПУФТБОУФЧЕ СДТБ Й ДБЦЕ ЕУМЙ НЩ ПФНПОФЙТХЕН РЕТЧЩК НЕЗБВБКФ ОЕ РТПЙЪПКДЈФ ОЙЛБЛЙИ ПЫЙВПЛ.

б ФЕРЕТШ УБНПЕ ЧЛХУОПЕ - РТПУФЕКЫЕЕ СДТП ОБ СЪЩЛЕ ЧЩУПЛПЗП ХТПЧОС:

Typedef struct { unsigned long long base; unsigned long long size; } BootModuleInfo; void kernel_main(char boot_disk_id, void *memory_map, BootModuleInfo *boot_module_list) { char *screen_buffer = (void*)0xB8000; char *msg = "Hello world!"; unsigned int i = 24 * 80; while (*msg) { screen_buffer = *msg; msg++; i++; } }

ьФП СДТП ОЕ ДЕМБЕФ ОЙЮЕЗП ПУПВЕООПЗП - РТПУФП ЧЩЧПДЙФ УФТПЛХ "Hello world!" ОБ РПУМЕДОАА УФТПЮЛХ ФЕЛУФПЧПЗП ЬЛТБОБ. уФТХЛФХТБ ПРЙУБООБС Ч ОБЮБМЕ ВХДЕФ ОХЦОБ ДМС ДПУФХРБ Л УРЙУЛХ ЪБЗТХЦЕООЩИ НПДХМЕК.

чБЦОП РПНОЙФШ, ЮФП ОЙЛБЛПК УФБОДБТФОПК ВЙВМЙПФЕЛЙ Х ОБУ ОЕФ - ОБН ДПУФХРОЩ ФПМШЛП ФЕ ЖХОЛГЙЙ, ЛПФПТЩЕ НЩ УДЕМБЕН УБНЙ. чУЕ printf, strcpy, memcpy Й Ф. Р. РТЙДЈФУС ТЕБМЙЪПЧЩЧБФШ УБНПУФПСФЕМШОП, ОЕ РЩФБКФЕУШ ПВТБФЙФШУС Л ОЙН. ч УМЕДХАЭЕН ЧЩРХУЛЕ НЩ ЪБКНЈНУС УПЪДБОЙЕН ОБЫЕЗП УПВУФЧЕООПЗП ЦХФЛП ХТЕЪБООПЗП БОБМПЗБ libc, ЮФПВЩ РТПЗТБННЙТПЧБФШ ВЩМП ХДПВОЕЕ. фХФ ОБЮЙОБЕФУС УБНБС ЙОФЕТЕУОБС ЮБУФШ, Б РТЙОСФЩЕ ТЕЫЕОЙС ЧП НОПЗПН РПЧМЙСАФ ОБ ЧУА УФТХЛФХТХ УЙУФЕНЩ.

уВПТЛБ СДТБ

йУРПМОСЕНЩЕ ЖБКМЩ УПВЙТБАФУС Ч ДЧБ ЬФБРБ - ЛПНРЙМСГЙС, Б РПФПН МЙОЛПЧЛБ. оБ РЕТЧПН ЬФБРЕ ЛПНРЙМСФПТ РТЕПВТБЪХЕФ ЙУИПДОЩК ЛПД Ч ЛПНБОДЩ РТПГЕУУПТБ Й УПИТБОСЕФ ЧУЈ ЬФП Ч ПВЯЕЛФОЩК ЖБКМ. лБЦДЩК НПДХМШ УЙУФЕНЩ УПИТБОСЕФУС Ч ПФДЕМШОПН ЖБКМЕ. ч ЬФПН ЖБКМЕ ФБЛ ЦЕ УПДЕТЦЙФУС ЙОЖПТНБГЙС П ЖХОЛГЙСИ, ПРЙУБООЩИ Ч НПДХМЙ, РПЬФПНХ ЙЪ ПДОПЗП ЖБКМБ НПЦОП УЧПВПДОП ЧЩЪЩЧБФШ ЖХОЛГЙА ЙЪ ДТХЗПЗП. чЕУШ ЛПД Ч ПВЯЕЛФОЩИ ЖБКМБИ ОЕ РТЙЧСЪБО Л ЛПОЛТЕФОЩН БДТЕУБН. оБ ЧФПТПН ЬФБРЕ МЙОЛЕТ УПВЙТБЕФ ЧУЕ ПВЯЕЛФОЩЕ ЖБКМЩ Ч ПДЙО ВЙОБТОЩК. рТЙ ЬФПН ЛПД РТЙЧСЪЩЧБЕФУС Л ЛПОЛТЕФОЩН БДТЕУБН (ЕУМЙ, ЛПОЕЮОП, НЩ ОЕ УПВЙТБЕН ДЙОБНЙЮЕУЛЙ ЪБЗТХЦБЕНХА ВЙВМЙПФЕЛХ), ЧНЕУФП УУЩМПЛ ОБ ЖХОЛГЙЙ РПДУФБЧМСАФУС ОХЦОЩЕ БДТЕУБ. оБН ОХЦОП РПМХЮЙФШ ОБ ЧЩИПДЕ ПУПВЩК ДЧПЙЮОЩК ЖБКМ. ьФП РТПУФП ЛПД Й ДБООЩЕ, ВЕЪ ЛБЛЙИ-МЙВП ЪБЗПМПЧЛПЧ (ФП ЕУФШ ЬФП ОЕ PE Й ОЕ ELF). ч ЛБЮЕУФЧЕ ВБЪПЧПЗП БДТЕУБ ЙУРПМШЪХЕФУС БДТЕУ 0xFFC00000. дМС ХРТПЭЕОЙС ЬФПЗП НЩ ПРЙЫЕН ЧУЈ, ЮФП ОБН ОХЦОП Ч УРЕГЙБМШОПН ЖПТНБФЕ УЛТЙРФБ ld:

OUTPUT_FORMAT("binary") ENTRY(_start) SECTIONS { .text 0xFFC00000: { *(.text) *(.code) *(.rodata*) } .data ALIGN(0x1000) : { *(.data) } .bss ALIGN(0x1000) : { *(.bss) } .empty ALIGN(0x1000) - 1: { BYTE(0) } }

ьФПФ УЛТЙРФ ЗПЧПТЙФ, ЮФП ОБЫ ЖБКМ ВХДЕФ МЕЦБФШ Ч РБНСФЙ ОЕРТЕТЩЧОЩН ВМПЛПН ОБЮЙОБС У БДТЕУБ 0xFFC00000. ч УБНПН ОБЮБМЕ ВХДЕФ ЙДФЙ УЕЛГЙС ЛПДБ, РПФПН УЕЛГЙС read-only ДБООЩИ, ЪБФЕН ПВЩЮОЩИ ДБООЩИ, РПФПН ОЕЙОЙГЙБМЙЪЙТПЧБООЩИ. чУЕ УЕЛГЙЙ ЧЩТПЧОЕОЩ ОБ ТБЪНЕТ УФТБОЙГЩ 4 лв (ЧДТХЗ НЩ РПФПН ЪБИПФЙН ЪБЭЙФЙФШ ОБ ХТПЧОЕ ФБВМЙГЩ УФТБОЙГ ЛПД ПФ ЪБРЙУЙ). рПУМЕДОЕЕ ПРЙУБОЙЕ УЕЛГЙЙ.empty ОЕПВИПДЙНП ДМС ФПЗП, ЮФПВЩ ДБЦЕ ОЕЙОЙГЙБЙМЙЪПТПЧБООЩЕ РЕТЕНЕООЩЕ ЪБОЙНБМЙ НЕУФП Ч ЖБКМЕ (ФБН ВХДХФ ОХМЙ). чЕДШ ОБЮБМШОЩК ЪБЗТХЪЮЙЛ ЧЩДЕМСЕФ РБНСФШ ДМС СДТБ ТХЛПЧПДУФЧХСУШ ТБЪНЕТПН ЖБКМБ.

уПВТБФШ ЧУЈ СДТП НПЦОП УМЕДХАЭЙНЙ ЛПНБОДБНЙ:

Fasm startup.asm startup.o gcc -c -m32 -ffreestanding -o main.o main.c ld --oformat=binary -melf_i386 -T script.ld -o kernel.bin startup.o main.o

рБТБНЕФТ GCC -ffreestanding ХЛБЪЩЧБЕФ ЕНХ ПФЛМАЮЙФШ ЧУЕ УФБОДБТФОЩЕ ВЙВМЙПФЕЛЙ. чЕДШ ПОЙ РТЙЧСЪБОЩ Л ЛПОЛТЕФОПК ПРЕТБГЙПООПК УЙУФЕНЕ, Б НЩ РЙЫЕН ОПЧХА.

уВПТЛБ ПВТБЪБ ДЙУЛБ

пВПКДХУШ ВЕЪ МЙЫОЙИ ЛПННЕОФБТЙЕЧ Й РТПУФП РТЙЧЕДХ МЙОХЛУПЧЩК УЛТЙРФ УВПТЛЙ ПВТБЪБ:

Dd if=bin/boot.bios.bin of=bin/boot_sector.bin bs=512 count=1 dd if=bin/boot.bios.bin of=disk/boot.bin bs=1 skip=512 cp bin/kernel.bin disk/kernel.bin bin/make_listfs of=disk.img bs=512 size=2880 boot=bin/boot_sector.bin src=./disk

пО РТЕДРПМБЗБЕФ, ЮФП ЧУЕ УЛПНРЙМЙТПЧБООЩЕ ЖБКМЩ МЕЦБФ Ч bin Ч ФЕЛХЭЕН ЛБФБМПЗЕ, Б ЕЭЈ ЙНЕЕФУС ЛБФБМПЗ disk, Ч ЛПФПТПН МЕЦЙФ boot.cfg УМЕДХАЭЕЗП УПДЕТЦБОЙС:

# Loading kernel Lkernel.bin # Boot 32 bit kernel S32

еУМЙ ЧЩ ЧУЈ УДЕМБМЙ РТБЧЙМШОП, РПМХЮЕООЩК ПВТБЪ НПЦОП ЪБРХУФЙФШ Ч ЬНХМСФПТЕ ЙМЙ ДБЦЕ ОБ ТЕБМШОПН ЦЕМЕЪЕ Й ЧЩ РПМХЮЙФЕ РПДПВОХА ЛБТФЙОХ:

ъБЗТХЪЮЙЛ УЮЙФЩЧБЕФ ЛПОЖЙЗХТБГЙПООЩК ЖБКМ, ЪБЗТХЦБЕФ СДТП, РЕТЕИПДЙФ Ч ЪБЭЙЭЈООЩК ТЕЦЙН Й РЕТЕДБЈФ ЕНХ ХРТБЧМЕОЙЕ. рПМХЮЙЧ ЕЗП, ОБЫЕ СДТП ЧЩЧПДЙФ РПУМЕДОАА УФТПЛХ ОБ ЬЛТБО. ьФП МЙЫШ ОБЮБМП ДПМЗПЗП РХФЙ, НЩ РЕТЕИПДЙН Л УБНПК ЙОФЕТЕУОПК ЮБУФЙ ТБЪТБВПФЛЙ. фЕРЕТШ ЧЩРХУЛЙ ВХДХФ ЗПТБЪДП ВПМЕЕ РТПУФЩН ДМС ЧПУРТЙСФЙС, ВМБЗПДБТС ЙУРПМШЪПЧБОЙА СЪЩЛБ ЧЩУПЛПЗП ХТПЧОС, ЛПФПТЩК ЛБЛ С ОБДЕАУШ ЧУЕ Й ФБЛ ЪОБАФ. еУМЙ ЧЩ ОЕ ИПФЙФЕ ТБЪВЙТБФШУС У Assembler, НПЦЕФЕ РТПУФП ЧЪСФШ НПК ЗПФПЧЩК ЪБЗТХЪЮЙЛ Й startup.asm Й ЙЪНЕОСФШ ХЦЕ ФПМШЛП УПДЕТЦЙНПЕ main.c, РПУЛПМШЛХ ЧЕУШ ЛПД ДП ЬФПЗП ОЕ ДЙЛФХЕФ ЦЈУФЛП ЛБЛЙЕ-МЙВП РБТБНЕФТЩ СДТБ (ЛТПНЕ жу У ЛПФПТПК НЩ ЪБЗТХЦБЕНУС) Й РПЪЧПМСЕФ РПУФТПЙФШ ОБ УЧПЕК ВБЪЕ ЮФП ХЗПДОП.

бЧФПНБФЙЪБГЙС УВПТЛЙ ЙМЙ Makefile

чЩ НПЗМЙ ЪБНЕФЙФШ, ЮФП ЧТХЮОХА ОБВЙЧБФШ УФПМШЛП ЛПНБОД ДПУФБФПЮОП ХФПНЙФЕМШОП. л ФПНХ ЦЕ ОЕ ЧУЕЗДБ ЕУФШ ОЕПВИПДЙНПУФШ РЕТЕЛПНРЙМЙТПЧБФШ ЧУЕ ЖБКМЩ. оБРТЙНЕТ, ЕУМЙ startup.asm ОЕ ВЩМ ЙЪНЕОЈО, НПЦОП ОЕ ЧЩЪЩЧБФШ fasm. уРЕГЙБМШОП ДМС ХРТПЭЕОЙС ЛПНРЙМСГЙЙ РТЙМПЦЕОЙК ВЩМБ РТЙДХНБОБ ХФЙМЙФБ make, ЛПФПТБС ЧИПДЙФ Ч УФБОДБТФОХА РПУФБЧЛХ GCC Й MinGW.

мАВПК Makefile УФПЙФ ЙЪ ОБВПТБ РТБЧЙМ У ФБЛПК УФТХЛФХТПК:

ЙНСгЕМЙйМЙжБКМБ: йНСрЕТЧПЗПйУИПДОПЗПжБКМБ йНСчФПТПЗПйУИПДОПЗПжБКМБ... лПНБОДЩлПНРЙМСГЙЙ

рЕТЧПЕ РТБЧЙМП, ЛПФПТПЕ ДПМЦОП ВЩФШ Ч МАВПН Makefile - ГЕМШ all. make УНПФТЙФ ОБ ЪБЧЙУЙНПУФЙ ГЕМЙ all Й ЛПНРЙМЙТХЕФ ЙИ, Б ЪБФЕН ЧЩРПМОСЕФ ЛПНБОДЩ Й ЬФПК ГЕМЙ. дМС ЛБЦДПК ДТХЗПК ГЕМЙ УОБЮБМБ УПВЙТБАФУС ЕЈ ЪБЧЙУЙНПУФЙ. рТЙ ЬФПН ЙНС ГЕМЙ Й ЙНС ЪБЧЙУЙНПУФЕК НПЗХФ УПЧРБДБФШ У ЙНЕОБНЙ ТЕБМШОЩИ ЖБКМПЧ. ч ФБЛПН УМХЮБЕ РЕТЕУВПТЛБ ГЕМЙ РТПЙЪПКДЈФ ФПМШЛП ЕУМЙ ЙУИПДОЙЛЙ ВЩМЙ ЙЪНЕОЕОЩ.

еЭЈ ПДОБ ГЕМШ, ЛПФПТБС ЮБУФП ЙУРПМШЪХЕФУС Ч Makefile - clean. еЈ ЪБДБЮБ ХДБМЙФШ ЧУЕ ВЙОБТОЩЕ ЖБКМЩ, ЮФПВЩ ОБЮБФШ УВПТЛХ "У ЮЙУФПЗП МЙУФБ". чПФ ФБЛ НПЦЕФ ЧЩЗМСДЕФШ Makefile ДМС СДТБ:

All: startup.o main.o script.ld ld --oformat=binary -melf_i386 -T script.ld -o kernel.bin startup.o main.o startup.o: startup.i386.asm fasm startup.i386.asm startup.o main.o: main.c gcc -c -m32 -ffreestanding -o main.o main.c clean: rm -v *.o kernel.bin

ьФПФ ФЕЛУФ ОЕПВИПДЙНП УПИТБОЙФШ Ч ЖБКМ У ЙНЕОЕН Makefile (ВЕЪ ТБУЫЙТЕОЙС) Ч ЛБФБМПЗ У ЙУИПДОЩНЙ ФЕЛУФБНЙ СДТБ. фЕРЕТШ ДПУФБФПЮОП ЧЩРПМОЙФШ ЛПНБОДХ make ВЕЪ РБТБНЕФТПЧ, ОБИПДСУШ Ч ЬФПН ЛБФБМПЗЕ Й НЩ РПМХЮЙН ЖБКМ kernel.bin (МЙВП УППВЭЕОЙС ПВ ПЫЙВЛБИ, ЕУМЙ ЮФП-ФП РПЫМП ОЕ ФБЛ).

б ЧПФ ФБЛ С УПВЙТБА ЪБЗТХЪЮЙЛ:

All: boot.bios.bin boot.bios.bin: boot.bios.asm fasm boot.bios.asm boot.bios.bin clean: rm -v boot.bios.bin

Й make_listfs:

All: compile compile: make_listfs.c gcc -o make_listfs make_listfs.c clean: rm -f make_listfs make_listfs.exe

оХ Й ОБЛПОЕГ ТБУУЛБЦХ РТП ЧЩЪПЧ ДТХЗЙИ Makefile ЙЪ ПДОПЗП. с ДПУФБФПЮОП МЕОЙЧ, ЮФПВЩ ДБЦЕ ЪБИПДЙФШ Ч ЛБФБМПЗЙ У ЛБЦДЩН ЛПНРПОЕОФПН УЙУФЕНЩ, РПЬФПНХ УПЪДБМ 1 Makefile, ЛПФПТЩК УПВЙТБЕФ УТБЪХ ЧУА УЙУФЕНХ. х НЕОС ЕУФШ РБРЛБ src, Ч ОЕК РПДЛБФБМПЗЙ: boot, kernel, make_listfs. ч УБНПК src ОБИПДЙФУС ЧПФ ФБЛПК Makefile:

All: make -C boot/ make -C kernel/ make -C make_listfs/ clean: make -C boot/ clean make -C kernel/ clean make -C make_listfs clean

фЕРЕТШ, ОБИПДСУШ Ч ЛБФБМПЗЕ src С РТПУФП РЙЫХ make Й РПМХЮБА РПМОПУФША УПВТБООХА УЙУФЕНХ, Б ЕУМЙ ОБРЙУБФШ make clean, ФП ЧУЕ ДЧПЙЮОЩЕ ЖБКМЩ ВХДХФ ХДБМЕОЩ Й ПУФБОХФУС ФПМШЛП ЙУИПДОЙЛЙ.

оХ Й Ч ДПЧЕТЫЕОЙЕ РПУМЕДОЙК УЛТЙРФ, ЛПФПТЩК ЧЩРПМОСЕФ РПМОХА ЛПНРЙМСГЙА Й УВПТЛХ ЧУЕИ ЛПНРПОЕОФПЧ Й ПВТБЪБ ДЙУЛБ. ч ПДОПН ЛБФБМПЗЕ У ОЙН ОБДП ТБЪНЕУФЙФШ src, РХУФПК ЛБФБМПЗ bin Й ЛБФБМПЗ disk У ЖБКМПН boot.cfg.

#!/bin/sh make -C src cp src/boot/boot.bios.bin bin/ cp src/kernel/kernel.bin bin/ cp src/make_listfs/make_listfs bin/ dd if=bin/boot.bios.bin of=bin/boot_sector.bin bs=512 count=1 dd if=bin/boot.bios.bin of=disk/boot.bin bs=1 skip=512 cp bin/kernel.bin disk/kernel.bin bin/make_listfs of=disk.img bs=512 size=2880 boot=bin/boot_sector.bin src=./disk read -p "Press Enter to continue..." dummy

у ФБЛЙН ОБВПТПН УЛТЙРФПЧ УВПТЛБ УЙУФЕНБ УФБОПЧЙФУС РТЕДЕМШОП РТПУФПК, ПУПВЕООП ЕУМЙ ХЮЕУФШ, ЮФП РПУМЕДОЙК УЛТЙРФ НПЦОП ЪБРХУЛБФШ ДЧПКОЩН ЛМЙЛПН ЙЪ ЖБКМПЧПЗП НЕОЕДЦЕТБ. тБЪМЙЮОЩЕ ЛПНБОДЩ ЧТПДЕ dd, cp, rm ОЕ УХЭЕУФЧХАФ РПД Windows, РПЬФПНХ ЕЈ РПМШЪПЧБФЕМСН РТЙЗПДЙФУС РБЛЕФ MSYS ЙМЙ Cygwin. пДОБЛП РТПУФБС УВПТЛБ ЧУЕИ ЛПНРПОЕОФПЧ ВХДЕФ ТБВПФБФШ ДБЦЕ ЕУМЙ Х ЧБУ ЕУФШ ФПМШЛП GCC Й fasm (make_listfs МЕЗЛП УЛПНРЙМЙТХЕФУС Й ЪБРХУФЙФУС Ч ЧЙДЕ Windows-РТЙМПЦЕОЙС).

рТЙНЕЮБОЙЕ ДМС РПМШЪПЧБФЕМЕК пу Windows

ld ДМС Windows ОЕ УПЧУЕН РПМОПГЕООЩК - ПО ОЕ РПДДЕТЦЙЧБЕФ ЧЩЧПД УТБЪХ Ч ВЙОБТОЩК ЖБКМ, ФПМШЛП Ч EXE. йУРТБЧЙФШ ЬФП НПЦОП УПЪДБЧ УОБЮБМБ EXE (ld ОЕ ПВТБФЙФ ЧОЙНБОЙЕ, ЮФП ВБЪПЧЩЕ БДТЕУБ УЕЛГЙК ОЕЧПЪНПЦОЩЕ ДМС ЧЕОДПЧЩИ ВЙОБТОЙЛПЧ), Б РПФПН ЧЩФБЭЙФШ ПФФХДБ ЮЙУФЩЕ ДБООЩЕ У РПНПЭША objcopy. еУМЙ ЧЩ УФПМЛОЈФЕУШ У ФЕН, ЮФП ld ПФЛБЪЩЧБЕФУС УПЪДБЧБФШ ЖБКМ kernel.bin, ЧПУРПМШЪХКФЕУШ ЧПФ ФБЛЙН Makefile ДМС СДТБ:

All: startup.o main.o script.ld ld -melf_i386 -T script.ld -o kernel.bin startup.o main.o objcopy kernel.bin -O binary startup.o: startup.i386.asm fasm startup.i386.asm startup.o main.o: main.c gcc -c -m32 -ffreestanding -o main.o main.c clean: rm -v *.o kernel.bin

ъБПДОП ХВЕТЙФЕ УФТПЛХ OUTPUT_FORMAT("binary") ЙЪ script.ld. фЕРЕТШ Й РПД Windows РПМХЮЙФУС УПВТБФШ СДТП УЙУФЕНЩ.

ъБЗТХЪЛБ УЙУФЕНЩ ОБ ТЕБМШОПК НБЫЙОЕ

рПУМЕ ФБЛЙИ ХУРЕИПЧ Х ОЕЛПФПТЩИ НПЦЕФ ЧПЪОЙЛОХФШ ЦЕМБОЙЕ ПРТПВПЧБФШ ОПЧХА пу ОБ ТЕБМШОПН ЦЕМЕЪЕ. ьФП ОЕ РТЕДУФБЧМСЕФ РТПВМЕН. у РПНПЭША HxD Ч Windows ПФЛТПКФЕ ДЙУЛЕФХ ЙМЙ ЖМЕЫЛХ, ЧЩВТБЧ ЧБТЙБОФ "пФЛТЩФШ ДЙУЛ". рТЙ ПФЛТЩФЙЙ ЖМЕЫЛЙ ЧБЦОП ПФЛТЩФШ ЙНЕООП УБНХ ЖМЕЫЛХ, Б ОЕ ЕЈ ТБЪДЕМ. ч ДТХЗПК ЧЛМБДЛЕ ПФЛТПКФЕ disk.img, ЧЩДЕМЙФЕ ЕЗП УПДЕТЦЙНПЕ РПМОПУФША Й УЛПРЙТХКФЕ ОБ ДЙУЛ У ЕЗП УБНПЗП ОБЮБМБ. рПУМЕ ЬФПЗП НПЦОП ОБЦБФШ "уПИТБОЙФШ" Й ДПЦДБФШУС ПЛПОЮБОЙС ЪБРЙУЙ. чУЕ ДБООЩЕ ОБ ЖМЕЫЛЕ ЙМЙ ДЙУЛЕФЕ РТЙ ЬФПН ВХДХФ ХОЙЮФПЦЕОЩ, Б ДМС ФПЗП, ЮФПВЩ ЕЈ ЙУРПМШЪПЧБФШ УОПЧБ РП ОБЪОБЮЕОЙА, ЕЈ РТЙДЈФУС ЪБОПЧП ПФЖПТНБФЙТПЧБФШ!

рПМШЪПЧБФЕМЙ Linux НПЗХФ РПУФХРЙФШ РТПЭЕ - ЧЩРПМОЙФШ УРЕГЙБМШОХА ЛПНБОДХ Ч ФЕТНЙОБМЕ. дМС ДЙУЛЕФЩ:

Dd if=disk.img of=/dev/fd0

дМС ЖМЕЫЛЙ:

Dd if=disk.img of=/dev/sdX

чНЕУФП sdX ОБДП РПДУФБЧЙФШ ОБУФПСЭЕЕ ЙНС ХУФТПКУФЧБ (sda, sdb, sdc, sdd Й Ф. Д.). зМБЧОПЕ РТЙ ЬФПН ОЕ РЕТЕРХФБФШ Й ОЕ ЪБРЙУБФШ ПВТБЪ ОБ УЙУФЕНОЩК ДЙУЛ, ХОЙЮФПЦЙЧ ЧУЕ ДБООЩЕ. тБЪХНЕЕФУС, ПВЕ ЛПНБОДЩ ДПМЦОЩ ЧЩРПМОСФШУС ПФ ЙНЕОЙ root ЙМЙ У РПНПЭША sudo.

рПУМЕ ЬФПЗП ОБДП ОБУФТПЙФШ Ч BIOS ЪБЗТХЪЛХ У ДЙУЛЕФЩ ЙМЙ ЖМЕЫЛЙ (УФБТЩЕ BIOS ОЕ РПДДЕТЦЙЧБАФ ЖМЕЫЛЙ) Й ОБУМБЦДБФШУС ЧЙДПН "Hello world".

ъБЛМАЮЕОЙЕ

оХ ЧПФ УПВУФЧЕООП Й ЧУЈ ОБ УЕЗПДОС. нЩ ОБЛПОЕГ-ФП ЪБЛПОЮЙМЙ РТПЗТБННЙТПЧБОЙЕ ОБ Assembler (ИПФС Ч у ЧУЈ ТБЧОП РТЙДЈФУС ЙОПЗДБ ДЕМБФШ БУУЕНВМЕТОЩЕ ЧУФБЧЛЙ ДМС ТБВПФЩ У ПВПТХДПЧБОЙЕН) Й РЕТЕЫМЙ ОБ СЪЩЛ ЧЩУПЛПЗП ХТПЧОС. еЭЈ ПЮЕОШ НОПЗП РТЕДУФПЙФ УДЕМБФШ. нЩ НПЦЕФЕ ХЦЕ РТПЧПДЙФШ ТБЪМЙЮОЩЕ ЬЛУРЕТЙНЕОФЩ, ЙЪНЕОСС НПК main.c, ФПМШЛП ХЮФЙФЕ, ЮФП МАВБС ПЫЙВЛБ (ДПУФХР Л ОЕУРТПЕГЙТПЧБООПК РБНСФЙ, ДЕМЕОЙЕ ОБ ОПМШ) РТЙЧЕДЈФ Л РЕТЕЪБЗТХЪЛЕ ЙМЙ ЪБЧЙУБОЙА УЙУФЕНЩ (НЩ РПЛБ ОЕ ПВТБВБФЩЧБЕН ЙУЛМАЮЕОЙС, РПЬФПНХ РТПГЕУУПТ ОЕ НПЦЕФ РТПДПМЦЙФШ ТБВПФХ РПУМЕ ПЫЙВЛЙ). дП ЧУФТЕЮЙ!

мАВЩЕ ЧПРТПУЩ ЧЩ НПЦЕФЕ ЪБДБФШ ОБ НПК БДТЕУ: [email protected] . й ДБ, УЕКЮБУ УБНПЕ ЧТЕНС ДМС ТБЪМЙЮОЩИ ЙДЕК РП ЛПОГЕРГЙЙ пу Й РТЕДМПЦЕОЙК.

Данный цикл статей посвящён низкоуровневому программированию, то есть архитектуре компьютера, устройству операционных систем, программированию на языке ассемблера и смежным областям. Пока что написанием занимаются два хабраюзера - и . Для многих старшеклассников, студентов, да и профессиональных программистов эти темы оказываются весьма сложными при обучении. Существует много литературы и курсов, посвящённых низкоуровневому программированию, но по ним сложно составить полную и всеохватывающую картину. Сложно, прочитав одну-две книги по ассемблеру и операционным системам, хотя бы в общих чертах представить, как же на самом деле работает эта сложная система из железа, кремния и множества программ - компьютер.

Каждый решает проблему обучения по-своему. Кто-то читает много литературы, кто-то старается поскорее перейти к практике и разбираться по ходу дела, кто-то пытается объяснять друзьям всё, что сам изучает. А мы решили совместить эти подходы. Итак, в этом курсе статей мы будем шаг за шагом демонстрировать, как пишется простая операционная система. Статьи будут носить обзорный характер, то есть в них не будет исчерпывающих теоретических сведений, однако мы будем всегда стараться предоставить ссылки на хорошие теоретические материалы и ответить на все возникающие вопросы. Чёткого плана у нас нет, так что многие важные решения будут приниматься по ходу дела, с учётом ваших отзывов.

Возможно, мы умышленно будем заводить процесс разработки в тупик, чтобы позволить вам и себе полностью осознать все последствия неверно принятого решения, а также отточить на нем некоторые технические навыки. Так что не стоит воспринимать наши решения как единственно верные и слепо нам верить. Еще раз подчеркнём, что мы ожидаем от читателей активности в обсуждении статей, которая должна сильно влиять на общий процесс разработки и написания последующих статей. В идеале хотелось бы, чтобы со временем некоторые из читателей присоединились к разработке системы.

Мы будем предполагать, что читатель уже знаком с основами языков ассемблер и Си, а также элементарными понятиями архитектуры ЭВМ. То есть, мы не будем объяснять, что такое регистр или, скажем, оперативная память. Если вам не будет хватать знаний, вы всегда можете обратиться к дополнительной литературе. Краткий список литературы и ссылки на сайты с хорошими статьями есть в конце статьи. Также желательно уметь пользоваться Linux, так как все инструкции по компиляции будут приводиться именно для этой системы.

А теперь - ближе к делу. В оставшейся части статьи мы с вами напишем классическую программу «Hello World». Наш хеллоуворлд получится немного специфическим. Он будет запускаться не из какой-либо операционной системы, а напрямую, так сказать «на голом железе». Перед тем, как приступить непосредственно к написанию кода, давайте разберёмся, как же конкретно мы пытаемся это сделать. А для этого надо рассмотреть процесс загрузки компьютера.

Итак, берем свой любимый компьютер и нажимаем самую большую кнопочку на системном блоке. Видим веселую заставку, системный блок радостно пищит спикером и через какое-то время загружается операционная система. Как вы понимаете, операционная система хранится на жёстком диске, и вот тут возникает вопрос: а каким же волшебным образом операционная система загрузилась в ОЗУ и начала выполняться?

Знайте же: за это отвечает система, которая есть на любом компьютере, и имя ей - нет, не Windows, типун вам на язык - называется она BIOS. Расшифровывается ее название как Basic Input-Output System, то есть базовая система ввода-вывода. Находится BIOS на маленькой микросхемке на материнской плате и запускается сразу после нажатия большой кнопки ВКЛ. У BIOS три главных задачи:

  1. Обнаружить все подключенные устройства (процессор, клавиатуру, монитор, оперативную память, видеокарту, голову, руки, крылья, ноги и хвосты…) и проверить их на работоспособность. Отвечает за это программа POST (Power On Self Test – самотестирование при нажатии ВКЛ). Если жизненно важное железо не обнаружено, то никакой софт помочь не сможет, и на этом месте системный динамик пропищит что-нибудь зловещее и до ОС дело вообще не дойдет. Не будем о печальном, предположим, что у нас есть полностью рабочий компьютер, возрадуемся и перейдем к рассмотрению второй функции BIOS:
  2. Предоставление операционной системе базового набора функций для работы с железом. Например, через функции BIOS можно вывести текст на экране или считать данные с клавиатуры. Потому она и называется базовой системой ввода-вывода. Обычно операционная система получает доступ к этим функциям посредством прерываний.
  3. Запуск загрузчика операционной системы. При этом, как правило, считывается загрузочный сектор - первый сектор носителя информации (дискета, жесткий диск, компакт-диск, флэшка). Порядок опроса носителей можно задать в BIOS SETUP. В загрузочном секторе содержится программа, иногда называемая первичным загрузчиком. Грубо говоря, задача загрузчика - начать запуск операционной системы. Процесс загрузки операционной системы может быть весьма специфичен и сильно зависит от её особенностей. Поэтому первичный загрузчик пишется непосредственно разработчиками ОС и при установке записывается в загрузочный сектор. В момент запуска загрузчика процессор находится в реальном режиме.
Печальная новость: размер начального загрузчика должен быть всего 512 байт. Почему так мало? Для этого нам надо ознакомиться с устройством дискеты. Вот познавательная картинка:

На картинке изображена поверхность дискового накопителя. У дискеты 2 поверхности. На каждой поверхности есть кольцеобразные дорожки (треки). Каждый трек делится на маленькие дугообразные кусочки, называемые секторами. Так вот, исторически сложилось, что сектор дискеты имеет размер 512 байт. Самый первый сектор на диске, загрузочный сектор, читается BIOS"ом в нулевой сегмент памяти по смещению 0x7С00, и дальше по этому адресу передается управление. Начальный загрузчик обычно загружает в память не саму ОС, а другую программу-загрузчик, хранящуюся на диске, но по каким-то причинам (скорее всего, эта причина - размер) не влезающую в один сектор. А поскольку пока что роль нашей ОС выполняет банальный хеллоуворлд, наша главная цель - заставить компьютер поверить в существование нашей ОС, пусть даже и на одном секторе, и запустить её.

Как устроен загрузочный сектор? На PC единственное требование к загрузочному сектору - это содержание в двух его последних байтах значений 0x55 и 0xAA - сигнатуры загрузочного сектора. Итак, уже более-менее понятно, что нам нужно делать. Давайте же писать код! Приведённый код написан для ассемблера yasm .

Section .text use16 org 0x7C00 ; наша программа загружается по адресу 0x7C00 start: mov ax, cs mov ds, ax ; выбираем сегмент данных mov si, message cld ; направление для строковых команд mov ah, 0x0E ; номер функции BIOS mov bh, 0x00 ; страница видеопамяти puts_loop: lodsb ; загружаем очередной символ в al test al, al ; нулевой символ означает конец строки jz puts_loop_exit int 0x10 ; вызываем функцию BIOS jmp puts_loop puts_loop_exit: jmp $ ; вечный цикл message: db "Hello World!", 0 finish: times 0x1FE-finish+start db 0 db 0x55, 0xAA ; сигнатура загрузочного сектора

Эта короткая программа требует ряда важных пояснений. Строка org 0x7C00 нужна для того, чтобы ассемблер (имеется в виду программа, а не язык) правильно рассчитал адреса для меток и переменных (puts_loop, puts_loop_exit, message). Вот мы ему и сообщаем, что программа будет загружена в память по адресу 0x7C00.
В строках
mov ax, cs mov ds, ax
происходит установка сегмента данных (ds) равным сегменту кода (cs), поскольку в нашей программе и данные, и код хранятся в одном сегменте.

Далее в цикле посимвольно выводится сообщение «Hello World!». Для этого используется функция 0x0E прерывания 0x10 . Она имеет следующие параметры:
AH = 0x0E (номер функции)
BH = номер видеостраницы (пока не заморачиваемся, указываем 0)
AL = ASCII-код символа

В строке « jmp $ » программа зависает. И правильно, незачем ей выполнять лишний код. Однако чтобы компьютер опять заработал, придется перезагрузиться.

В строке « times 0x1FE-finish+start db 0 » производится заполнение остатка кода программы (за исключением последних двух байт) нулями. Делается это для того, чтобы после компиляции в последних двух байтах программы оказалась сигнатура загрузочного сектора.

С кодом программы вроде разобрались, давайте теперь попробуем скомпилировать это счастье. Для компиляции нам понадобится, собственно говоря, ассемблер - выше упомянутый yasm . Он есть в большинстве репозиториев Linux. Программу можно откомпилировать следующим образом:

$ yasm -f bin -o hello.bin hello.asm

Полученный файл hello.bin нужно записать в зарузочный сектор дискеты. Делается это примерно так (разумеется, вместо fd нужно подставить имя своего дисковода).

$ dd if=hello.bin of=/dev/fd

Поскольку далеко не у всех остались дисководы и дискеты, можно воспользоваться виртуальной машиной, например, qemu или VirtualBox . Для этого придётся сделать образ дискеты с нашим загрузчиком и вставить его в «виртуальный дисковод».
Создаём образ диска и заполняем его нулями:

$ dd if=/dev/zero of=disk.img bs=1024 count=1440

Записываем в самое начало образа нашу программу:
$ dd if=hello.bin of=disk.img conv=notrunc

Запускаем полученный образ в qemu:
$ qemu -fda disk.img -boot a

После запуска вы должны увидеть окошко qemu с радостной строчкой «Hello World!». На этом первая статья заканчивается. Будем рады увидеть ваши отзывы и пожелания.

Читая Хабр в течении последних двух лет, я видел только несколько попыток разработки ОС (если конкретно: от пользователей и (отложено на неопределённый срок) и (не заброшено, но пока больше походит на описание работы защищённого режима x86-совместимых процессоров, что бесспорно тоже необходимо знать для написания ОС под x86); и описание готовой системы от (правда не с нуля, хотя в этом нет ничего плохого, может даже наоборот)). Мне почему-то думается, что почти все системные (да и часть прикладных) программисты хотя бы раз, но задумывались о написании собственной операционной системы. В связи с чем, 3 ОС от многочисленного сообщества данного ресурса кажется смешным числом. Видимо, большинство задумывающихся о собственной ОС так никуда дальше идеи и не идёт, малая часть останавливается после написания загрузчика, немногие пишут куски ядра, и только безнадёжно упёртые создают что-то отдалённо напоминающее ОС (если сравнивать с чем-то вроде Windows/Linux). Причин для этого можно найти много, но главной на мой взгляд является то, что люди бросают разработку (некоторые даже не успев начать) из-за небольшого количества описаний самого процесса написания и отладки ОС, который довольно сильно отличается от того, что происходит при разработке прикладного ПО.

Этой небольшой заметкой хотелось бы показать, что, если правильно начать, то в разработке собственной ОС нету ничего особо сложного. Под катом находится краткое и довольно общее руководство к действию по написанию ОС с нуля.

Как не надо начинать
Просьба не воспринимать следующий ниже текст как явную критику чьих-то статей или руководств по написанию ОС. Просто слишком часто в подобных статьях под громкими заголовками акцент делается на реализации какой-то минимальной заготовки, а подаётся она как прототип ядра. На самом деле следует задумываться о структуре ядра и взаимодействии частей ОС в целом, а тот прототип рассматривать как стандартное «Hello, World!»-приложение в мире прикладного ПО. В качестве небольшого оправдания этих замечаний, следует сказать, что ниже есть подраздел «Hello, World!», которому в данном случае уделено ровно столько внимания сколько нужно, и не больше.

Не надо писать загрузчик. Умные люди придумали Multiboot Specification , реализовали и подробно описали, что это такое и как его использовать. Не хочу повторяться, просто скажу, что это работает, облегчает жизнь, и его надо применять. Спецификацию, кстати, лучше прочесть полностью, она небольшая и даже содержит примеры.

Не надо писать ОС полностью на ассемблере. Это не так чтобы плохо, скорее наоборот - быстрые и маленькие программы всегда будут в почёте. Просто так как этот язык требует значительно больших усилий на разработку, то использование ассемблера приведёт только к уменьшению энтузиазма и, как следствие, к забрасыванию исходников ОС в долгий ящик.

Не надо загружать кастомный шрифт в видео память и выводить что-либо на русском. Толку от этого никакого. Гораздо проще и универсальнее использовать английский, а изменение шрифта оставить на потом, загружая его с жёсткого диска через драйвер файловой системы (заодно будет дополнительный стимул сделать больше, чем просто начать).

Подготовка
Для начала как всегда следует ознакомиться с общей теорией, дабы иметь какие-то представления о предстоящем объёме работ. Хорошими источниками по рассматриваемому вопросу являются книги Э. Таненбаума, которые уже упоминались в других статьях о написании ОС на Хабре. Также есть статьи с описанием существующих систем, и есть различные руководства/рассылки/статьи/примеры/сайты с уклоном в разработку ОС, ссылки на часть из которых приведены в конце статьи.

После начального ликбеза необходимо определиться с главными вопросами:

  • целевая архитектура - x86 (real/protected/long mode), PowerPC, ARM, ...
  • архитектура ядра/ОС - монолит, модульный монолит, микроядро, экзоядро, разные гибриды
  • язык и его компилятор - C, C++, ...
  • формат файла ядра - elf, a.out, coff, binary, ...
  • среда разработки (да, это тоже играет не последнюю роль) - IDE, vim, emacs, ...
Далее следует углублять знания согласно выбранному и по следующим направлениям:
  • видео память и работа с ней - вывод в качестве доказательства работы необходим с самого начала
  • HAL (Hardware Abstraction layer) - даже если поддержка нескольких аппаратных архитектур и не планируется грамотное отделение самых низкоуровневых частей ядра от реализации таких абстрактных вещей как процессы, семафоры и так далее лишним не будет
  • управление памятью - физической и виртуальной
  • управление исполнением - процессы и потоки, их планирование
  • управление устройствами - драйвера
  • виртуальные файловые системы - для обеспечения единого интерфейса к содержимому различных ФС
  • API (Application Programming Interface) - как именно приложения будут обращаться к ядру
  • IPC (Interprocess Communication) - рано или поздно процессам придется взаимодействовать
Инструменты
Учитывая выбранные язык и средства разработки следует подобрать такой набор утилит и их настроек, которые в будущем позволят путём написания скриптов, максимально облегчить и ускорить сборку, подготовку образа и запуск виртуальной машины с проектом. Остановимся немного детальнее на каждом из этих пунктов:
  • для сборки подойдут любые стандартные средства, как то make, cmake,… Тут в ход могут пойти скрипты для линкера и (специально написанные) утилиты для добавления Multiboot-заголовка, контрольных сумм или для каких-либо других целей.
  • под подготовкой образа имеется ввиду его монтирование и копирование файлов. Соответственно, формат файла образа надо подбирать так, чтобы его поддерживала как утилита монтирования/копирования, так и виртуальная машина. Естественно, никто не запрещает совершать действия из этого пункта либо как финальную часть сборки, либо как подготовку к запуску эмулятора. Всё зависит от конкретных средств и выбранных вариантов их использования.
  • запуск виртуальной машины труда не представляет, но нужно не забыть сначала отмонтировать образ (отмонтирование в этом пункте, так как до запуска виртуальной машины реального смысла в этой операции нет). Также не лишним будет скрипт для запуска эмулятора в отладочном режиме (если таковой имеется).
Если все предыдущие шаги выполнены, следует написать минимальную программу, которая будет загружаться как ядро и выводить что-нибудь на экран. В случае обнаружения неудобств или недостатков выбранных средств, необходимо их (недостатки) устранить, ну или, в худшем случае, принять как данность.

На данном шаге необходимо проверить как можно больше возможностей средств разработки, которые планируется использовать в будущем. Например, загрузку модулей в GRUB или использование в виртуальной машине физического диска/раздела/флешки вместо образа.

После того как этот этап прошёл успешно, начинается настоящая разработка.

Обеспечение run-time поддержки
Так как предлагается писать на языках высокого уровня, следует позаботиться об обеспечении поддержки части средств языка, которые обычно реализуются авторами пакета компилятора. Например для C++, сюда относятся:
  • функция для динамического выделения блока данных на стеке
  • работа с heap
  • функция копирования блока данных (memcpy)
  • функция-точка входа в программу
  • вызовы конструкторов и деструкторов глобальных объектов
  • ряд функций для работы с исключениями
  • стаб для нереализованных чисто-виртуальных функций
При написании «Hello, World!» отсутствие этих функций может никак не дать о себе знать, но по мере добавления кода, линкер начнёт жаловаться на неудовлетворённые зависимости.

Естественно, тут же следует упомянуть и о стандартной библиотеке. Полная реализация не является необходимой, но основное подмножество функций реализовать стоит. Тогда написание кода будет значительно привычнее и быстрее.

Отладка
Не смотрите, что об отладке говорится ближе к концу статьи. На самом деле это очень серьёзный и непростой вопрос в разработке ОС, так как обычные средства тут неприменимы (за некоторым исключением).

Можно посоветовать следующее:

  • само собой разумеющееся, отладочный вывод
  • assert с немедленным выходом в «отладчик» (см. следующий пункт)
  • некоторое подобие консольного отладчика
  • проверить не позволяет ли эмулятор подключать отладчик, таблицы символов или ещё что-нибудь
Без встроенного в ядро отладчика поиск ошибок имеет вполне реальный шанс превратится в кошмар. Так что от его написания на некотором этапе разработки просто никуда не деться. А раз это неизбежно, то лучше начать его писать заранее и таким образом значительно облегчить себе разработку и сэкономить довольно много времени. Важно суметь реализовать отладчик независимым от ядра образом, чтобы отладка минимальным образом влияла на нормальную работу системы. Вот несколько типов команд, которые могут быть полезны:
  • часть стандартных отладочных операций: точки останова, стек вызовов, вывод значений, печать дампа, ...
  • команды вывода различной полезной информации, вроде очереди исполнения планировщика или различной статистики (она не так бесполезно как может показаться сначала)
  • команды проверки непротиворечивости состояния различных структур: списков свободной/занятой памяти, heap или очереди сообщений
Развитие
Дальше необходимо написать и отладить основные элементы ОС, которые в данный момент должны обеспечить её стабильную работу, а в будущем - лёгкую расширяемость и гибкость. Кроме менеджеров памяти/процессов/(чего-нибудь ещё) очень важным является интерфейс драйверов и файловых систем. К их проектированию следует подходить с особой тщательностью, учитывая всё разнообразие типов устройств/ФС. Со временем их конечно можно будет поменять, но это очень болезненный и подверженный ошибкам процесс (а отладка ядра - занятие не из лёгких), поэтому просто запомните - минимум десять раз подумайте над этими интерфейсами прежде чем возьмётесь за их реализацию.
Подобие SDK
По мере развития проекта в нём должны добавляться новые драйвера и программы. Скорее всего уже на втором драйвере (возможно определённого типа)/программе будут заметны некоторые общие черты (структура каталогов, файлы управления сборкой, спецификация зависимостей между модулями, повторяющийся код в main или в обработчиках системных запросов (например если драйвера сами проверяют их совместимость с устройством)). Если так и есть, то это признак необходимости разработки шаблонов для различного типа программ под вашу ОС.

Необходимости в документации, описывающей процесс написания того или другого типа программы, нет. Но сделать заготовку из типовых элементов стоит. Это не только упростит добавление программ (что можно делать и копированием существующих программ с их последующим изменением, но это потребует больше времени), но также позволит легче их обновлять при изменениях в интерфейсах, форматах или чем-то ещё. Понятно, что таких изменений в идеале быть не должно, но так как разработка ОС - вещь нетипичная, то есть достаточно много мест для потенциально неверных решений. А вот понимание ошибочности принятых решений как всегда придёт через некоторое время после их внедрения.

Дальнейшие действия
Если кратко, то: читать про операционные системы (и в первую очередь именно про их устройство), развивать свою систему (темпы на самом деле не важны, главное - не прекращать совсем и возвращаться к проекту время от времени с новыми силами и идеями) и естественно исправлять в ней ошибки (для нахождения которых надо иногда запускать систему и «играться» с ней). Со временем процесс разработки будет становиться всё легче и легче, ошибки будут встречаться реже, а вы будете зачислены в список «безнадёжно упёртых», тех немногих, которые несмотря на некоторую абсурдность идеи разработки собственной ОС, всё же сделали это.

Загрузка...