Школа ассемблера: разработка операционной системы. Полет «Колибри». На что способна ОС, целиком написанная на ассемблере Пишем ос на ассемблере

Оригинал: 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 или устанавливает флаг переноса, если файл с заданным именем не найден.

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

Хочется сразу сказать, что я люблю теорию в сочетании с практикой, поэтому начнем.

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

Сначала сделаем загрузчик для USB-флешки!

Внимание!!! Первая наша ассемблерная программа будет работать как для флешки так и для других устройств таких как Floppy - диск или Жесткий диск. Впоследствии, чтобы все примеры работали корректно, я буду приводить ряд уточнений относительно работы кода на разных устройствах.

Писать мы будем на Fasm , так как именно он считается наилучшим компилятором для написания загрузчиков коим является MBR. Вторая причина выбора Fasm заключается в том, что он очень упрощает процесс компиляции файлов. Никаких директив командной строки и т.п. ерунды, которая может напрочь отбить охоту изучать ассемблер и достигать поставленных целей.Итак, на начальном этапе нам понадобятся две программы и какая-нибудь ненужная флешка минимального размера. Я нарыл у себя 1Gb (быстро форматируется, да и не жалко, если что). После работы нашего загрузчика флешка перестанет функционировать нормально. моя Windows 7 отказывается форматировать флешку. Возвращать флешку к жизни советую утилитой HP USB Disk Storage Format Tool (HPUSBFW.EXE) или другими утилитами для форматирования флешек.

Устанавливаем их и выкидываем соответствующие ярлыки на рабочий стол или куда вам нравится.

Подготовка окончена переходим к действиям

Открываем Fasmw.exe и пишем туда следующее. Мы набросаем самый минимум кода для того чтобы увидеть результат. Позже мы разберем то, что тут накалякано. В кратце даю комментарии.

Код FASM: ============= boot.asm ==============

org 7C00h ; адреса нашей программы расчитываются с учетом данной директивы

use16 ; генерируется шестнадцатиричный код

cli ;запрещаем прерывания для смены адресов в сегментных регистрах

mov ax, 0

mov sp, 7C00h

sti ;разрешаем прерывания (после изменения адресов)

mov ax, 0003h ;установка видео режима для вывода строки на экран

int 10h

mov ax, 1301h ;собственно вывод строки функция 13h int 10h (позже будет подробнее)

mov bp, stroka ;адрес выводимой строки

mov dx, 0000h ;строка и колонка в которой выводится текст

mov cx, 15 ;количество символов выводимой строки

mov bx, 000eh ;00-номер видео страницы (лучше не трогать) 0e-атрибуты символа(цвет,фон)

int 10h

jmp $ ;топтаться на месте (зацикливает программу на этой точке)

stroka db "Ok, MBR loaded!"

times 510 - ($ - $$) db 0 ;заполнение нулями промежутка между предыдущим байтом и пос-

db 0x55 ,0xAA ;ледними двумя байтами

Скомпилируйте этот код (Ctrl+F9) в fasm"e и сохраните получившийся бинарный файл как boot.bin в какое-нибудь удобное место. Прежде чем записать наш бинарник на флешку немного теории.

Когда вы воткнули флешку в компьютер, для системы BIOS абсолютно не очевидно, что вы хотите загрузиться с флешки поэтому в настройках BIOS"а нужно выбрать устройство, с которого вы хотите грузиться. Итак мы выбрали загрузку с USB (как это сделать вам придется разобраться самим, поскольку интерфейс биоса имеет различные вариации... можете погуглить настройки BIOS для вашей материнской платы. Там ничего сложного нет, как правило).

Теперь когда BIOS знает, что вы хотите грузиться именно с флешки, он должен убедиться что нулевой сектор на флешке является загрузочным. Для этого BIOS просматривает последние два байта нулевого сектора и, если они равны 0x55 0xAA, то только тогда он будет загружен в оперативную память. Иначе, BIOS просто пройдет мимо вашей флешки. Найдя эти два волшебных байта, он грузит нулевой сектор в оперативную память по адресу 0000:7С00h, а после забывает про флешку и передает управление на этот адрес. Теперь вся власть над компьютером принадлежит вашему загрузчику и он, действуя уже из оперативной памяти, может загружать с флешки дополнительный код. Сейчас мы посмотрим как этот самый сектор выглядит в программе DMDE.

