Пишем макет 16-битного ядра на C/C++
В первой и второй статьях я лишь коротко представил процесс написания загрузчика на ассемблере и C. Для меня это было хоть и непросто, но в то же время интересно, так что я остался доволен. Однако создания загрузчика мне показалось мало, и я увлекся идеей его расширения дополнительной функциональностью. Но так как в итоге размер готовой программы превысил 512 байт, то при попытке запуска системы с несущего ее загрузочного диска я столкнулся с проблемой “This is not a bootable disk”.
О чем эта статья?
В ней мы рассмотрим значимость файловой системы для загрузчика, а также напишем макет ядра, которое будет просто отображать командную строку, позволяя ввод текста. При этом я поясню, почему внедряю загрузчик в дискету, отформатированную в FAT. И поскольку одной статьи для полноценного раскрытия темы файловых систем однозначно недостаточно я постараюсь придерживаться краткости и максимальной простоты изложения.
Нужен ли для этого опыт?
Статья окажется намного полезнее тем, у кого уже есть опыт программирования. Несмотря на ее чисто ознакомительный, казалось бы, характер, написание программ на ассемблере и C может оказаться достаточно сложным. Поэтому новичкам я рекомендую для начала ознакомиться с вводными принципами программирования и уже потом возвращаться к этому материалу.
Как и ранее, процесс изложения построен по принципу вопрос-ответ, что должно упростить восприятие информации.
План статьи
- Ограничения загрузчика
- Вызов из загрузчика других файлов диска
- Файловая система FAT
- Принцип работы FAT
- Среда разработки
- Написание загрузчика для FAT
- Мини-проект: написание 16-битного ядра
- Тестирование ядра
Ограничения загрузчика
В предыдущих статьях я написал загрузчик и, реализовав с его помощью вывод на экран цветных прямоугольников, захотел добавить дополнительную функциональность. Однако при этом я столкнулся с ограничением размера сектора в 512 байт, что не позволило добиться желаемого стандартным путем.
В итоге передо мной стоит две задачи:
- Расширить загрузчик кодом, реализующим дополнительную функциональность.
- Сохранить при этом размер загрузчика в 512 байт.
Как я буду это делать?
- Напишу программу kernel.c на C, внедрив в нее всю необходимую функциональность.
- Скомпилирую и сохраню исполняемый файл как kernel.bin.
- Скопирую этот файл во второй сектор загрузочного диска.
В загрузчике мы можем просто загрузить второй сектор, содержащий kernel.bin, в RAM по адресу, к примеру, 0x1000 , а затем перейти к этому адресу из 0x7с00 и запустить kernel.bin.
Вот схема для лучшего понимания идеи:

