Простейшая ос. Как написать операционную систему с нуля. Создание обновления безопасности

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


Привет, мир!

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

Как загружаются x86-машины

Прежде чем думать о том, как писать ядро, давай посмотрим, как компьютер загружается и передает управление ядру. Большинство регистров процессора x86 имеют определенные значения после загрузки. Регистр - указатель на инструкцию (EIP) содержит адрес инструкции, которая будет исполнена процессором. Его захардкоженное значение - это 0xFFFFFFF0. То есть x86-й процессор всегда будет начинать исполнение с физического адреса 0xFFFFFFF0. Это последние 16 байт 32-разрядного адресного пространства. Этот адрес называется «вектор сброса» (reset vector).

В карте памяти, которая содержится в чипсете, прописано, что адрес 0xFFFFFFF0 ссылается на определенную часть BIOS, а не на оперативную память. Однако BIOS копирует себя в оперативку для более быстрого доступа - этот процесс называется «шедоуинг» (shadowing), создание теневой копии. Так что адрес 0xFFFFFFF0 будет содержать только инструкцию перехода к тому месту в памяти, куда BIOS скопировала себя.

Итак, BIOS начинает исполняться. Сначала она ищет устройства, с которых можно загружаться в том порядке, который задан в настройках. Она проверяет носители на наличие «волшебного числа», которое отличает загрузочные диски от обычных: если байты 511 и 512 в первом секторе равны 0xAA55, значит, диск загрузочный.

Как только BIOS найдет загрузочное устройство, она скопирует содержимое первого сектора в оперативную память, начиная с адреса 0x7C00, а затем переведет исполнение на этот адрес и начнет исполнение того кода, который только что загрузила. Вот этот код и называется загрузчиком (bootloader).

Загрузчик загружает ядро по физическому адресу 0x100000. Именно он и используется большинством популярных ядер для x86.

Все процессоры, совместимые с x86, начинают свою работу в примитивном 16-разрядном режиме, которые называют «реальным режимом» (real mode). Загрузчик GRUB переключает процессор в 32-разрядный защищенный режим (protected mode), переводя нижний бит регистра CR0 в единицу. Поэтому ядро начинает загружаться уже в 32-битном защищенном режиме.

Заметь, что GRUB в случае с ядрами Linux выбирает соответствующий протокол загрузки и загружает ядро в реальном режиме. Ядра Linux сами переключаются в защищенный режим.

Что нам понадобится

  • Компьютер, совместимый с x86 (очевидно),
  • Linux,
  • ассемблер NASM,
  • ld (GNU Linker),
  • GRUB.

Входная точка на ассемблере

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

Как сделать так, чтобы ассемблерный код стал стартовой точкой для нашего ядра? Мы используем скрипт для компоновщика (linker), который линкует объектные файлы и создает финальный исполняемый файл ядра (подробнее объясню чуть ниже). В этом скрипте мы напрямую укажем, что хотим, чтобы наш бинарный файл загружался по адресу 0x100000. Это адрес, как я уже писал, по которому загрузчик ожидает увидеть входную точку в ядро.

Вот код на ассемблере.

kernel.asm
bits 32 section .text global start extern kmain start: cli mov esp, stack_space call kmain hlt section .bss resb 8192 stack_space:

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

Вторая строка начинает текстовую секцию, также известную как секция кода. Сюда пойдет весь наш код.

global - это еще одна директива NASM, она объявляет символы из нашего кода глобальными. Это позволит компоновщику найти символ start , который и служит нашей точкой входа.

kmain - это функция, которая будет определена в нашем файле kernel.c . extern объявляет, что функция декларирована где-то еще.

Далее идет функция start , которая вызывает kmain и останавливает процессор инструкцией hlt . Прерывания могут будить процессор после hlt , так что сначала мы отключаем прерывания инструкцией cli (clear interrupts).