1.Вставьте вашу флешку в компьютер и убедитесь, что на ней нет нужной вам информации.

2.Откройте программу DMDE. Все дальнейшие действия читайте на рисунках:

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


Кстати если говорить о минимальном коде загрузчика, то он может выглядеть например так:

Org 7C00h
jmp $
db 508 dup(0)
db 0x55,0xAA

Такой загрузчик, получив управление просто вешает компьютер, совершая в цикле одну бессмысленную команду jmp $. Я называю ее топтаться на месте.

На YouTube я выложил видео, которое возможно поможет вам:

Напоследок несколько кратких фактов о работе загрузчика:

1. Загрузчик, он же bootloader, он же MBR имеет размер 512 байт. Исторически сложилось,
что это условие должно выполняться, для поддержки старых носителей и устройств.
2. Загрузчик, всегда располагается в нулевом секторе флешки, Floppy-дискеты, жесткого диска, с точки зрения программы DMDE или других hex-редакторов, позволяющих работать с устройствами. Для загрузки бинарника (наш boot.bin) на одно из перечисленных устройств, нам не нужно задумываться об их внутренней физической структуре. Программа DMDE просто знает как нужно читать сектора на этих устройствах и отображает их в режиме LBA (просто нумерует их от 0 до последнего сектора). Про LBA можно почитать
3. Загрузчик всегда должен заканчиваться двумя байтами 0x55 0xAA.
4. Загрузчик всегда грузится в память по адресу 0000:7С00h.
5. С загрузчика начинается операционная система.


Сразу говорю, не закрывайте статью с мыслями «Блин, еще один Попов». У него всего-то слизанная 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. Ах да, чуть не забыл - скачать ОС можно на

Assembler

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

Архитектура x86

Ассемблеры для DOS

Наиболее известными ассемблерами для операционной системы DOS являлись Borland Turbo Assembler (TASM) и Microsoft Macro Assembler (MASM). Также в своё время был популярен простой ассемблер A86.
Изначально они поддерживали лишь 16-битные команды (до появления процессора Intel 80386). Более поздние версии TASM и MASM поддерживают и 32-битные команды, а также все команды, введённые в более современных процессорах, и системы команд, специфических для конкретной архитектуры (такие как, например, MMX, SSE, 3DNow! и т. д.).

Microsoft Windows

При появлении операционной системы Microsoft Windows появилось расширение TASM, именуемое TASM32, позволившее создавать программы для выполнения в среде Windows. Последняя известная версия Tasm — 5.3, поддерживающая инструкциии MMX, на данный момент включена в Turbo C++ Explorer. Но официально развитие программы полностью остановлено.
Microsoft поддерживает свой продукт под названием Microsoft Macro Assembler. Она продолжает развиваться и по сей день, последние версии включены в наборы DDK. Но версия программы, направленная на создание программ для DOS, не развивается. Кроме того, Стивен Хатчессон создал пакет для программирования на MASM под названием «MASM32».

GNU и GNU/Linux

В состав операционной системы GNU входит компилятор gcc, включающий в себя ассемблер gas (GNU Assembler), использующий AT&T синтаксис, в отличие от большинства других популярных ассемблеров, которые используют Intel-синтаксис.

Переносимые ассемблеры

Также существует открытый проект ассемблера, версии которого доступны под различные операционные системы, и который позволяет получать объектные файлы для этих систем. Называется этот ассемблер NASM (Netwide Assembler).
YASM это переписанная с нуля версия NASM под лицензией BSD (с некоторыми исключенями).
FASM (Flat Assembler) — молодой ассемблер под модифицированной для запрета перелицензирования (включая под GNU GPL) BSD?лицензией. Есть версии для KolibriOS, GNU/Linux, MS-DOS и Microsoft Windows, использует Intel-синтаксис и поддерживает инструкции AMD64.