Запуск из загрузчика других файлов диска
Как мы теперь знаем, у нас есть возможность передачи управления от загрузчика ( 0x7c00 ) в другую область памяти, где размещается, например, наш kernel.bin, после чего продолжить выполнение. Но здесь у меня я хочу кое-что уточнить.
Как узнать сколько секторов kernel.bin займет на диске?
Ну это простой вопрос. Для ответа на него нам достаточно выполнить несложную арифметику, а именно разделить размер kernel.bin на размер сектора, который составляет 512 байт. Например, если kernel.bin будет равен 1024 байта, то и займет он 2 сектора.
В связи с этим вам потребуется захардкодить в загрузчике необходимое для ядра количество секторов, и при возможном изменении его размера в дальнейшем не забывать о внесении соответствующих правок. В противном случае при несоответствии прописанного и фактического размеров загрузка ядра будет проваливаться.
Можно ли добавить помимо kernel.bin другие файлы, например office.bin, entertainment.bin, drivers.bin?
В принципе да. Для этого нужно будет поочередно добавить требуемые файлы на дискету, не забывая при этом обновлять загрузчик необходимой информацией об их расположении, занимаемом количестве секторов и прочем. Но тут я отдельно скажу, что в итоге сложность системы сильно увеличивается, и в конечном счете мне такой вариант не очень понравился.
Откуда мы знаем, что после загрузочного сектора выполняются именно желаемые файлы?
Хороший вопрос, так как мы, действительно, только загружаем соответствующий сектор в память и начинаем его выполнение. Это не идеальный вариант, и в нем кое-чего не хватает.
Чего не хватает?
Загрузчик вслепую загружает сектора каждого файла один за другим и затем начинает выполнение этих файлов. Но даже до того, как он попробует загрузить файлы в память, должен присутствовать способ проверить, существуют ли они вообще на диске.
Что произойдет, если по ошибке загрузить во второй сектор не тот файл, обновить загрузчик и начать выполнение?
В этом случае система просто дает сбой. Поэтому на диске должна присутствовать фиксированная область, где подобно индексу в книге будут прописаны все имена файлов.
Тогда загрузчик будет запрашивать индекс этого файла на диске, и в случае обнаружения таковой будет обнаружен в списке – продолжать загрузку этого файла в память.
Мне такой вариант очень нравится, так как он избавляет от лишних действий.
Здесь мы избегаем нескольких проблем. До этого загрузчик вслепую загружал жестко закодированные сектора. Но зачем загружать файл, не будучи уверенным в том, что это именно нужный файл, и что он вообще существует?
Как это решается?
Для этого нужно просто организовать информацию на диске, после чего перепрограммировать загрузчик, повысив тем самым его эффективность при загрузке файлов.
Такой вид организации информации называется файловой системой. Существует много типов файловых систем, среди которых есть как коммерческие, так и бесплатные. Вот некоторые из них:
- FAT
- FAT16
- FAT32
- NTFS
- EXT
- EXT2
- EXT3
- EXT4
Для лучшего понимания этой файловой системы вам потребуется знать некоторые технические нюансы.
Минимальной единицей измерения пространства FAT является кластер, который занимает 1 сектор накопителя. Иначе говоря, на дискете, отформатированной в FAT, 1 кластер эквивалентен 1 сектору и, соответственно, равен 512 байт.
Для удобства использования файловая система дополнительно разделяется на четыре основные области:
- загрузочный сектор (boot sector);
- таблицу размещения файлов (file allocation table);
- корневой каталог (root directory);
- область данных (data area).

Рассмотрим каждую часть подробнее.
Загрузочный сектор
Загрузочный сектор содержит служебную информацию, на основе которой ОС распознает тип файловой системы диска, после чего уже переходит к чтению его содержимого.
Информация о файловой системе FAT, содержащаяся в загрузочном секторе, называется блоком параметров BIOS.
Блок параметров BIOS
Ниже я привел пример значений из этого блока:

Таблица размещения файлов
Эта таблица представляет своего рода связанный список, в котором каждая ячейка, соответствующая кластеру файла, содержит значение следующего кластера, формируя таким образом связанную цепочку всех кластеров этого файла.
Это значение кластера служит для:
- определения окончания файла. Если оно находится между 0x0ff8 и 0x0fff , значит файл не содержит данных в других секторах, т.е. достигнут его конец.
- определения следующего кластера с данными этого файла.
Корневой каталог
Корневой каталог выступает в роли индекса всех находящихся на диске файлов. Именно здесь загрузчик ищет имя нужного файла и в случае обнаружения обращается к его первому кластеру для начала загрузки.
После обнаружения и начала считывания первого кластера загрузчик с помощью FAT находит все последующие занимаемые файлом кластеры.
Область данных
Здесь содержаться фактические данные файлов.
Как только программа обнаруживает искомый сектор файла, именно из этой области извлекаются соответствующие данные.
Принцип работы FAT
Продолжим наш пример с kernel.bin, который загрузчик помещает в память для выполнения. Теперь в этом сценарии нужно прописать для загрузчика следующую функциональность:
- Сравнить первые 11 байт данных с kernel.bin, начиная со смещения 0 в таблице корневого каталога.
- В случае совпадения этой строки – извлечь первый кластер kernel.bin из смещения 26 корневого каталога.
- Далее преобразовать этот кластер в соответствующий сектор и загрузить его данные в память.
- После загрузки первого сектора в память перейти к поиску в FAT следующего кластера файла и определить, является он последним, или есть еще данные в других кластерах.