В идеале мы должны выделить какое-то количество памяти под стек и направить на нее указатель стека (esp). GRUB, кажется, это и так делает за нас, и на этот момент указатель стека уже задан. Однако на всякий случай выделим немного памяти в секции BSS и направим указатель стека на ее начало. Мы используем инструкцию resb - она резервирует память, заданную в байтах. Затем оставляется метка, указывающая на край зарезервированного куска памяти. Прямо перед вызовом kmain указатель стека (esp) направляется на эту область инструкцией mov .

Ядро на C

В файле kernel.asm мы вызвали функцию kmain() . Так что в коде на C исполнение начнется с нее.

kernel.c
void kmain(void) { const char *str = "my first kernel"; char *vidptr = (char*)0xb8000; unsigned int i = 0; unsigned int j = 0; while(j < 80 * 25 * 2) { vidptr[j] = " "; vidptr = 0x07; j = j + 2; } j = 0; while(str[j] != "\0") { vidptr[i] = str[j]; vidptr = 0x07; ++j; i = i + 2; } return; }

Все, что будет делать наше ядро, - очищать экран и выводить строку my first kernel.

Первым делом мы создаем указатель vidptr, который указывает на адрес 0xb8000. В защищенном режиме это начало видеопамяти. Текстовая экранная память - это просто часть адресного пространства. Под экранный ввод-вывод выделен участок памяти, который начинается с адреса 0xb8000, - в него помещается 25 строк по 80 символов ASCII.

Каждый символ в текстовой памяти представлен 16 битами (2 байта), а не 8 битами (1 байтом), к которым мы привыкли. Первый байт - это код символа в ASCII, а второй байт - это attribute-byte . Это определение формата символа, в том числе - его цвет.

Чтобы вывести символ s зеленым по черному, нам нужно поместить s в первый байт видеопамяти, а значение 0x02 - во второй байт. 0 здесь означает черный фон, а 2 - зеленый цвет. Мы будем использовать светло-серый цвет, его код - 0x07.

В первом цикле while программа заполняет пустыми символами с атрибутом 0x07 все 25 строк по 80 символов. Это очистит экран.

Во втором цикле while символы строки my first kernel, оканчивающейся нулевым символом, записываются в видеопамять и каждый символ получает attribute-byte, равный 0x07. Это должно привести к выводу строки.

Компоновка

Теперь мы должны собрать kernel.asm в объектный файл с помощью NASM, а затем при помощи GCC скомпилировать kernel.c в другой объектный файл. Наша задача - слинковать эти объекты в исполняемое ядро, пригодное к загрузке. Для этого потребуется написать для компоновщика (ld) скрипт, который мы будем передавать в качестве аргумента.

link.ld
OUTPUT_FORMAT(elf32-i386) ENTRY(start) SECTIONS { . = 0x100000; .text: { *(.text) } .data: { *(.data) } .bss: { *(.bss) } }

Здесь мы сначала задаем формат (OUTPUT_FORMAT) нашего исполняемого файла как 32-битный ELF (Executable and Linkable Format), стандартный бинарный формат для Unix-образных систем для архитектуры x86.

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

SECTIONS - это самая важная для нас часть. Здесь мы определяем раскладку нашего исполняемого файла. Мы можем определить, как разные секции будут объединены и куда каждая из них будет помещена.

В фигурных скобках, которые идут за выражением SECTIONS , точка означает счетчик позиции (location counter). Он автоматически инициализируется значением 0x0 в начале блока SECTIONS , но его можно менять, назначая новое значение.

Ранее я уже писал, что код ядра должен начинаться по адресу 0x100000. Именно поэтому мы и присваиваем счетчику позиции значение 0x100000.

Взгляни на строку.text: { *(.text) } . Звездочкой здесь задается маска, под которую подходит любое название файла. Соответственно, выражение *(.text) означает все входные секции.text во всех входных файлах.

В результате компоновщик сольет все текстовые секции всех объектных файлов в текстовую секцию исполняемого файла и разместит по адресу, указанному в счетчике позиции. Секция кода нашего исполняемого файла будет начинаться по адресу 0x100000.