Архитектуры RISC


MCS-51
AVR
На данный момент существуют 2 компилятора производства Atmel (AVRStudio 3 и AVRStudio4). Вторая версия — попытка исправить не очень удачную первую. Так же ассемблер есть в составе WinAVR.
ARM
AVR32
MSP430
PowerPC

Ассемблирование и компилирование

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

Язык ассемблера

Язык ассемблера — тип языка программирования низкого уровня, представляющий собой формат записи машинных команд, удобный для восприятия человеком. Часто для краткости его называют просто ассемблером, что не верно.

Команды языка ассемблера один в один соответствуют командам процессора и, фактически, представляют собой удобную символьную форму записи (мнемокод) команд и их аргументов. Также язык ассемблера обеспечивает базовые программные абстракции: связывание частей программы и данныx через метки с символьными именами (при ассемблировании для каждой метки высчитывается адрес, после чего каждое вхождение метки заменяется на этот адрес) и директивы.
Директивы ассемблера позволяют включать в программу блоки данных (описанные явно или считанные из файла); повторить определённый фрагмент указанное число раз; компилировать фрагмент по условию; задавать адрес исполнения фрагмента, отличный от адреса расположения в памяти[уточнить!]; менять значения меток в процессе компиляции; использовать макроопределения с параметрами и др.
Каждая модель процессора, в принципе, имеет свой набор команд и соответствующий ему язык (или диалект) ассемблера.

Достоинства и недостатки

Достоинства языка ассемблера

Минимальное количество избыточного кода, то есть использование меньшего количества команд и обращений в память, позволяет увеличить скорость и уменьшить размер программы.
Обеспечение полной совместимости и максимального использования возможностей нужной платформы: использование специальных инструкций и технических особенностей данной платформы.
При программировании на ассемблере становятся доступными специальные возможности: непосредственный доступ к аппаратуре, портам ввода-вывода и особым регистрам процессора, а также возможность написания самомодифицирующегося кода (то есть метапрограммирование, причём без необходимости программного интерпретатора).
Последние технологии безопасности, внедряемые в операционные системы, не позволяют делать самомодифицирующегося кода, так как исключают одновременную возможность исполнения инструкций и запись в одном и том же участке памяти (технология W^X в BSD-системах, DEP в Windows).

Недостатки языка ассемблера

Большие объёмы кода и большое число дополнительных мелких задач, что приводит к тому, что код становится очень сложно читать и понимать, а следовательно усложняется отладка и доработка программы, а также трудность реализации парадигм программирования и любых других соглашений. что приводит к сложности совместной разработки.
Меньшее количество доступных библиотек, их малая совместимость между собой.
Непереносимость на другие платформы (кроме двоично совместимых).

Применение

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

Связывание ассемблерного кода с другими языками

Поскольку на ассемблере чаще всего пишут лишь фрагменты программы, их необходимо связывать с остальными частями на других языках. Это достигается 2 основными способами:
На этапе компиляции — вставка в программу ассемблерных фрагментов (англ. inline assembler) специальными директивами языка, в том числе написание процедур на языке ассемблера. Способ удобен для несложных преобразований данных, но полноценного ассемблерного кода, с данными и подпрограммами, включая подпрограммы с множеством входов и выходов, не поддерживаемых высокоуровневыми языками, с помощью него сделать нельзя.
На этапе компоновки , или раздельная компиляция. Для взаимодействия скомпонованных модулей достаточно, чтобы связующие функции поддерживали нужные соглашения о вызовах (англ. calling conventions) и типы данных. Написаны же отдельные модули могут быть на любых языках, в том числе и на ассемблере.

Синтаксис

Общепринятого стандарта для синтаксиса языков ассемблера не существует. Однако, существуют стандарты, которых придерживаются большинство разработчиков языков ассемблера. Основными такими стандартами являются Intel-синтаксис и AT&T-синтаксис.

Инструкции

Общий формат записи инструкций одинаков для обоих стандартов:

[метка:] опкод [операнды] [;комментарий]

где опкод — непосредственно мнемоника инструкции процессору. К ней могут быть добавлены префиксы (повторения, изменения типа адресации и пр.).
В качестве операндов могут выступать константы, названия регистров, адреса в оперативной памяти и пр.. Различия между стандартами Intel и AT&T касаются, в основном, порядка перечисления операндов и их синтаксиса при различных методах адресации.
Используемые мнемоники обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных - мнемоники процессоров и контроллеров Motorola, ARM, x86). Они описываются в спецификации процессоров. Возможные исключения:
Если ассемблер использует кроссплатформенный AT&T-синтаксис (оригинальные мнемоники приводятся к синтаксису AT&T)
Если изначально существовало два стандарта записи мнемоник (система команд была наследована от процессора другого производителя).
Например, процессор Zilog Z80 наследовал систему команд Intel i8080, расширил ее и поменял мнемоники (и обозначения регистров) на свой лад. Например сменил интеловские mov на ld. Процессоры Motorola Fireball наследовали систему команд Z80, несколько её урезав. Вместе с тем, Motorola официально вернулась к мнемоникам Intel. И в данный момент половина ассемблеров для Fireball работает с интеловскими мнемониками, а половина с мнемониками Zilog.

Директивы

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

Пример программы

Пример программы Hello world для MS-DOS для архитертуры x86 на диалекте TASM:

.MODEL TINY CODE SEGMENT ASSUME CS:CODE, DS:CODE ORG 100h START: mov ah,9 mov dx,OFFSET Msg int 21h int 20h Msg DB "Hello World",13,10,"$" CODE ENDS END START

Происхождение и критика термина «язык ассемблера»

Данный тип языков получил свое название от названия транслятора (компилятора) с этих языков — ассемблера (англ. assembler — сборщик). Название последнего обусловлено тем, что на первых компьютерах не существовало языков более высокого уровня, и единственной альтернативой созданию программ с помощью ассемблера было программирование непосредственно в кодах.
Язык ассемблера в русском языке часто называют «ассемблером» (а что-то связанное с ним — «ассемблерный»), что, согласно английскому переводу слова, неправильно, но вписывается в правила русского языка. Однако, сам ассемблер (программу) тоже называют просто «ассемблером», а не «компилятором языка ассемблера» и т. п.
Использование термина «язык ассемблера» также может вызвать ошибочное мнение о существовании единого языка низкого уровня, или хотя бы стандарта на такие языки. При именовании языка, на котором написана конкретная программа, желательно уточнять, для какой архитектуры она предназначена и на каком диалекте языка написана.

Сегодня в нашей кунсткамере любопытный образчик - операционная система, написанная на чистом ассемблере. Вместе с драйверами, графической оболочкой, десятками предустановленных программ и игр она занимает меньше полутора мегабайт. Знакомься - исключительно быстрая и преимущественно российская ОС «Колибри».

Развитие «Колибри» шло довольно быстро вплоть до 2009 года. Птичка научилась летать на разном железе, минимально требуя первый «Пентиум» и восемь мегабайт оперативной памяти. Минимальные системные требования «Колибри» таковы:

  • ЦП: Pentium, AMD 5x86 или Cyrix 5x86 без MMX с частотой 100 МГц;
  • ОЗУ: 8 Мбайт;
  • видеокарта: VESA-совместимая с поддержкой режима VGA (640 × 480 × 16).

Современная «Колибри» - это регулярно обновляемые «ночные сборки» последней официальной версии, вышедшей в конце 2009 года. Мы тестировали билд 0.7.7.0+ от 20 августа 2017 года .

WARNING

В настройках по умолчанию у KolibriOS отсутствует доступ к дискам, которые видны через BIOS. Хорошенько подумай и сделай бэкап, прежде чем менять эту настройку.