Среда разработки
Для успешной реализации этой задачи нам нужно иметь представление о следующем. Более подробную информацию по этим пунктам можете найти в двух предыдущих статьях.
- Операционная система (GNU Linux).
- Ассемблер (GNU Assembler).
- Набор инструкций (x86).
- Написание инструкций для микропроцессора x86 на GNU Assembler.
- Компилятор (GNU C компилятор GCC).
- Компоновщик (GNU linker ld)
- Эмулятор, например bochs, используемый для тестирования.
Написание загрузчика FAT
Ниже я привожу фрагмент кода для выполнения файла kernel.bin на FAT-диске.
Файл: stage0.S
В этом основном файле загрузки происходит:
- Инициализация всех регистров и настройка стека вызовом макроса initEnvironment .
- Вызов макроса loadFile для загрузки kernel.bin в память по адресу 0x1000:0000 и последующей передачи ему права выполнения.
Файл: macros.S
Этот файл содержит все предопределенные макросы и функции.
Общая сводка
initEnvironment:
- Макрос для установки сегментных регистров.
- Аргументов не требует.
writeString:
- Макрос для отображения на экране строки с завершающим нулем.
- В качестве аргумента передается строковая переменная с завершающим нулем.
- Макрос для чтения с диска заданного сектора и его загрузки в целевой адрес памяти.
- Количество аргументов: 4.
- Макрос для проверки наличия файла.
- Количество аргументов: 1.
clusterToLinearBlockAddress:
- Макрос для преобразования заданного кластера в номер сектора.
- Количество аргументов: 1.
- Макрос для загрузки целевого файла в память с последующей передачей ему права выполнения.
- Количество аргументов: 1.
- Макрос для передачи права выполнения конкретному адресу памяти в RAM.
- Аргументов не требует.
Файл: routines.S
Общая сводка
_initEnvironment:
- Функция, отвечающая за установку сегментных регистров.
- Аргументов не требует.
_writeString:
- Функция для отображения на экране строки с завершающим нулем.
- В качестве аргумента получает строковую переменную с завершающим нулем.
- Макрос для считывания заданного сектора с диска и его загрузки в целевой адрес памяти.
- Количество аргументов: 4.
pushw <номер сектора>
pushw <адрес>
pushw <смещение>
pushw <всего секторов>
call _readSector
addw $0x0008, %sp
- Функция для проверки наличия файла.
- Количество аргументов: 1
clusterToLinearBlockAddress:
- Макрос для преобразования ID заданного кластера в номер сектора.
- Количество аргументов: 1
- Макрос для загрузки целевого файла в память с последующей передачей ему права выполнения.
- Количество аргументов: 1
Файл: stage0.ld
Этот файл служит для линковки файла stage0.object .
Файл: bochsrc.txt
Файл-конфигурации, необходимый для запуска эмулятора bochs.
Мини-проект: написание 16-битного ядра
Ниже приведен исходный код макета ядра, используемого как часть процесса тестирования. Нам нужно только скомпилировать этот код, используя make file и проверить, загрузит ли его загрузчик.
Сначала будет отображаться заставка с головой дракона, сопровождаемая экраном приветствия, после которого откроется командная строка.
Учтите, что здесь не запрограммировано никаких команд или утилит для выполнения. Это просто макет ядра, заготовленный исключительно в целях тестирования.
Файл: kernel.c
Общая сводка
- Устанавливает сегментные регистры и формирует стек.
- Количество аргументов: none
setResolution():
- Устанавливает разрешение экрана 80*25.
- Количество аргументов: none.
clearScreen():
- Заполняет буфер экрана пробелами.
- Количество аргументов: none
- Устанавливает курсор в заданное положение на экране.
- Количество аргументов: 2.
- По желанию пользователя активирует или отключает курсор.
- Количество аргументов: 1.
- Устанавливает разрешение 80*25, очищает экран и устанавливает курсор в позицию (0,0).
- Количество аргументов: none
- Регистрирует нажатия клавиш без эха.
- Количество аргументов: none
- Возвращает скан-код нажатой клавиши и соответствующее значение ascii.
- Количество аргументов: none.
- Отображает символы нажатых клавиш на экране.
- Количество аргументов: 1.
printString():
- Выводит на экран строку с завершающим нулем.
- Количество аргументов: 1.
- Вызывает задержку на несколько секунд.
- Количество аргументов: 1.
- Возвращает значение длины строки с завершающим нулем.
- Количество аргументов: 1.
splashScreen():
- Отображает заданную картинку определенное время.
- Количество аргументов: 1.
- Отображает командную строку.
- Количество аргументов: none.
Тестирование ядра
Использование исходного кода:
В прикрепленном архиве sourcecode.tar.gz находятся все исходные файлы и каталоги, необходимые для генерации исполняемых файлов.
Убедитесь, что вы являетесь супер-пользователем системы, после чего распакуйте архив.
Для перехода к компиляции и тестированию кода установите эмулятор bochs-x64 и GNU bin-utils.
После извлечения файлов вы увидите 5 каталогов:
- bin
- iso
- kernel
- log
- src
- cd $(DIRECTORY)/src
- make -f Makefile test
- bochs
Сриншоты
Экран 1:
Это первый экран, отображаемый при выполнении ядра.