После того как компоновщик выдаст текстовую секцию, значение счетчика позиции будет 0x100000 плюс размер текстовой секции. Точно так же секции data и bss будут слиты и помещены по адресу, который задан счетчиком позиции.

GRUB и мультизагрузка

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

Существует стандарт для загрузки разных ядер x86 с помощью бутлоадера. Это называется «спецификация мультибута ». GRUB будет загружать только те ядра, которые ей соответствуют.

В соответствии с этой спецификацией ядро может содержать заголовок (Multiboot header) в первых 8 килобайтах. В этом заголовке должно быть прописано три поля:

  • magic - содержит «волшебное» число 0x1BADB002, по которому идентифицируется заголовок;
  • flags - это поле для нас не важно, можно оставить ноль;
  • checksum - контрольная сумма, должна дать ноль, если прибавить ее к полям magic и flags .

Наш файл kernel.asm теперь будет выглядеть следующим образом.

kernel.asm
bits 32 section .text ;multiboot spec align 4 dd 0x1BADB002 ;magic dd 0x00 ;flags dd - (0x1BADB002 + 0x00) ;checksum global start extern kmain start: cli mov esp, stack_space call kmain hlt section .bss resb 8192 stack_space:

Инструкция dd задает двойное слово размером 4 байта.

Собираем ядро

Итак, все готово для того, чтобы создать объектный файл из kernel.asm и kernel.c и слинковать их с применением нашего скрипта. Пишем в консоли:

$ nasm -f elf32 kernel.asm -o kasm.o

По этой команде ассемблер создаст файл kasm.o в формате ELF-32 bit. Теперь настал черед GCC:

$ gcc -m32 -c kernel.c -o kc.o

Параметр -c указывает на то, что файл после компиляции не нужно линковать. Мы это сделаем сами:

$ ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o

Эта команда запустит компоновщик с нашим скриптом и сгенерирует исполняемый файл под названием kernel .

WARNING

Хакингом ядра лучше всего заниматься в виртуалке. Чтобы запустить ядро в QEMU вместо GRUB, используй команду qemu-system-i386 -kernel kernel .

Настраиваем GRUB и запускаем ядро

GRUB требует, чтобы название файла с ядром следовало конвенции kernel-<версия> . Так что переименовываем файл - я назову свой kernel-701 .

Теперь кладем ядро в каталог /boot . На это понадобятся привилегии суперпользователя.

В конфигурационный файл GRUB grub.cfg нужно будет добавить что-то в таком роде:

Title myKernel root (hd0,0) kernel /boot/kernel-701 ro

Не забудь убрать директиву hiddenmenu, если она прописана.

GRUB 2

Чтобы запустить созданное нами ядро в GRUB 2, который по умолчанию поставляется в новых дистрибутивах, твой конфиг должен выглядеть следующим образом:

Menuentry "kernel 701" { set root="hd0,msdos1" multiboot /boot/kernel-701 ro }

Благодарю Рубена Лагуану за это дополнение.

Перезагружай компьютер, и ты должен будешь увидеть свое ядро в списке! А выбрав его, ты увидишь ту самую строку.



Это и есть твое ядро!

Пишем ядро с поддержкой клавиатуры и экрана

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

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

Работа с портами: чтение и вывод

read_port: mov edx, in al, dx ret write_port: mov edx, mov al, out dx, al ret

Доступ к портам ввода-вывода осуществляется при помощи инструкций in и out , входящих в набор x86.

В read_port номер порта передается в качестве аргумента. Когда компилятор вызывает функцию, он кладет все аргументы в стек. Аргумент копируется в регистр edx при помощи указателя на стек. Регистр dx - это нижние 16 бит регистра edx . Инструкция in здесь читает порт, номер которого задан в dx , и кладет результат в al . Регистр al - это нижние 8 бит регистра eax . Возможно, ты помнишь из институтского курса, что значения, возвращаемые функциями, передаются через регистр eax . Таким образом, read_port позволяет нам читать из портов ввода-вывода.

Функция write_port работает схожим образом. Мы принимаем два аргумента: номер порта и данные, которые будут записаны. Инструкция out пишет данные в порт.

