Читая Хабр в течении последних двух лет, я видел только несколько попыток разработки ОС (если конкретно: от пользователей и (отложено на неопределённый срок) и (не заброшено, но пока больше походит на описание работы защищённого режима 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 поддержки

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

Можно посоветовать следующее:
- само собой разумеющееся, отладочный вывод
- assert с немедленным выходом в «отладчик» (см. следующий пункт)
- некоторое подобие консольного отладчика
- проверить не позволяет ли эмулятор подключать отладчик, таблицы символов или ещё что-нибудь
- часть стандартных отладочных операций: точки останова, стек вызовов, вывод значений, печать дампа, ...
- команды вывода различной полезной информации, вроде очереди исполнения планировщика или различной статистики (она не так бесполезно как может показаться сначала)
- команды проверки непротиворечивости состояния различных структур: списков свободной/занятой памяти, heap или очереди сообщений
Развитие

Подобие SDK

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

Что нужно знать, чтобы написать операционную систему
Создание операционной системы - одна из сложнейших задач в программировании, поскольку требует обширных и комплексных знаний о работе компьютера. Каких именно? Разбираемся ниже.
Что такое ОС
Операционная система (ОС) - это программное обеспечение, которое работает с компьютерным железом и его ресурсами и является мостом между аппаратной и программной частью компьютера.
Компьютеры первого поколения не имели операционных систем. Программы на первых ЭВМ включали в себя код для непосредственной работы системы, связи с периферийными устройствами и вычислений, для выполнения которых эта программа и писалась. Из-за такого расклада даже простые по логике работы программы были сложны в программной реализации.
По мере того как компьютеры становились более разнообразными и сложными, писать программы, которые работали и как ОС, и как приложение, стало попросту неудобно. Поэтому, чтобы программы было легче писать, владельцы компьютеров начали разрабатывать программное обеспечение. Так и появились операционные системы.
ОС предоставляет всё необходимое для работы пользовательских программ. Их появление означало, что теперь программам не нужно контролировать весь объём работ компьютера (это отличный пример инкапсуляции). Теперь программам нужно было работать именно с операционной системой, а система уже сама заботилась о ресурсах и работе с периферией (клавиатура, принтер).
Кратко об истории операционных систем
Язык Cи
Как уже упоминалось выше, для написания ОС есть несколько высокоуровневых языков программирования. Однако самый популярный из них - Си.
Начать изучать этот язык можно отсюда . Этот ресурс ознакомит вас с базовыми понятиями и подготовит к более сложным задачам.
«Learn C the Hard Way » - название ещё одной книги. Кроме привычной теории в ней собрано много практических решений. Этот учебник расскажет обо всех аспектах языка.
Либо же можете выбрать одну из этих книг:
- «The C Programming Language » Кернигхана и Ритчи;
- «C Programming Absolute Beginner’s Guide » Пэрри и Миллера.
Разработка ОС
После освоения всего необходимого, что касается информатики, языка ассемблера и Cи, вам стоит прочесть хотя бы одну или две книги про непосредственную разработку ОС. Вот несколько ресурсов для этого:
«Linux From Scratch ». Здесь рассматривается процесс сборки операционной системы Linux (учебник переведён на много языков, в том числе и на русский). Тут, как и в остальных учебниках, вам предоставят все необходимые базовые знания. Полагаясь на них можно попробовать себя в создании ОС. Чтобы сделать программную часть ОС более профессиональной, присутствуют дополнения к учебнику: «
Оригинал: 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 или устанавливает флаг переноса, если файл с заданным именем не найден.
Сразу говорю, не закрывайте статью с мыслями «Блин, еще один Попов». У него всего-то слизанная Ubuntu, а у меня все с нуля, включая ядро и приложения. Итак, продолжение под катом.
Группа ОС: вот .
Сначала я кину вам один скриншот.
Больше их нет, а теперь поподробнее о том, зачем я ее пишу.
Был теплый апрельский вечер, четверг. Я еще с детства мечтал написать ОС, как вдруг подумал: «Я же теперь знаю плюсы и асм, чего бы не воплотить мою мечту?». Загуглил сайты по этой тематике и нашел статью с Хабра: "Как начать и не бросить писать ОС ". Спасибо ее автору за ссылку на OSDev Wiki внизу. Я зашел туда и начал работу. Там были в одной статье все данные о минимальной ОС. Я начал собирать кросс-gcc и binutils, а потом все переписал оттуда. Видели вы бы мою радость, когда я увидел надпись «Hello, kernel World!» Я прямо со стула подпрыгнул и понял - я не сдамся. Я написал «консоль» (в кавычках, у меня не было доступа к клавиатуре), но потом решил написать оконную систему. В итоге она заработала, но доступа к клавиатуре у меня не было. А потом я решил придумать название, опираясь на X Window System. Загуглил Y Window System - она есть. В итоге назвал Z Window System 0.1, входящая в OS365 pre-alpha 0.1. И да, ее не видел никто, кроме меня самого. Потом я понял, как реализовать поддержку клавиатуры. Скрин самой первой версии, когда еще не было ничего, даже оконной системы:
В ней даже не двигался курсор текста, как вы видите. Затем я написал парочку простых приложений на основе Z. И вот релиз 1.0.0 alpha. Там были много вещей, даже меню системы. А файловый менеджер и калькулятор просто не работали.
Меня прямо терроризировал друг, которому важны одни красивости (Митрофан, сорри). Говорил «Запили VBE-режим 1024*768*32, запили, запили! Ну запили!». Ну я уже устал его выслушивать и все-таки запилил его. О реализации ниже.
Я сделал все моим загрузчиком, а именно GRUB"ом. С его помощью можно задать графический режим без осложнений путем добавления нескольких магических строчек в заголовок Multiboot.
Set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set GRAPH, 1<<2
.set FLAGS, ALIGN | MEMINFO | GRAPH
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.long 0, 0, 0, 0, 0
.long 0 # 0 = set graphics mode
.long 1024, 768, 32 # Width, height, depth
А потом из структуры информации Multiboot я беру адрес фреймбуфера и разрешение экрана и пишу туда пиксели. VESA все сделали очень замороченно - цвета RGB надо вводить в обратном порядке (не R G B, а B G R). Я несколько дней не понимал - почему пиксели не выводятся!? В итоге я понял, что забыл поменять значения 16 цветовых констант с 0...15 на их RGB-эквиваленты. В итоге релизнул, заодно запилил градиентный фон. Потом я сделал консоль, 2 приложения и релизнул 1.2. Ах да, чуть не забыл - скачать ОС можно на
Аббревиатура "NT" маркетингом расшифровывается как "New Technologies", но в проектной документации, она означала совсем другое. Дело в том, что Windows NT разрабатывалась для нового, еще не выпущенного в 1988-м году, процессора Intel i860. Его кодовое название было "N10" (N T
en).
Первая версия - Windows NT 3.1, вышла через 5 лет, в 1993 году. На этот момент в команде было уже 250 разработчиков.
Windows сегодня
- 1 миллиард пользователей
- 140 миллионов строк кода (включая тестовый код и инструментарий)
Код Windows очень разный. Какие-то части написаны 20 лет назад, какие-то появились только в текущей версии. Например, код Web Services on Devices (WSD) в Windows Vista существует в своей первой версии, код GDI находится на завершающей стадии своего развития и почти не изменяется, код DirectX уже хорошо разработан, но активно изменяется и в настоящее время. - 8000 разработчиков
- 36 языков локализации
- 20 лет разработки
Разработка Windows
20-30 лет назад использовалась только одна методология программирования "Водопад". Она представляет собой последовательность:
Спецификации → Дизайн → Реализация → Тестирование → Поставка.
Но такая методология работает только для небольших проектов. Для такого продукта, как Windows сегодня, нужны другие методологии:
- Product Cycle Model
- Team Software Process
- "Экстремальное программирование"
У всех этих методологий есть и преимущества и недостатки. В зависимости от размера команды и этапа развития компонента разные группы разработчиков Windows применяют разные методологии разработки.
Для Windows, как продукта в целом, используется Product Cycle Model:
- Периоды по 3-4 месяца
- Внутри периода - "водопад"
Самая главная проблема в разработке продукта такого масштаба состоит в том, что разработка требует времени. На начальном этапе решаются те проблемы, которые существуют в текущем времени и существующими средствами. Но единственная вещь, которая постоянна, это то, что все изменится. За годы разработки:
- Требования изменятся
- Возможности изменятся
- График работ изменится
- Проект изменится
- Пользователи изменятся
Несмотря на то, что разные команды ведут разработку по-разному, существуют "универсальные" правила:
- Выпуск промежуточных версий (milestones, beta, CTP) для широких масс тестеров
- Выпуск внутренних сборок с короткими циклами (1 сутки)
- Простота и надежность дизайна
- Личные и командные вычитывания кода
- Unit-тесты
- Верификационные тесты (Build Verification Tests)
- Любая промежуточная сборка должна быть качественной (то, что написано, должно работать)
От себя отмечу, что за месяц работы с Windows 7 build 6801 в качестве основной ОС на домашнем компьютере, у меня сформировалось положительное впечатление об этой сборки.
Весь процесс разработки Windows построен вокруг ежедневной сборки:
- Это пульс продукта
- Разработка никогда не прекращается
- Ежедневное автоматическое тестирование
- Интеграция на ранней стадии
- Ответственность разработчиков
- Очевидное состояние продукта
Когда-то раньше была только одна ветка исходного кода, и все разработчики вносили изменения прямо в неё. Сейчас команда разработчиков настолько большая, что это не работает. Поддерживается множество веток, среди которых есть основная - WinMain. У каждой лаборатории есть своя локальная ветка разработки, в которую интегрируются изменения. Проверенные изменения со временем интегрируются в WinMain.
Ежедневный цикл разработки:
- 15:00 - Допущенные к интеграции изменения в систему контроля исходного кода
- Сборка 6 версий (Free/Checked - x86, x64, IA64)
- 18:00 - Новые версии доступны для тестирования
- Новая версия устанавливается на несколько тысяч рабочих станций и серверов для тестирования
- Автоматизированный стресс-тест
- 05:00 - Протоколы тестов анализируются, сбои диагностируются
- 09:00 - Сводные отчеты автоматически рассылаются командам
- 09:30 - Сводное совещание руководителей команд для определения целей
Все участники проекта, включая самых высокопоставленных руководителей, используют промежуточные версии на своих рабочих (а обычно и домашних) компьютерах.
На чем пишется Windows?
- C, C++, C#, Ассемблер (x86, x64, IA64)
Ассемблеры применяются в довольно ограниченном объеме в тех ситуациях, когда без этого не обойтись - Visual Studio, Source Insight, build, nmake
- Source Depot - система контроля исходных текстов
- WinDbg, KD, NTSD - отладчики
Многие внутренние инструменты, такие как build, можно скачать с microsoft.com/whdc/devtools.
Изменения ядра Windows 7
Ядро Windows 7 претерпело следующие изменения:
- Рефакторинг
Почему в Windows нельзя удалить графическую подсистему?
Ответ на этот вопрос с технической точки зрения состоит в том, что графическая подсистема в Windows не самостоятельна, это часть подсистемы Win32.
В Windows 7 произошел рефакторинг многих низкоуровневых компонентов для того, чтобы разбить зависимости. Пользователям это не будет заметно, появятся только новые Dll, например kernel32.dll разделилась на kernel32.dll и kernelbase.dll.
Это разбиение дало возможность выделить минимальное ядро, называемое MinWin (20 мегабайт на диске). - Поддержка EFI для x86 и x64 (как в Vista SP1)
Многие производители пытаются избавиться от BIOS в пользу EFI. - Загрузка с VHD (виртуальный жесткий диск)
- Параллельная инициализация устройств и старт сервисов
При загрузке Windows довольно длительное время занимает построение дерева устройств. PNP-менеджер должен опрашивать драйверы шин (PCI, USB, FireWire и др.) на предмет того, какие устройства на них есть. И большую часть времени процессор ждет, пока устройства ответят (или нет). Ведь для того, чтобы определить устройства на шине нужно их опросить. Если они есть, то они ответят, а если нет, то приходится ждать, и процессор простаивает. Параллельное выполнение этих задач сокращает время загрузки. - Удаление Dispatcher lock из планировщика и PFN lock из менеджера памяти
Последние несколько лет тактовые частоты процессоров не растут, и развитие идет в сторону увеличения кол-ва параллельно выполняющихся инструкций как на уровне одного ядра, так и на уровне системы (multicore). В связи с этим, была проведена большая работа по улучшению масштабирования.
Два самых "горячих" лока, которые были в ядре, это Dispatcher lock и PFN lock были удалены.
Dispatcher lock использовался планировщиком при изменении состояния потоков. Этот лок был удален, и состояние потока "ожидание" разделилось на несколько:- Ожидание: В процессе
- Ожидание: Завершено
- Ожидание: Отменено
- Поддержка 256 логических процессоров
Раньше в Windows в качестве affinity mask использовалось машинное слово. Это было сделано из-за того, что так было легко находить свободные процессоры - каждый бит представляет собой процессор. Соответственно, в 32-битной системе поддерживалось 32 логических процессора, а в 64-битной - 64.
В Windows 7 в результате перехода на сегментную модель affinity mask стала возможна поддержка 256 логических процессоров. Процессоры стали группироваться в группы/сегменты. В каждой группе могут находиться до 64-х процессоров. В результате получается обратная совместимость, старые программы "видят" только процессоры в одной группе, а новые программы, использующие новые интерфейсы, работают со всеми процессорами в системе. - Улучшенное энергосбережение: отключение процессорных сокетовСегодня стоит серьезная проблема энергосбережения не только перед владельцами ноутбуков, но и владельцами датацентров. В США 2% электроэнергии потребляются компьютерными датацентрами. Многие из них выключают часть своих серверов на время низкой активности пользователей (выходные дни).
Было выяснено, что гораздо выгоднее отключать весь процессорный сокет, чем по одному ядру на нескольких, т.к. в этом случае можно отключить и всю инфраструктуру поддержки сокета (контроллер памяти).
Сопровождение Windows, обновления
Раньше обновления зачастую были кумулятивными(накапливаемыми). Это означало, что если ошибочный код содержался в раннем обновлении компонента, то и поздние версии будут содержать этот код. Но не всем пользователям нужны все обновления, у них разная конфигурация.
Теперь после выпуска (RTM) в Windows существует 2 версии исходного кода:
- RTM GDR (General Distribution Release)
Включает те немногие изменения, которые предназначены для всех. В основном исправления безопасности. - RTM LDR (Limited Distribution Release)
Во время установки обновления клиент Windows Update выбирает нужную ему ветку и устанавливает код из нее.
Создание обновления безопасности
Работа по созданию обновления безопасности начинается с обнаружения уязвимости. Есть масса разных способов обнаружения - внутренние команды безопасности, партнеры безопасности, разработчики. Когда уязвимость обнаружена, начинается 2 параллельных процесса:
- Разработка исправления для всех платформ
- Поиск "вариантов"
Масштабный поиск похожих вариантов уязвимостей на всех платформах. Поиск не идентичного кода, а похожего.
После разработки исправления, начинаются проверки его кода. Когда они завершатся, исправление интегрируется в сборку, и сборка отправляется на тестирование:
- Ручное и автоматическое тестирование компонент
- Автоматическое тестирование искажений форматов файлов, сетевых компонент и т.п. (больше миллиона вариантов)
- Тестирование системы в целом, включая тестирование обратной совместимости
Только исправления, удовлетворяющие всем критериям качества, допускаются к выпуску на Windows Update и Download Center.
- Вперёд >