Изменения в ночных сборках хоть и невелики, но за годы их накопилось достаточно. Обновленная «Колибри» может писать на разделы FAT16–32 / ext2 - ext4 и поддерживает другие популярные файловые системы (NTFS, XFS, ISO-9660) в режиме чтения. В ней появилась поддержка USB и сетевых карт, был добавлен стек TCP/IP и звуковые кодеки. В общем, в ней уже можно что-то делать, а не просто посмотреть разок на сверхлегкую операционку с GUI и впечатлиться скоростью запуска.



Как и предшествующие версии, последняя «Колибри» написана на flat assembler (FASM) и занимает одну дискету - 1,44 Мбайт. Благодаря этому ее можно целиком разместить в какой-нибудь специализированной памяти. Например, умельцы записали KolibriOS прямо во Flash BIOS . Во время работы она может целиком размещаться в кеше некоторых процессоров. Только представь: вся операционка вместе с программами и драйверами кеширована!

INFO

При посещении сайта kolibrios.org браузер может предупредить об опасности. Причина, судя по всему, - это ассемблерные программы в дистрибутиве. Сейчас VirusTotal определяет сайт как совершенно безопасный.

«Колибри» легко загружается с дискеты, винчестера, флешки, Live CD или в виртуальной машине. Для эмуляции достаточно указать тип ОС «другая», выделить ей одно ядро процессора и немного оперативки. Диск подключать необязательно, а при наличии роутера с DHCP «Колибри» моментально подключится к интернету и локальной сети. Сразу при загрузке ты увидишь соответствующее уведомление.


Одна проблема - протокол HTTPS не поддерживается встроенным в «Колибри» браузером. Поэтому сайт посмотреть в ней не удалось, так же как открыть страницы Google, Yandex, Wikipedia, «Сбербанка»... собственно, никакой привычный адрес. Все давно перешли на защищенный протокол. Единственный сайт с олдскульным чистым HTTP, который мне попался, - это «портал Правительства России», но и он выглядел в текстовом браузере не лучшим образом.



Настройки внешнего вида в «Колибри» с годами улучшаются, но все еще далеки от идеала. Список поддерживаемых видеорежимов отображается на экране загрузки «Колибри» при нажатии клавиши с латинской буквой a.



Перечень доступных вариантов невелик, и нужного разрешения в нем может не оказаться. Если у тебя видеокарта с ГП AMD (ATI), то можно сразу добавить кастомные настройки. Для этого нужно загрузчику ATIKMS передать параметр -mxx , например:

/RD/1/DRIVERS/ATIKMS -m1280x800x60 -1

Здесь /RD/1/DRIVERS/ATIKMS - это путь до загрузчика (RD - RAM Disk).

При работе системы выбранный видеорежим можно посмотреть командой vmode и (теоретически) переключать вручную. Если «Колибри» запущена в виртуалке, то это окно останется пустым, а вот при чистой загрузке драйверы видео Intel можно добавить от i915 до Skylake включительно.

Удивительно, но в KolibriOS уместилась куча игр. Среди них есть логические и аркадные, пятнашки, змейка, танки (нет, не WoT) - целый «Игровой центр»! На «Колибри» портировали даже Doom и Quake.



Еще из важного нашлась читалка FB2READ. Она корректно работает с кириллицей и имеет настройки отображения текста.



Все пользовательские файлы рекомендую хранить на флешке, но подключать ее нужно обязательно через порт USB 2.0. Наша флешка USB 3.0 (в порте USB 2.0) объемом 16 Гбайт с файловой системой NTFS определилась сразу. Если нужно записывать файлы, то стоит подключить флешку с разделом FAT32.



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



Встроенный текстовый редактор имеет подсветку ASM-синтаксиса и даже позволяет сразу запускать набранные программы.



Среди средств разработки есть компилятор Oberon-07/11 для i386 Windows, Linux и KolibriOS, а также низкоуровневые эмуляторы: E80 - эмулятор ZX Spectrum, FCE Ultra - один из лучших эмуляторов NES, DOSBox v.0.74 и другие. Все они были специально портированы на «Колибри».

Если оставить KolibriOS на несколько минут, то запустится скринсейвер. На экране побегут строки кода, в которых можно увидеть отсылку к MenuetOS.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «сайт», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score!