Прерывания

Теперь, прежде чем мы вернемся к написанию драйвера, нам нужно понять, как процессор узнает, что какое-то из устройств выполнило операцию.

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

За прерывания в архитектуре x86 отвечает чип под названием Programmable Interrupt Controller (PIC). Он обрабатывает хардверные прерывания и направляет и превращает их в соответствующие системные прерывания.

Когда пользователь что-то делает с устройством, чипу PIC отправляется импульс, называемый запросом на прерывание (Interrupt Request, IRQ). PIC переводит полученное прерывание в системное прерывание и отправляет процессору сообщение о том, что пора остановить то, что он делает. Дальнейшая обработка прерываний - это задача ядра.

Без PIC нам бы пришлось опрашивать все устройства, присутствующие в системе, чтобы посмотреть, не произошло ли событие с участием какого-то из них.

Давай разберем, как это работает в случае с клавиатурой. Клавиатура висит на портах 0x60 и 0x64. Порт 0x60 отдает данные (когда нажата какая-то кнопка), а порт 0x64 передает статус. Однако нам нужно знать, когда конкретно читать эти порты.

Прерывания здесь приходятся как нельзя более кстати. Когда кнопка нажата, клавиатура отправляет PIC сигнал по линии прерываний IRQ1. PIС хранит значение offset , сохраненное во время его инициализации. Он добавляет номер входной линии к этому отступу, чтобы сформировать вектор прерывания. Затем процессор ищет структуру данных, называемую «таблица векторов прерываний» (Interrupt Descriptor Table, IDT), чтобы дать функции - обработчику прерывания адрес, соответствующий его номеру.

Затем код по этому адресу исполняется и обрабатывает прерывание.

Задаем IDT

struct IDT_entry{ unsigned short int offset_lowerbits; unsigned short int selector; unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits; }; struct IDT_entry IDT; void idt_init(void) { unsigned long keyboard_address; unsigned long idt_address; unsigned long idt_ptr; keyboard_address = (unsigned long)keyboard_handler; IDT.offset_lowerbits = keyboard_address & 0xffff; IDT.selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */ IDT.zero = 0; IDT.type_attr = 0x8e; /* INTERRUPT_GATE */ IDT.offset_higherbits = (keyboard_address & 0xffff0000) >> 16; write_port(0x20 , 0x11); write_port(0xA0 , 0x11); write_port(0x21 , 0x20); write_port(0xA1 , 0x28); write_port(0x21 , 0x00); write_port(0xA1 , 0x00); write_port(0x21 , 0x01); write_port(0xA1 , 0x01); write_port(0x21 , 0xff); write_port(0xA1 , 0xff); idt_address = (unsigned long)IDT ; idt_ptr = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16); idt_ptr = idt_address >> 16 ; load_idt(idt_ptr); }

IDT - это массив, объединяющий структуры IDT_entry. Мы еще обсудим привязку клавиатурного прерывания к обработчику, а сейчас посмотрим, как работает PIC.

Современные системы x86 имеют два чипа PIC, у каждого восемь входных линий. Будем называть их PIC1 и PIC2. PIC1 получает от IRQ0 до IRQ7, а PIC2 - от IRQ8 до IRQ15. PIC1 использует порт 0x20 для команд и 0x21 для данных, а PIC2 - порт 0xA0 для команд и 0xA1 для данных.

Оба PIC инициализируются восьмибитными словами, которые называются «командные слова инициализации» (Initialization command words, ICW).

В защищенном режиме обоим PIC первым делом нужно отдать команду инициализации ICW1 (0x11). Она сообщает PIC, что нужно ждать еще трех инициализационных слов, которые придут на порт данных.

Эти команды передадут PIC:

  • вектор отступа (ICW2),
  • какие между PIC отношения master/slave (ICW3),
  • дополнительную информацию об окружении (ICW4).

Вторая команда инициализации (ICW2) тоже шлется на вход каждого PIC. Она назначает offset , то есть значение, к которому мы добавляем номер линии, чтобы получить номер прерывания.

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