Экран 2:
Дальше идет экран приветствия:

Экран 3:
Это командная строка, в которой можно ввести текст.

Экран 4:
Здесь я привожу пример написания команд и перехода строки при нажатии Ввода.

Если у вас возникнут какие-либо сложности, смело пишите в комментариях (ссылка на оригинал публикации), буду рад помочь разобраться.
Заключение
Надеюсь, что эта статья помогла вам понять принцип работы файловой системы, а также освоить написание загрузчика для ее считывания. Теперь вы можете смело продолжать экспериментировать с внедрением в написанное нами ядро другой функциональности.
Руководство по созданию ядра для x86-системы. Часть 1. Просто ядро
Давайте напишем простое ядро, которое можно загрузить при помощи бутлоадера GRUB x86-системы. Это ядро будет отображать сообщение на экране и ждать.

Как загружается x86-система?
Прежде чем мы начнём писать ядро, давайте разберёмся, как система загружается и передаёт управление ядру.
В большей части регистров процессора при запуске уже находятся определённые значения. Регистр, указывающий на адрес инструкций (Instruction Pointer, EIP), хранит в себе адрес памяти, по которому лежит исполняемая процессором инструкция. EIP по умолчанию равен 0xFFFFFFF0. Таким образом, x86-процессоры на аппаратном уровне начинают работу с адреса 0xFFFFFFF0. На самом деле это — последние 16 байт 32-битного адресного пространства. Этот адрес называется вектором перезагрузки (reset vector).
Теперь карта памяти чипсета гарантирует, что 0xFFFFFFF0 принадлежит определённой части BIOS, не RAM. В это время BIOS копирует себя в RAM для более быстрого доступа. Адрес 0xFFFFFFF0 будет содержать лишь инструкцию перехода на адрес в памяти, где хранится копия BIOS.
Так начинается исполнение кода BIOS. Сперва BIOS ищет устройство, с которого можно загрузиться, в предустановленном порядке. Ищется магическое число, определяющее, является ли устройство загрузочным (511-ый и 512-ый байты первого сектора должны равняться 0xAA55).
Когда BIOS находит загрузочное устройство, она копирует содержимое первого сектора устройства в RAM, начиная с физического адреса 0x7c00; затем переходит на адрес и исполняет загруженный код. Этот код называется бутлоадером.
Бутлоадер загружает ядро по физическому адресу 0x100000. Этот адрес используется как стартовый во всех больших ядрах на x86-системах.
Все x86-процессоры начинают работу в простом 16-битном режиме, называющимся реальным режимом. Бутлоадер GRUB переключает режим в 32-битный защищённый режим, устанавливая нижний бит регистра CR0 в 1. Таким образом, ядро загружается в 32-битном защищённом режиме.
Заметьте, что в случае с ядром Linux GRUB видит протоколы загрузки Linux и загружает ядро в реальном режиме. Ядро самостоятельно переключается в защищённый режим.
Что нам нужно?
- x86-компьютер;
- Linux; ;
- gcc;
- ld (GNU Linker); ;
Исходники можно найти на GitHub.
Задаём точку входа на ассемблере
Как бы не хотелось ограничиться одним Си, что-то придётся писать на ассемблере. Мы напишем на нём небольшой файл, который будет служить исходной точкой для нашего ядра. Всё, что он будет делать — вызывать внешнюю функцию, написанную на Си, и останавливать поток программы.
Как же нам сделать так, чтобы этот код обязательно был именно исходной точкой?
Мы будем использовать скрипт-линковщик, который соединяет объектные файлы для создания конечного исполняемого файла. В этом скрипте мы явно укажем, что хотим загрузить данные по адресу 0x100000.
Вот код на ассемблере:
Первая инструкция, bits 32 , не является x86-ассемблерной инструкцией. Это директива ассемблеру NASM, задающая генерацию кода для процессора, работающего в 32-битном режиме. В нашем случае это не обязательно, но вообще полезно.
Со второй строки начинается секция с кодом.
global — это ещё одна директива NASM, делающая символы исходного кода глобальными. Таким образом, линковщик знает, где находится символ start — наша точка входа.
kmain — это функция, которая будет определена в файле kernel.c . extern значит, что функция объявлена где-то в другом месте.
Затем идёт функция start , вызывающая функцию kmain и останавливающая процессор инструкцией hlt . Именно поэтому мы заранее отключаем прерывания инструкцией cli .
В идеале нам нужно выделить немного памяти и указать на неё указателем стека ( esp ). Однако, похоже, что GRUB уже сделал это за нас. Тем не менее, вы всё равно выделим немного места в секции BSS и переместим на её начало указатель стека. Мы используем инструкцию resb , которая резервирует указанное число байт. Сразу перед вызовом kmain указатель стека ( esp ) устанавливается на нужное место инструкцией mov .
Ядро на Си
В kernel.asm мы совершили вызов функции kmain() . Таким образом, наш «сишный» код должен начать исполнение с kmain() :
Всё, что сделает наше ядро — очистит экран и выведет строку «my first kernel».
Сперва мы создаём указатель vidptr , который указывает на адрес 0xb8000. С этого адреса в защищённом режиме начинается «видеопамять». Для вывода текста на экран мы резервируем 25 строк по 80 ASCII-символов, начиная с 0xb8000.
Каждый символ отображается не привычными 8 битами, а 16. В первом байте хранится сам символ, а во втором — attribute-byte . Он описывает форматирование символа, например, его цвет.
Для вывода символа s зелёного цвета на чёрном фоне мы запишем этот символ в первый байт и значение 0x02 во второй. 0 означает чёрный фон, 2 — зелёный цвет текста.
Вот таблица цветов:
В нашем ядре мы будем использовать светло-серый текст на чёрном фоне, поэтому наш байт-атрибут будет иметь значение 0x07.
В первом цикле программа выводит пустой символ по всей зоне 80×25. Это очистит экран. В следующем цикле в «видеопамять» записываются символы из нуль-терминированной строки «my first kernel» с байтом-атрибутом, равным 0x07. Это выведет строку на экран.
Связующая часть
Мы должны собрать kernel.asm в объектный файл, используя NASM; затем при помощи GCC скомпилировать kernel.c в ещё один объектный файл. Затем их нужно присоединить к исполняемому загрузочному ядру.
Для этого мы будем использовать связывающий скрипт, который передаётся ld в качестве аргумента.
Сперва мы зададим формат вывода как 32-битный Executable and Linkable Format (ELF). ELF — это стандарный формат бинарных файлов Unix-систем архитектуры x86. ENTRY принимает один аргумент, определяющий имя символа, являющегося точкой входа. SECTIONS — это самая важная часть. В ней определяется разметка нашего исполняемого файла. Мы определяем, как должны соединяться разные секции и где их разместить.
В скобках после SECTIONS точка (.) отображает счётчик положения, по умолчанию равный 0x0. Его можно изменить, что мы и делаем.
Смотрим на следующую строку: .text : < *(.text) >. Звёздочка (*) — это специальный символ, совпадающий с любым именем файла. Выражение *(.text) означает все секции .text из всех входных файлов.
Таким образом, линковщик соединяет все секции кода объектных файлов в одну секцию исполняемого файла по адресу в счётчике положения (0x100000). После этого значение счётчика станет равным 0x100000 + размер полученной секции.
Аналогично всё происходит и с другим секциями.
Grub и Multiboot
Теперь все файлы готовы к созданию ядра. Но остался ещё один шаг.
Существует стандарт загрузки x86-ядер с использованием бутлоадера, называющийся Multiboot specification. GRUB загрузит наше ядро, только если оно удовлетворяет этим спецификациям.
Следуя им, ядро должно содержать заголовок в своих первых 8 килобайтах. Кроме того, этот заголовок должен содержать 3 поля, являющихся 4 байтами:
- магическое поле: содержит магическое число 0x1BADB002 для идентификации ядра.
- поле flags: нам оно не нужно, установим в ноль.
- поле checksum: если сложить его с предыдущими двумя, должен получиться ноль.
Наш kernel.asm станет таким:
Строим ядро
Теперь мы создадим объектные файлы из kernel.asm и kernel.c и свяжем их, используя наш скрипт.
Эта строка запустит ассемблер для создания объектного файла kasm.o в формате ELF-32.
Опция «-c» гарантирует, что после компиляции не произойдёт скрытого линкования.
Это запустит линковщик с нашим скриптом и создаст исполняемый файл, называющийся kernel.
Настраиваем grub и запускаем ядро
GRUB требует, чтобы имя ядра удовлетворяло шаблону kernel-<version> . Поэтому переименуйте ядро. Своё я назвал kernel-701.
Теперь поместите его в директорию /boot. Для этого понадобятся права суперпользователя.
В конфигурационном файле GRUB grub.cfg добавьте следующее:
Не забудьте убрать директиву hiddenmenu , если она есть.
Перезагрузите компьютер, и вы увидите список ядер с вашим в том числе. Выберите его, и вы увидите:
Русские Блоги
Написать операционную систему со мной (2) — самое простое ядро в истории
Последний разговаривал, мы представили процесс запуска компьютера и дали простой основной код записи загрузки. Этот код — это всего лишь несколько символов, и ничего не существует — начать ядро. Эта лекция, давайте сначала посмотрим на то, как начинается ядро, то напишите простое ядро, начните его с Grub с основной загрузкой, которая была реализована.
Как начать ядро
Предыдущая лекция мы сказали, что после того, как компьютер считывает «основную загрузку», после того, как машинный код «главной загрузки», установленная заранее загрузчик «Main Boot», будет установлена предварительная установка, которая является выбором пользователя, который Ядро начинается и будет загружен в ядро. Дайте контроль в ядре. GNU Grub (Grand Unified Bootloader — это загрузчик для удовлетворения спецификации MultiBoot, Grub выбирает различные ядра на разделах операционной системы, на следующем рисунке является графическим интерфейсом GRUB:

Рисунок Grub Интерфейс
Ядро, которое может быть запущено GRUB, необходимо соответствовать двум условиям:
(1) заголовок MultiBoot должен быть включен в первые 8K байты ядра;
(2) Ядро загружено более 1 МБ адреса памяти.
Так что же заголовок MultiBoot? Он должен содержать 3 домены 4 байта (есть другие небежные домены, мы не обсуждаем), следующим образом:
Эта статья не обсуждает реализацию GRUB, мы будем использовать прежние письменности, которые были написаны (автор даст ему), и мы должны выполнять завершение ядра стартапов Grub. Для того, чтобы завершить это ядро, Нам нужно написать небольшое количество сборки, чтобы присоединиться к заголовочному устройству MultiBoot в ядре, а затем запишите запись ядра с языком C и доработать целевой код Compilation и Code Code Code Completion для создания реального ядра. Отказ Давайте сделаем эти шаги, шаг за шагом!
Name already in use
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Пишем свою собственную операционную систему с нуля!
Идея написать ОС возникла у меня в процессе поиска идеи для сайд-проекта. Это исключительно хобби-проект, не рассчитанный на серьезность и достоверность, и хотя я пытался объяснить многие новые и неочевидные концепты, с которыми я столкнулся в процессе разработки, я мог что-то упустить, так как я сам только учусь — именно поэтому я настоятельно рекомендую пользоваться гуглом и любыми другими источниками информации когда вы познакомитесь с чем-то новым в гайде. Гуглите абсолютно всё. Я серьезно.
**Prerequisites: **Для комфортного прохождения гайда нужно уметь программировать на языке Си на базовом уровне (одно из обязательных требований: понимать принципы работы с указателями), иметь опыт разработки на высокоуровневых ЯП. С синтаксисом ассемблера можно ознакомиться по ссылке ниже, но все же рекомендую побольше почитать или посмотреть по нему туториалов.
Навигация по репозиторию
— гайд с последовательными уроками, теорией и задокументированным кодом
- Гайд разделен на главы, например 00-BOOT-SECTOR
- Главы разделены на упражнения, например ex00
- Упражнения содержат в себе код и теорию. Выглядят как main.asm
— исходный код ОС
Установка и запуск
- Установить эмулятор QEMU (подробнее: https://www.qemu.org/download/)
- Собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:
- Клонировать и собрать проект
- Запустить образ ОС с помощью эмулятора
Справочник по синтаксису ассемблера NASM
Ссылки на полезный материал которым я пользовался в качестве теории.
- Серия статей о ядре Linux и его внутреннем устройстве: https://github.com/proninyaroslav/linux-insides-ru
- Статья «Давай напишем ядро!»: https://xakep.ru/2018/06/18/lets-write-a-kernel/
- Небольшая книга по разработке собственной ОС (70 страниц): https://www.cs.bham.ac.uk/
About
Пишем свою собственную операционную систему с нуля (bootloader + tiny kernel)