ICW4 задает дополнительные параметры окружения. Нам нужно определить только нижний бит, чтобы PIC знали, что мы работаем в режиме 80x86.

Та-дам! Теперь PIC проинициализированы.

У каждого PIC есть внутренний восьмибитный регистр, который называется «регистр масок прерываний» (Interrupt Mask Register, IMR). В нем хранится битовая карта линий IRQ, которые идут в PIC. Если бит задан, PIC игнорирует запрос. Это значит, что мы можем включить или выключить определенную линию IRQ, выставив соответствующее значение в 0 или 1.

Чтение из порта данных возвращает значение в регистре IMR, а запись - меняет регистр. В нашем коде после инициализации PIC мы выставляем все биты в единицу, чем деактивируем все линии IRQ. Позднее мы активируем линии, которые соответствуют клавиатурным прерываниям. Но для начала все же выключим!

Если линии IRQ работают, наши PIC могут получать сигналы по IRQ и преобразовывать их в номер прерывания, добавляя офсет. Нам же нужно заполнить IDT таким образом, чтобы номер прерывания, пришедшего с клавиатуры, соответствовал адресу функции-обработчика, которую мы напишем.

На какой номер прерывания нам нужно завязать в IDT обработчик клавиатуры?

Клавиатура использует IRQ1. Это входная линия 1, ее обрабатывает PIC1. Мы проинициализировали PIC1 с офсетом 0x20 (см. ICW2). Чтобы получить номер прерывания, нужно сложить 1 и 0x20, получится 0x21. Значит, адрес обработчика клавиатуры будет завязан в IDT на прерывание 0x21.

Задача сводится к тому, чтобы заполнить IDT для прерывания 0x21. Мы замапим это прерывание на функцию keyboard_handler , которую напишем в ассемблерном файле.

Каждая запись в IDT состоит из 64 бит. В записи, соответствующей прерыванию, мы не сохраняем адрес функции-обработчика целиком. Вместо этого мы разбиваем его на две части по 16 бит. Нижние биты сохраняются в первых 16 битах записи в IDT, а старшие 16 бит - в последних 16 битах записи. Все это сделано для совместимости с 286-ми процессорами. Как видишь, Intel выделывает такие номера на регулярной основе и во многих-многих местах!

В записи IDT нам осталось прописать тип, обозначив таким образом, что все это делается, чтобы отловить прерывание. Еще нам нужно задать офсет сегмента кода ядра. GRUB задает GDT за нас. Каждая запись GDT имеет длину 8 байт, где дескриптор кода ядра - это второй сегмент, так что его офсет составит 0x08 (подробности не влезут в эту статью). Гейт прерывания представлен как 0x8e. Оставшиеся в середине 8 бит заполняем нулями. Таким образом, мы заполним запись IDT, которая соответствует клавиатурному прерыванию.

Когда с маппингом IDT будет покончено, нам надо будет сообщить процессору, где находится IDT. Для этого существует ассемблерная инструкция lidt, она принимает один операнд. Им служит указатель на дескриптор структуры, которая описывает IDT.

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

В переменной idr_ptr у нас есть указатель, который мы передаем инструкции lidt в функции load_idt() .

Load_idt: mov edx, lidt sti ret

Дополнительно функция load_idt() возвращает прерывание при использовании инструкции sti .

Заполнив и загрузив IDT, мы можем обратиться к IRQ клавиатуры, используя маску прерывания, о которой мы говорили ранее.

Void kb_init(void) { write_port(0x21 , 0xFD); }

0xFD - это 11111101 - включаем только IRQ1 (клавиатуру).

Функция - обработчик прерывания клавиатуры

Итак, мы успешно привязали прерывания клавиатуры к функции keyboard_handler , создав запись IDT для прерывания 0x21. Эта функция будет вызываться каждый раз, когда ты нажимаешь на какую-нибудь кнопку.

Keyboard_handler: call keyboard_handler_main iretd

Эта функция вызывает другую функцию, написанную на C, и возвращает управление при помощи инструкций класса iret. Мы могли бы тут написать весь наш обработчик, но на C кодить значительно легче, так что перекатываемся туда. Инструкции iret/iretd нужно использовать вместо ret , когда управление возвращается из функции, обрабатывающей прерывание, в программу, выполнение которой было им прервано. Этот класс инструкций поднимает флаговый регистр, который попадает в стек при вызове прерывания.

Void keyboard_handler_main(void) { unsigned char status; char keycode; /* Пишем EOI */ write_port(0x20, 0x20); status = read_port(KEYBOARD_STATUS_PORT); /* Нижний бит статуса будет выставлен, если буфер не пуст */ if (status & 0x01) { keycode = read_port(KEYBOARD_DATA_PORT); if(keycode < 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

Здесь мы сначала даем сигнал EOI (End Of Interrupt, окончание обработки прерывания), записав его в командный порт PIC. Только после этого PIC разрешит дальнейшие запросы на прерывание. Нам нужно читать два порта: порт данных 0x60 и порт команд (он же status port) 0x64.

Первым делом читаем порт 0x64, чтобы получить статус. Если нижний бит статуса - это ноль, значит, буфер пуст и данных для чтения нет. В других случаях мы можем читать порт данных 0x60. Он будет выдавать нам код нажатой клавиши. Каждый код соответствует одной кнопке. Мы используем простой массив символов, заданный в файле keyboard_map.h , чтобы привязать коды к соответствующим символам. Затем символ выводится на экран при помощи той же техники, что мы применяли в первой версии ядра.

Чтобы не усложнять код, я здесь обрабатываю только строчные буквы от a до z и цифры от 0 до 9. Ты с легкостью можешь добавить спецсимволы, Alt, Shift и Caps Lock. Узнать, что клавиша была нажата или отпущена, можно из вывода командного порта и выполнять соответствующее действие. Точно так же можешь привязать любые сочетания клавиш к специальным функциям вроде выключения.

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

Аббревиатура "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 использовался планировщиком при изменении состояния потоков. Этот лок был удален, и состояние потока "ожидание" разделилось на несколько:
    • Ожидание: В процессе
    • Ожидание: Завершено
    • Ожидание: Отменено
    PFN 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.

  • Вперёд >

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

рТЕДЩДХЭЙЕ ЧЩРХУЛЙ НПЗМЙ ВЩФШ ОЕУЛПМШЛП ЪБРХФБООЩНЙ. оБЮБМШОБС ЪБЗТХЪЛБ, 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] . й ДБ, УЕКЮБУ УБНПЕ ЧТЕНС ДМС ТБЪМЙЮОЩИ ЙДЕК РП ЛПОГЕРГЙЙ пу Й РТЕДМПЦЕОЙК.

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

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

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

Автор Vulf gamer задал вопрос в разделе Другие языки и технологии

Как создать свою ОС? и получил лучший ответ

Ответ от Александр Багров[гуру]
Идея похвальная.
Прежде всего нужно знать систему команд машины, для которой намереваешься писать ОС.
Система команд находит свое прямое отражение в языке ассемблера.
Поэтому в первую очередь нужно придумать свой язык ассемблера и написать для него программу (ассемблер) , транслирующий буквенно-цифровую символику в машинный
код.
Если интересно, то можно посмотреть, какими бы требования должна обладать новая (идеальная) ОС.
Некоторые такие черты перечислены тут:
.ru/D_OS/OS-PolyM.html#IdealOS
Необходимо изучать материалы сайтов-разработчиков микропроцессоров. Например, Intel и AMD.
Возможно, тебе будет полезен видео-курс лекций по ОС, который представлен здесь:
.ru/D_OS/OS_General.html
PS: Не слушай пессимистов. Исходи из идеологии петуха, гонящегося за курицей:
"Не догоню, хоть разогреюсь. "
Источник: Сайт "Используй ПК правильно! "

Ответ от 2 ответа [гуру]

Привет! Вот подборка тем с ответами на Ваш вопрос: Как создать свою ОС?

Ответ от Вадим Хпрламов [новичек]
Конечно) Тут-же одни гейтсы сидят) На майкрософте спроси)



Ответ от Ирина стародубцева [новичек]
возьми все ОС и в одну запихай


Ответ от Александр Тунцов [гуру]
Ознакомься с ОС Linux, обучись программированию и в путь


Ответ от ~In Flames~ [гуру]
Программирование учить на высшем уровне, собрать целую толпу таких же компьютерных гениев и тогда уже можно делать.


Ответ от Rasul Magomedov [гуру]
Начни с создания нескучных обоев


Ответ от Капитан Гугл [гуру]
Про "10 лет на изучение основ" - не слушай, Торвальдс первую версию Линукса написал в 22 года, а компьютер у него в 12 появился. Как ты понимаешь, он не только основы изучал.
Начни с изучения уже существующего - с одной стороны, "Современные операционные системы" Танненбаума, с другой стороны - собери Linux From Scratch, с третьей - учи Ассемблер, C, C++. За все про все - можно в 3-4 года уложиться. После этого можешь приступать к разработке своей системы... если еще захочешь.


Ответ от Ёаня Семенов [гуру]
знаешь как делал Гейтс? попробуй так же, говорят прибыльно получается..
когда его наказали родители, он от нечего делать стал прыгать попой на клаве, потом продал назвав то что получилось " windows "
п с а если реально то напиши сначала "Hello World" в С++ и сразу поймешь что идея параноидальная


Ответ от Kostafey [гуру]
А зачем? Чем принципиально не устраивают существующие? Неужели нет ни одной, хоть частично удовлетворяющей вашим требованиям к ОС? Может стоит лучше присоединиться к команде разработчиков? Толку в 100500 раз больше будет.
И потом, вы забросите эту идею еще на 0,(0)1% ее реализации.


Ответ от Евгений Ломега [гуру]
Э. Таненбаум "Операционные системы: разработка и реализация "
удачи
PS К сожалению как это делал Бил Гейтс у тебя вряд ли получится. У него мама крутая банкирша, у тебя?


Ответ от Krab Bark [гуру]
Написать простейшую ОС самому можно, но она никак не сможет конкурировать с ОС вроде Windows, MAC OS или Linux, над которыми минимум десяток лет трудились сотни или тысячи программистов. Кроме того, ОС - только фундамент. Нужно, чтобы разработчики оборудования писали для этой ОС свои драйверы, разработчики прикладных программ писали для нее редакторы, плееры, браузеры, игры, черта в ступе... А без этого ОС останется никому не нужным фундаментом для дома, который никто строить не будет.


Ответ от Вадим Стаханов [активный]
Лучше бы на филолога пошел бы учится. А потом бы кричал "Свободная касса! "


Ответ от =Serge= [гуру]
Ура! Наконец то 58 вопрос на сайте про создание "своей" ОС))
Вот вопросы про "написать свою ОС" - их только 34))
Читай....92 вопроса*10 ответов = приблизительно 920 ответов))
Заодно возможно поймешь что значат "нескучные обои")).


Ответ от Irreproducible [гуру]
еще один Денис Попов с очередным BolgenOS"ом?


Ответ от Иван татарчук [новичек]
запусти блокнот скачай компилятор жабаскрипт и начни прыгать попой по клавиатуре
через 60мин компилируй и все
твоя оска готова


Ответ от Овечка Мила [новичек]
ОС? Какая именно? ОС-орижинал честер (оригинальный персонаж (в переводе))
Она нужна доя изображения себя мультиках или фильмах.
1. Придумай какого именно мультика/фильма ты хочешь ОС
2. Рассмотрим стиль мультика/фильма
3. Кем будет твой персонаж (фея, пони, маг, робот и т. п.)
4. Опиши мысленно его, потом на бумаге
5. Придумай дизайн
6. Придумай имя и био
7. Нарисуй персонажа!
8. Теперь за дело с Пэинт Туд Саи