Как написать свою операционную систему на ассемблере

Время на прочтение
3 мин

Количество просмотров 38K

Всем привет! Сегодня мы напишем загрузчик, который будет выводить «Hello World» и запустим его на VirtualBox. Писать будем на ассемблере FASM. Скачать его можно отсюда. Также нам понадобится собственно VirtualBox и UltraISO. Перед тем как писать код, разберемся как загружаются операционные системы.

Итак, когда мы нажимаем большую кнопку включения на нашем компьютере запускается система, которая есть на любом компьютере — BIOS (Basic Input/Output System или базовая система ввода/вывода). Задача BIOS это:

  1. Обнаружить все подключенные устройства и проверить их на работоспособность. За это отвечает программа POST (Power On Self Test, самотестирование при включении). Если жизненно необходимое железо не обнаружено, то системный динамик (если таковой имеется) пропищит что-то непонятное и дальше загрузка не пойдет.
  2. Предоставить операционной системе функции для работы с железом.
  3. Считать самый первый сектор загрузочного устройства в нулевой сегмент оперативной памяти по смещению 0x7C00h и передать туда управление. 1 сектор на диске равен 512 байтам. Поэтому, наш загрузчик не должен превышать 512 байт. BIOS определяет, что сектор загрузочный по наличию в последних двух его байтах значений 0x55 и 0xAA.

Теперь можно приступать к написанию кода. Запускаем файл FASMW.EXE, который находится в архиве с FASM-мом и вставляем туда следующий код:

org 7C00h

 start:
    cli              ;Запрещаем прерывания (чтобы ничего не отвлекало)
    xor ax, ax       ;Обнуляем регистр ax
    mov ds, ax       ;Настраиваем dataSegment на нулевой адрес
    mov es, ax       ;Настраиваем сегмент es на нулевой адрес
    mov ss, ax       ;Настраиваем StackSegment на нулевой адрес
    mov sp, 07C00h   ;Указываем на текущую вершину стека
    sti              ;Запрещаем прерывания

  ;Очищаем экран
  mov ax, 3
  int 10h

  mov ah, 2h
  mov dh, 0
  mov dl, 0
  xor bh, bh
  int 10h

  ;Печатаем строку
  mov ax, 1301h
  mov bp, message
  mov cx, 12
  mov bl, 02h
  int 10h

  jmp $

message db 'Hello World!',0

times 510 - ($ - $$) db 0 ;Заполнение оставшихся байт нулями до 510-го байта
db 0x55, 0xAA ;Загрузочная сигнатура  

Этот код требует немного пояснений. Командой

org 7C00h

мы говорим, что код нужно загружать в ОЗУ по адресу 0x7C00. В строках

  mov ax, 3
  int 10h

мы устанавливаем видео режим 80х25 (80 символов в строке и 25 строк) и тем самым очищаем экран.

  mov ah, 2h
  mov dh, 0
  mov dl, 0
  xor bh, bh
  int 10h

Здесь мы устанавливаем курсор. За это отвечает функция 2h прерывания 10h. В регистр dh мы помещаем координату курсора по Y, а в регистр dl — по X.

  mov ax, 1301h
  mov bp, message
  mov cx, 12
  mov bl, 02h
  int 10h

Печатаем строку. За это отвечает функция 13h прерывания 10h. В регистр bp мы помещаем саму строку, в регистр cx — число символов в строке, в регистр bl — атрибут, в нашем случае цвет, он будет зеленым. На цвет фона влияют первые 4 бита, на цвет текста — вторые 4 бита. Ниже представлена таблица цветов

0 - черный, 1 - синий, 2 - зеленый, 3 - желтый, 4 - красный, 5 - фиолетовый, 6 - коричневый, 7 - светло-серый, 8 - темно-серый, 9 - светло-синий, A - светло-зеленый, B - светло-желтый, C - светло-красный, D- светло-фиолетовый, E - светло-коричневый, F – Белый.

В строке

jmp $

Программа зависает.

Откомпилируем код нажатием клавиш Ctrl + F9 и сохраним полученный файл как boot.bin.

Запуск

Запускаем UltraISO и перетаскиваем наш бинарник в специальную область (отмечено красной стрелкой).

Далее кликаем правой кнопкой мыши по бинаринку и нажимаем кнопку примерно с такой надписью: «Установить загрузочным файлом». Далее сохраняем наш файл в формате ISO.
Открываем VIrtualBox и создаем новую виртуальную машину (если вы не знаете, как это делается, кликайте сюда). Итак, после того, как вы создали виртуальную машину, нажимаем «Настроить, выбираем пункт „Носители“, нажимаем на „Пусто“, там где „Привод“ есть значок оптического диска. Нажимаем на него и выбираем „Выбрать образ оптического диска“, ищем наш ISO файл, нажимаем „Открыть“. Сохраняем все настройки и запускаем виртуальную машину. На экране появляется наш „Hello World!“.

На этом первый выпуск подходит к концу. В следующей части мы научим наш загрузчик читать сектора диска и загрузим свое первое ядро!

Школа ассемблера: разработка операционной системы

Оригинал: 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 [si], 0
je loop
cmp word [si], "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 на экран.

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

Наша операционная система является упрощенной версией операционной системы MikeOS (http://mikeos.sf.net), к исходному коду которой вы можете обращаться в поисках вдохновения

Наша операционная система является упрощенной версией операционной системы MikeOS (http://mikeos.sf.net), к исходному коду которой вы можете обращаться в поисках вдохновения

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

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

В библиотеке lib.asm полно полезных подпрограмм - внимательно присмотритесь к их реализациям

В библиотеке lib.asm полно полезных подпрограмм — внимательно присмотритесь к их реализациям


Предыдущие статьи из серии «Школа ассемблера»:

  • «Начинаем программировать на языке ассемблера: переход на уровень аппаратного обеспечения»
  • «Школа ассемблера: условные инструкции, циклы и библиотеки»
  • «Начинаем программировать на языке ассемблера»

Если вам понравилась статья, поделитесь ею с друзьями:


Создание мини-ОС

19.09.2011

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

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

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

В качестве инструмента программирования выберем FASM по причине большей компактности программ.
Тем более FASM поддерживает прямые переходы jmp 0000:0500h. В MASM для этого нам пришлось создавать бы переменную, которая содержала бы адрес.
Это бы вело к увеличению размера программы, который итак ограничен 512 байтами.

Немного видоизменим загрузочный сектор, убрав из него все лишнее. Это будет файл fboot.asm.

;===================== Загрузочный сектор. Евгений Попов, 2011===================== 
;==================================================================================
 org 7c00h 	;BIOS производит чтение 512 байт первого сектора MBR в ОЗУ по адресу 0x00007C00 
			;(0x07C0:0x0000 в формате реального режима), затем прочитанному коду передаётся управление
start:
		cli 			;запрещаем прерывания
        xor ax,ax		;обнуляем регистр ах
        mov ds,ax		;настраиваем сегмент данных на нулевой адрес
        mov es,ax		;настраиваем сегмент es на нулевой адрес
        mov ss,ax		;настраиваем сегмент стека на нулевой адрес
        mov sp,07C00h	;сегмент sp указывает на текущую вершину стека
        sti		    ;разрешаем прерывания
           
        mov ax, 0002h  	;очищаем экран - функция 00h прерывания 10h	
        int 10h
        
        mov dx,0h
        call SetCursorPos
        
        mov bp, msg             
        mov cx, 13
        call PrintMes	;Вывод на экран строки msg
        
        add dh,1		;переходим на одну строку ниже
        call SetCursorPos
        mov bp, Con             ;Вывод на экран строки Con
        mov cx, 23
        call PrintMes
                
		 mov ah,10h
         int 16h
                
Continue:
        cmp al, 0Dh 	;Если нажимаем на Enter, то переходим к загрузке ядра
        jz Kernel
        jmp Continue	;Если нет, снова ожидаем нажатия клавиши
                
Kernel:
  		mov ax,0000h
        mov es,ax
        mov bx,500h
        mov ch,0			;номер цилиндра - 0
        mov cl,02h			;начальный сектор - 2
        mov dh,0			;номер головки - 0
        mov	dl,80h			;жесткий диск - 80h
        mov al,01h			;кол-во читаемых секторов -1
        mov ah,02h
        int 13h
        jmp 0000:0500h		;переход на 0000:0500h, куда загружен второй сектор

;===================== Подпрограммы ===================================
PrintMes:                   ;в регистре  bp - строка, в регистре cx - длина этой строки
        mov bl,04h			;в регистре  bl- атрибут
        mov ax,1301h		 функция 13h прерывания 10h
        int 10h
        ret
        ;----------------------------------
SetCursorPos:        ;установка курсора : функция 02h прерывания 10h
        mov ah,2h
        xor bh,bh
        int 10h 
        ret
           
;===================== выводимые сообщения===================== 
      	msg db 'OS Loading...',0     
        Con db 'Press Enter to Continue',0
 
times(512-2-($-07C00h)) db 0
db 055h,0AAh ;сигнатура, символизирующая о завершении загрузочного сектора
 

Теперь файл ядра — fkernel.asm. Оболочка будет выводить приглашение к вводу программы. Мы же должны напечатать команду write и
после нажатия клавиши Enter перейти в текстовый редактор

org 500h				;этот сектор будет загружаться по адресу 0000:0500h
message:
	mov ax, 0002h  	;очищаем экран
        int 10h
        
	mov dx,0h
	call SetCursorPos
        mov bp, msg
        mov cx, 20
        mov bl,04h					
        xor bh,bh
        mov ax,1301h
        int 10h			;вывод приглашения к вводу команды
        
        add dh,2			;переводим курсор на один пункт вниз для ввода команды
        call SetCursorPos
        mov si,0
		
Command: 
	mov ah,10h
        int 16h
        cmp ah, 0Eh		;Если нажата клавиша BackSpase - удалить символ
        jz Delete_symbol
        cmp al, 0Dh
        jz Input_Command
        mov [string+si],al
        inc si
        mov ah,09h
        mov bx,0004h
        mov cx,1
        int 10h
        add dl,1
	call SetCursorPos
 	jmp Command
        
Input_Command:		;Если нажат Enter, то переходим в третий сектор
	mov ax,cs
	mov ds,ax
	mov es,ax
	mov di,string
	push si		;так как содержание регистра si меняется, сохраним в стеке
	mov si,write
	mov cx,5
	rep cmpsb ;сравниваем строки - если команда write, то переходим
	je wrt
	pop si
	jmp Command
		
Delete_symbol:
	cmp dl,0
	jz Command
	sub dl,1		;сдвигаем курсор влево
	call SetCursorPos
	mov al,20h		;вместо уже напечатанного символа выводим пробел
	mov [string + si],al ;стираем символ в строке
	mov ah,09h
	mov bx,0004h
        mov cx,1
        int 10h
        dec si			;уменьшаем кол-во напечатанных символов
	jmp Command
		
wrt:	mov ax,0000h
        mov es,ax
        mov bx,700h         
        mov ch,0			;номер цилиндра - 0
        mov cl,03h			;начальный сектор - 3
        mov dh,0			;номер головки - 0
        mov dl,80h			;жесткий диск - 80h
        mov al,01h			;кол-во читаемых секторов -1
        mov ah,02h
        int 13h
	jmp 0000:0700h

SetCursorPos:        ;установка курсора
        mov ah,2h
        xor bh,bh
        int 10h 
        ret

msg db 'Input the command...',0
write db 'write',0
string db 5 dup(?) ;буфер для ввода команды

Последняя часть — создание текстового редактора (файл fwriter.asm).
Поскольку создание текстовых редакторов на ассемблере — не самое легкое дело, его функционал будет минимальным.
Редактор позволит сохранять текст, редактировать, загружать сохраненный текст.
Основной недостаток — макисмальное количество символов текста — 256.
Что конечно не очень хорошо, поскольку в этом случае при сохранении текста у нас заполняется всего половина четвертого сектора.

org 700h
start:
  	mov ax,0002h	;очищаем экран
  	int 10h
        xor dx,dx
        call SetCursorPos	;устанавливаем курсор
                                
        mov bp, msg
        mov cx, 24
        call PrintMes	;Вывод на экран строки msg
        
        mov dl,0
        mov dh,1
        call SetCursorPos	;переводим курсор на одну строку вниз
        mov bp, helper
        mov cx,77
        call PrintMes		;Вывод на экран строки helper
        
Option:						;Выбор - загрузить текст из четвертого сектора или начать новый
        mov ah,10h
        int 16h
        cmp ah, 3Bh			;Если нажата клавиша F1 - загружаем текст
        jz Load_text
        cmp al, 0Dh			;Если нажата клавиша Enter - печатаем текст
        jz Print_text
	jmp Option
        
Load_text:					;Загрузка текста
	mov ax,0000h
        mov es,ax
        mov bx,string         
        mov ch,0			;номер цилиндра - 0
        mov cl,4			;начальный сектор - 4
        mov dh,0			;номер головки - 0
        mov dl,80h			;жесткий диск - 80h
        mov al,01h			;кол-во читаемых секторов -1
        mov ah,02h
        int 13h
        xor dl,dl
        mov dh,3
        call SetCursorPos
        mov bp, string
        mov cx, 256
        call PrintMes
        mov si,255
        add dl, 15			;256-80*3=16
        add dh,3
        call SetCursorPos
        jmp Command
        
Print_text:
        xor dx,dx
        add dh,3
        call SetCursorPos	;получаем позицию курсора
        mov si,0			;Печать символов
Command: 
	mov ah,10h
        int 16h
        cmp al, 1Bh		;Если нажата клавиша Esc - выход из приложения
        jz Esc
        cmp al, 0Dh		;Если нажата клавиша Enter - переход на новую строку
        jz Caret
        cmp ah, 0Eh		;Если нажата клавиша BackSpase - удалить символ
        jz Delete_symbol
        cmp ah, 3Ch		;Если нажата клавиша F2- сохранить текст
        jz Save_text
        cmp si, 256
        jz Command
        mov [string + si],al
        inc si
        mov ah,09h
        mov bx,0004h
        mov cx,1
        int 10h
        add dl,1
        call SetCursorPos
 	jmp Command
		
Caret:	;переход на новую строку
	add dh,1
	xor dl,dl
        call SetCursorPos
        jmp Command
        
Save_text:	;запись текста в 4 сектор
	mov ax,0000h
        mov es,ax
	mov ah, 03h
	mov al,1
	mov ch,0
	mov cl,4
	mov dh,0
	mov dl,80h
	mov bx, string
	int 13h
	jmp Command
		
Delete_symbol: ;удаление символа после нажатия BackSpase
	cmp dl,0
	jne Delete
	cmp dh,3
	jz Command
	sub dh,1
	mov dl,79
	jmp Cursor_Pos
Delete:		sub dl,1			;сдвигаем курсор влево
Cursor_Pos: 
	call SetCursorPos
	mov al,20h			;вместо уже напечатанного символа выводим пробел
	mov [string + si],al ;стираем символ в строке
	mov ah,09h
        mov bx,0004h
        mov cx,1
        int 10h
        cmp si,0
        jz Command
        dec si				;уменьшаем кол-во напечатанных символов
	jmp Command
Esc:     
        jmp 0000:0500h		;возвращаемся во второй сектор
        
;===================== Подпрограммы ===================================
  PrintMes:                ;в регистре  bp - строка, в регистре cx - длина этой строки
        mov bl,04h			;в регистре  bl- атрибут
        mov ax,1301h
        int 10h
        ret
        ;----------------------------------
  SetCursorPos:        								;установка курсора
        mov ah,2h
        xor bh,bh
        int 10h 
        ret
            
        ;===================== выводимые сообщения===================== 
      	msg db 'This is a text writer...',0 
      	helper db 'To print text - press Enter, to load text - press F1, to save text - press F2',0
        string db 256 dup(?)	;буфер для вводимого сообщения

Теперь скомпилируем три файла и создадим из них образ

macro align value { db value-1 - ($ + value-1) mod (value) dup 0 }
HEADS = 1
SPT = 4	;4 сектора по 512 байт
Begin:
	file "fboot.bin",512 ; загрузчик
	file "fkernel.bin" ; первый файл, типа оболочка shell
	align 512
	file "fwriter.bin" ; второй файл - текстовый редатор
	align 512
	align HEADS*SPT*512

Загружаем систему через Bochs

Нажимаем Enter и загружаем ядро

Вводим команду write и переходим в текстовый редактор

os-project

Пишем свою собственную операционную систему с нуля!

Идея написать ОС возникла у меня в процессе поиска идеи для сайд-проекта. Это исключительно хобби-проект, не рассчитанный на серьезность и достоверность, и хотя я пытался объяснить многие новые и неочевидные концепты, с которыми я столкнулся в процессе разработки, я мог что-то упустить, так как я сам только учусь — именно поэтому я настоятельно рекомендую пользоваться гуглом и любыми другими источниками информации когда вы познакомитесь с чем-то новым в гайде. Гуглите абсолютно всё. Я серьезно.

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

Навигация по репозиторию

guide/ — гайд с последовательными уроками, теорией и задокументированным кодом

  • Гайд разделен на главы, например 00-BOOT-SECTOR
  • Главы разделены на упражнения, например ex00
  • Упражнения содержат в себе код и теорию. Выглядят как main.asm

src/ — исходный код ОС

Установка и запуск

  1. Установить эмулятор QEMU (подробнее: https://www.qemu.org/download/)
sudo apt install qemu-kvm qemu
  1. Собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:
wget http://newos.org/toolchains/i386-elf-4.9.1-Linux-x86_64.tar.xz
mkdir /usr/local/i386elfgcc
tar -xf i386-elf-4.9.1-Linux-x86_64.tar.xz -C /usr/local/i386elfgcc --strip-components=1
export PATH=$PATH:/usr/local/i386elfgcc/bin
  1. Клонировать и собрать проект
git clone https://github.com/thedenisnikulin/os-project
cd os-project/src/build
make
  1. Запустить образ ОС с помощью эмулятора
qemu-system-i386 -fda os-image.bin

Справочник по синтаксису ассемблера NASM

https://www.opennet.ru/docs/RUS/nasm/nasm_ru3.html


Дополнительная информация

Ссылки на полезный материал которым я пользовался в качестве теории.

На русском языке:

  • Серия статей о ядре Linux и его внутреннем устройстве: https://github.com/proninyaroslav/linux-insides-ru
  • Статья «Давай напишем ядро!»: https://xakep.ru/2018/06/18/lets-write-a-kernel/

На английском языке:

  • Небольшая книга по разработке собственной ОС (70 страниц): https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf
  • Общее введене в разработку операционных систем: https://wiki.osdev.org/Getting_Started
  • Туториал по разработке ядра операционной системы для 32-bit x86 архитектуры. Первые шаги в создании собсвтенной ОС: https://wiki.osdev.org/Bare_Bones
  • Продолжение предыдущего туториала: https://wiki.osdev.org/Meaty_Skeleton
  • Про загрузку ОС (booting): https://wiki.osdev.org/Boot_Sequence
  • Список туториалов по написанию ядра и модулей к ОС: https://wiki.osdev.org/Tutorials
  • Внушительных размеров гайд по разработке ОС с нуля: http://www.brokenthorn.com/Resources/OSDevIndex.html
  • Книга, описывающая ОС xv6 (не особо вникал, но должно быть что-то годное): https://github.com/mit-pdos/xv6-riscv-book, сама ОС: https://github.com/mit-pdos/xv6-public
  • «Небольшая книга о разработке операционных систем» https://littleosbook.github.io/
  • Операционная система от 0 до 1 (книга): https://github.com/tuhdo/os01
  • ОС, написанная как пример для предыдущей книги: https://github.com/tuhdo/sample-os
  • Интересная статья про программирование модулей для Линукса и про системное программирование https://jvns.ca/blog/2014/09/18/you-can-be-a-kernel-hacker/
  • Еще одна статья от автора предыдущей https://jvns.ca/blog/2014/01/04/4-paths-to-being-a-kernel-hacker/
  • Пример простого модуля к ядру линукса: https://github.com/jvns/kernel-module-fun/blob/master/hello.c
  • Еще один туториал о том, как написать ОС с нуля: https://github.com/cfenollosa/os-tutorial
  • Статья «Давайте напишем ядро»: https://arjunsreedharan.org/post/82710718100/kernels-101-lets-write-a-kernel
  • Сабреддит по разработке ОС: https://www.reddit.com/r/osdev/
  • Большой список идей для проектов для разных ЯП, включая C/C++: https://github.com/tuvtran/project-based-learning/blob/master/README.md
  • Еще один список идей для проектов https://github.com/danistefanovic/build-your-own-x
  • «Давайте напишем ядро с поддержкой ввода с клавиатуры и экрана»: https://arjunsreedharan.org/post/99370248137/kernel-201-lets-write-a-kernel-with-keyboard-and

Пишем операционную систему. Часть 1. Загрузчик +34

Из песочницы, Системное программирование


Рекомендация: подборка платных и бесплатных курсов 3D max — https://katalog-kursov.ru/

Всем привет! Сегодня мы напишем загрузчик, который будет выводить «Hello World» и запустим его на VirtualBox. Писать будем на ассемблере FASM. Скачать его можно отсюда. Также нам понадобится собственно VirtualBox и UltraISO. Перед тем как писать код, разберемся как загружаются операционные системы.

Итак, когда мы нажимаем большую кнопку включения на нашем компьютере запускается система, которая есть на любом компьютере — BIOS (Basic Input/Output System или базовая система ввода/вывода). Задача BIOS это:

  1. Обнаружить все подключенные устройства и проверить их на работоспособность. За это отвечает программа POST (Power On Self Test, самотестирование при включении). Если жизненно необходимое железо не обнаружено, то системный динамик (если таковой имеется) пропищит что-то непонятное и дальше загрузка не пойдет.
  2. Предоставить операционной системе функции для работы с железом.
  3. Считать самый первый сектор загрузочного устройства в нулевой сегмент оперативной памяти по смещению 0x7C00h и передать туда управление. 1 сектор на диске равен 512 байтам. Поэтому, наш загрузчик не должен превышать 512 байт. BIOS определяет, что сектор загрузочный по наличию в последних двух его байтах значений 0x55 и 0xAA.

Теперь можно приступать к написанию кода. Запускаем файл FASMW.EXE, который находится в архиве с FASM-мом и вставляем туда следующий код:

org 7C00h

 start:
    cli              ;Запрещаем прерывания (чтобы ничего не отвлекало)
    xor ax, ax       ;Обнуляем регистр ax
    mov ds, ax       ;Настраиваем dataSegment на нулевой адрес
    mov es, ax       ;Настраиваем сегмент es на нулевой адрес
    mov ss, ax       ;Настраиваем StackSegment на нулевой адрес
    mov sp, 07C00h   ;Указываем на текущую вершину стека
    sti              ;Запрещаем прерывания

  ;Очищаем экран
  mov ax, 3
  int 10h

  mov ah, 2h
  mov dh, 0
  mov dl, 0
  xor bh, bh
  int 10h

  ;Печатаем строку
  mov ax, 1301h
  mov bp, message
  mov cx, 12
  mov bl, 02h
  int 10h

  jmp $

message db 'Hello World!',0

times 510 - ($ - $$) db 0 ;Заполнение оставшихся байт нулями до 510-го байта
db 0x55, 0xAA ;Загрузочная сигнатура  

Этот код требует немного пояснений. Командой

org 7C00h

мы говорим, что код нужно загружать в ОЗУ по адресу 0x7C00. В строках

  mov ax, 3
  int 10h

мы устанавливаем видео режим 80х25 (80 символов в строке и 25 строк) и тем самым очищаем экран.

  mov ah, 2h
  mov dh, 0
  mov dl, 0
  xor bh, bh
  int 10h

Здесь мы устанавливаем курсор. За это отвечает функция 2h прерывания 10h. В регистр dh мы помещаем координату курсора по Y, а в регистр dl — по X.

  mov ax, 1301h
  mov bp, message
  mov cx, 12
  mov bl, 02h
  int 10h

Печатаем строку. За это отвечает функция 13h прерывания 10h. В регистр bp мы помещаем саму строку, в регистр cx — число символов в строке, в регистр bl — атрибут, в нашем случае цвет, он будет зеленым. На цвет фона влияют первые 4 бита, на цвет текста — вторые 4 бита. Ниже представлена таблица цветов

0 - черный, 1 - синий, 2 - зеленый, 3 - желтый, 4 - красный, 5 - фиолетовый, 6 - коричневый, 7 - светло-серый, 8 - темно-серый, 9 - светло-синий, A - светло-зеленый, B - светло-желтый, C - светло-красный, D- светло-фиолетовый, E - светло-коричневый, F – Белый.

В строке

jmp $

Программа зависает.

Откомпилируем код нажатием клавиш Ctrl + F9 и сохраним полученный файл как boot.bin.

Запуск

Запускаем UltraISO и перетаскиваем наш бинарник в специальную область (отмечено красной стрелкой).

Далее кликаем правой кнопкой мыши по бинаринку и нажимаем кнопку примерно с такой надписью: «Установить загрузочным файлом». Далее сохраняем наш файл в формате ISO.
Открываем VIrtualBox и создаем новую виртуальную машину (если вы не знаете, как это делается, кликайте сюда). Итак, после того, как вы создали виртуальную машину, нажимаем «Настроить, выбираем пункт „Носители“, нажимаем на „Пусто“, там где „Привод“ есть значок оптического диска. Нажимаем на него и выбираем „Выбрать образ оптического диска“, ищем наш ISO файл, нажимаем „Открыть“. Сохраняем все настройки и запускаем виртуальную машину. На экране появляется наш „Hello World!“.

На этом первый выпуск подходит к концу. В следующей части мы научим наш загрузчик читать сектора диска и загрузим свое первое ядро!

Данная статья в большей степени является не руководством и не мануалом, а просто моими заметками. Идея этой статьи собрать множество особенностей и знаний в одно целое, надеюсь, она кому-то пригодится =)

Что происходит с ОЗУ при загрузке компьютера

Когда вы нажимаете кнопку старта на компьютере(или замыкаете контакты на материнке) BIOS проверяет оборудование и загружает первый сектор жесткого диска(512 байт), который помечен как загрузочный, по адресу 7C00h (h — hex) и начинает выполнять программу которая лежит в этих 512 байтах. От сюда следует, что у нас в распоряжеии есть только 512 байт.

В конце нашей программы (именуемой загрузчиком) должна быть сигнатура загрузчика — это два байта 55h и AAh, по этим двум байтам BIOS определяет, является ли эта программа загрузчиком. В загрузчике мы должны написать загрузку с жесткого диска либо второго загрузчика, либо сразу ядра ОС, в нашем случае сразу ядра ОС.

Ядро ОС будет располагаться по адресу 0500h, программы по адресу 7E00h, вершина стека 7DFFh.

Структура памяти при запуске компьютера.

Ядро располагается на 3 секторе жесткого диска и будет занимать 4 сектора(4 *512) или 2 Kb.Для загрузки даных с жесткого диска будет использовать прерывание 13h и функция 42h.
У этой функции на вход идет DAPS структура, в которой описано куда, сколько и от куда грузить сектора.

Структура DAPS

  • 1 байт — размер структура(в нашем случае 16 байт)

  • 1 байт — всегда 0, резерв

  • 1 байт — сколько загружать секторов(в нашем случае 4(размер ядра))

  • 1 байт — всегда 0, резерв

  • 2 байта — по какому смещению загружать данные

  • 2 байта — по какому сегменту загружать данные

  • 8 байт — номер сектора с которого начинать загружать данные

#define u_int16 unsigned short int
#define u_char8 unsigned char
#define u_long_int unsigned long int
#define u_long_int64 unsigned long long int
struct daps
{
    u_char8 p_size = 16;
    u_char8 p_empty = 0;
    u_char8 p_n_setors;
    u_char8 p_empty2 = 0;
    u_int16 p_adres;
    u_int16 p_segment;
    u_long_int64 sector;
    file data_file;
};

На file data_file пока не смотрите, это пригодиться в будущем, для удобства чтения файлов в нашей ФС (файловай системе).

Загрузчик

Код загрузчика


use16
org 7c00h
cli             ;запрещаем прерывания
        xor ax,ax       ;обнуляем регистр ах
        mov ds,ax       ;настраиваем сегмент данных на нулевой адрес
        mov es,ax       ;настраиваем сегмент es на нулевой адрес
        mov ss,ax       ;настраиваем сегмент стека на нулевой адрес
        mov sp,07DFFh   ;сегмент sp указывает на текущую вершину стека
sti         ;разрешаем прерывания

push cs
pop ds
mov si,paket
mov ah,42h
int 13h
jmp 0000:0500h

jmp $
paket:;DAPS
        db 16;const paksize
        db 0;null
        db 4;кол-во секторов
        db 0;null
        dw 0500h;смещение
        dw 0;сегмент
        dq 2;начало
times(512-2-($-07C00h)) db 0
db 055h,0AAh
;16 байт 1 сегмент

Ядро ОС

Наше ядро при запуске сохраняет номер диска, который BIOS положил в регистр DL, в переменную BOOT_DISK(она нужна будет для доступа к диску, файлам и тд) и прыгает на метку START_K. То что идет после START_K ставит вектора прерываний 90h(основное API ОС) и 91h(Возврат управления ОС).

Установка векторов прерываний осуществляется с помощью этого макроса, на вход номер прерывания и адрес функции обработчика.

macro SET_INTERRUPT_HANDLER NUM, HANDLER
{
    pusha
    xor ax,ax
    push ax
    pop es
    mov al,NUM
    mov bl,4h
    mul bl
    mov bx,ax
    mov si,HANDLER
    mov [es:bx],si
    add bx,2
    push cs
    pop ax
    mov [es:bx], ax
    popa
}

Далее загружается таблица файлов, в нашей ОС она находится во втором секторе жесткого диска. Загрузка также происходит через DAPS.

DAPS таблицы файлов

DAPS_TABEL_FILES:
    db 16;const paksize
    db 0;null
    db 1;кол-во секторов
    db 0;null
    dw TABLE_FILES;смещение
    dw 0;сегмент
    dq 1;начало

Загрузка с помощью макроса и функции 17h прерывания 90h(которое установило ядро)

macro LOAD_DAPS DAPS
{
    push cs
    pop ds
    mov si, DAPS
    mov ah, 17h
    int 90h
}

Функция 17h прерывания 90h(по сути просто бертка над 13h)

cmp ah,17h;-|-in - ds:si - daps
je HF_LOAD_DAPS

iret

HF_LOAD_DAPS:
    call F_LOAD_DAPS
iret
;-|-in - ds:si - daps
; |-out - (load file table on ram)
F_LOAD_DAPS:
	mov dl,[BOOT_DISK];вот и пригодилась наша переменная с номером диска
	mov ah,42h
	int 13h
ret

Далее идет печать строки приветствия с помощью макроса PRINT.

macro PRINT STR,COLOR
{
    mov ah,2
    push cs
    pop ds
    mov di,STR
    mov bl,COLOR
    int 90h
}

Он вызывает 2 функцию прерывания 90h, которая вызывает функцию F_PRINT.

;--------------------Печать Форматированной Строки-------------------------
F_PRINTSF:;ds:di-str,bl-color
    call F_GET_CURSOR
	xor cx,cx
	mov cl,[ds:di]
	inc di
	MAIN_START_F_PRINTSF:
		call F_READ_VIDEO
		mov ah,013h
		push ds
		pop es
		mov bp,di
		mov al,1
		int 10h
ret
;--------------------Печать Строки-------------------------
F_PRINT:;ds:di-str,bl-color
	push di
	push ds
	call F_GET_CURSOR
	call F_GET_LEN_STR
	pop ds
	pop di
	call MAIN_START_F_PRINTSF
ret
;-------------------Чтение видео режима------------------------------------------------------
F_READ_VIDEO:;out al=video ah=число колонок bh= номер активной страницы дисплея
    mov ah,0fh
    int 10h
ret
;------------Получение курсора; Выход: dh,dl - string,char ch,cl=нач.и кончеч строки курсора ----------------------------------------------------
F_GET_CURSOR:;out= dh,dl - string,char ch,cl=нач.и кончеч строки курсора
    call F_READ_VIDEO
    mov ah,03h
    int 10h
ret
;-------------------Фуекция подсчета длины строки.------------------------------------------------------------------------------

;-|-in - ds:di=str, cx=len
; |-out - cx=len
F_GET_LEN_STR:
	xor cx,cx
	START_F_GET_LEN_STR:
	mov al,[ds:di]
	cmp al,0
	je EXIT_F_GET_LET_STR
	inc di
	inc cx
	jmp START_F_GET_LEN_STR
	EXIT_F_GET_LET_STR:
ret

Далее идет поиск файла с именем cmd и его запуск. Реализованно это с помощью макросов SEACH_FILE и LOAD_DAPS.

macro SEACH_FILE TABLE_FILES, FILENAME
{
    push cs
    pop ds
    mov bx,TABLE_FILES
    mov di, FILENAME
    mov ah,10h
    int 90h
}

Макрос вызывает 10h функцию прерывания 90h.

;-------------------Поиск адреса файла------------------------------------------------------------------------------------------

;-|-in - ds:bx=tableFiles, ds:di=flename
; |-out - ch-dorogka cl=sector, al=numSectors ah = type
; |-except - not found - ax=0, cx=0
F_SEACH_FILE:
jmp startpfseachFile
	pfseachFilecxsave: db 0
	pfseachFilebxsave: db 0,0
	pfseachFiledisave: db 0,0
	startpfseachFile:
	xor cx,cx
	mov cl,32
	add bx,4
	mov [pfseachFiledisave],di
	startSeach:
	mov [pfseachFilecxsave],cl
	mov [pfseachFilebxsave],bx
	mov di,[pfseachFiledisave]
	mov si,[pfseachFilebxsave]
	call F_CMP_STRING
	cmp al,0
	je pgetDataForstartFile
	mov cl,[pfseachFilecxsave]
	mov bx,[pfseachFilebxsave]
	add bx,16
	loop startSeach
	xor bx,bx
	xor cx,cx
	xor ax,ax
	jmp exitpfseachFile
	pgetDataForstartFile:
	mov bx,[pfseachFilebxsave]
	mov di,bx
	dec di
	mov cl,[ds:di]
	dec di
	mov ch,[ds:di]
	mov al,ch
	dec di
	mov ch,[ds:di]
	dec di
	mov ah,[ds:di]
	exitpfseachFile:
ret

Запуск файла cmd.


START_PROGRAMM:
    mov si, DAPS_RUNTIME_FILE
    mov [si + 2], al
    mov [si + 8], cl
    LOAD_DAPS DAPS_RUNTIME_FILE
    ;LOAD_FILE ch, cl, al, 0000, 0500h
    NEW_LINE
jmp 0000:7E00h

DAPS программ.

DAPS_RUNTIME_FILE:
    db 16;const paksize
    db 0;null
    db 1;кол-во секторов
    db 0;null
    dw 7E00h;смещение
    dw 0;сегмент
    dq 7;начало

Код Ядра

org 0500h
GLOBAL:
mov [BOOT_DISK],dl
jmp START_K
;-----------------------------------------------------------
    include 'INCLUDESMACROS.INC'
    include 'INCLUDESBASE_FUNCTIONS.INC'
    include 'INCLUDESINTERRUPT_HANDLER_RETURN.INC'
    include 'INCLUDESMAIN_INTERRUPT_HANDLER.INC'
    include 'INCLUDESKEYBOARD.INC'
    include 'INCLUDESCONST.INC'
;-----------------------------------------------------------
START_K:
SET_INTERRUPT_HANDLER 90H,MAIN_INTERRUPT_HANDLER
SET_INTERRUPT_HANDLER 91H,INTERRUPT_HANDLER_RETURN
LOAD_DAPS DAPS_TABEL_FILES
PRINT HELLO_WORLD, BLACK
MAIN:
;NEW_LINE
;PRINT INPUT_STR, BLACK
;GET_STRING BUFFER, 13
SEACH_FILE TABLE_FILES, CMD
cmp ax,0
je PRINT_ERROR


cmp ah,1
je START_PROGRAMM
jmp MAIN

START_PROGRAMM:
    mov si, DAPS_RUNTIME_FILE
    mov [si + 2], al
    mov [si + 8], cl
    LOAD_DAPS DAPS_RUNTIME_FILE
    ;LOAD_FILE ch, cl, al, 0000, 0500h
    NEW_LINE
jmp 0000:7E00h

PRINT_ERROR:
    NEW_LINE
    PRINT ERROR, RED
jmp MAIN


RETURN_INT:
    jmp MAIN
;сюда передает управление int 91h
jmp $
DAPS_RUNTIME_FILE:
    db 16;const paksize
    db 0;null
    db 1;кол-во секторов
    db 0;null
    dw 7E00h;смещение
    dw 0;сегмент
    dq 7;начало
DAPS_TABEL_FILES:
    db 16;const paksize
    db 0;null
    db 1;кол-во секторов
    db 0;null
    dw TABLE_FILES;смещение
    dw 0;сегмент
    dq 1;начало
HELLO_WORLD: string "WaaOS Loaded, Hello! =)"
ERROR: string "Command not found :("
CMD: string "cmd"
INPUT_STR: string "user:>"
BOOT_DISK: db 0
BUFFER: db 13 dup(0)
TMP: db 255 dup(0)
TABLE_FILES:
CALC_SIZE SIZE_KERNEL, GLOBAL

Код CMD

#include "BASE_LIB.H"
void clear_str_file_name(u_char8 *str, u_char8 len){
    for(u_int16 i = 0; i < len; i++){
        str[i] = 0;
    }
}
void main(void)
{
    u_char8 user[] = "user:>";
    u_char8 not_found[] = "Command not found :(";
    while (true)
    {
        print(new_line, Black);
        print(user, White);
        f_string user_guffer = input();
        for(u_char8 i =0 ; i < 254; i++){
            if(user_guffer.data[i] == ' '){
                user_guffer.data[i] = 0;
            }
        }
        u_char8 file_name[13];
        clear_str_file_name(file_name, 13);
        for(u_char8 i = 0; i < 13; i++){
            if(user_guffer.data[i] == 0) break;
            if(user_guffer.data[i] == ' ') break;
            file_name[i] = user_guffer.data[i];
        }
        if(file_name[0] != ' ' && file_name[0] != 0){
            daps daps_file = get_r_daps_file(file_name, (u_int16) 0x07E00);
            print(new_line, Black);
            if(daps_file.p_empty != 1){
                start_programm(&daps_file, user_guffer.data);
            } else {
                print(not_found, Red);
            }
        }
    }
}

Заключение

Если вам интересно будет почитать про файловую систему, которая используется в этйо ОС, и библиотеку для СИ и доп. функции прерывания 90h, сделю вторую, третью и тд части. Спасибо что дочитали до конца.

Весь код ОС

Авторы: @lllzebralll @aovzerk

Философская…
Задание: выжать максимум производительности и функциональности из определенной аппаратуры для определенных задач.
Определенная аппаратура: процессор AMD Sempron 3300+ (Palermo (Venice-128), DH-E6)
матплата Epox EP-8KDA7I
озу DDR Patriot Memory PSD1G400
Hynix HYMD264 646D8R-D43
Определенная задача: высокоскоростной рендер данных заданной точности.

Как я оказался у данной аппаратуры и с данной задачей — долгая и скучная история о том, как таял парк хороших машинок с каждым рождением ребенков у знакомых и родственников; о том, как кто-то тратил время на воздух, и долго шел к своей

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

и придется задумываться уже над каждой строчкой. Отправной точкой для наших изысканий стоит считать литературу от производителя, и такая ,слава богу, есть. Архитектура AMD64 (описательные тома 24592, 24593, 24594, 26568, 26569) плюс

больше конкретики для моего семейства в томе 26094 «BIOS and Kernel Developer’s Guide for AMD Athlon TM 64 and AMD Opteron TM Processors»(в нем же указаны ссылки на еще более дополнительные писюльки), в совместительстве с технологией

Hypertransport 1.03 (просто надо иметь это в виду). Поковыряв первый том 24592, можно обнаружить ряд полезных качеств, скрытых в моем безнадежно устаревшем камешке : это 8 дополнительных регистров общего назначения, 8 дополнительных

регистров на функции SSE, единая несегментная адресация (за некоторым исключением), так называемая flat-модель памяти. Ой, да я забыл сказать, что все это доступно лишь только в long-mode, и максимальная отдача от этих плюшек будет лишь

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

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

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

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

так:

1. BOOTSTRAP:
#RESET => инициализация ядра (ноды) №0 процессора => инициализация Hypertransport в real-mode=> инициализация всех когерентностей на шине Hypertransport => инициализация всех некогерентностей на шине Hypertransport =>снятие сигнала

#RESET => AP инициализация остальных ядер (нод) процессора => BIOS.

2. BIOS:
Загрузка микропрограммы БИОС в память по адресу FFFF:0 с передачей управления => запуск POST => инициализация всех остальных железяк => распределение значений CMOS => поиск на указанных к загрузке устройств нулевого сектора с особой

сигнатурой 55ААh на конце => Загрузка кода с этого сектора в память по адресу 0000:7C00 => передача управления по тому же адресу => ЗАГРУЗЧИК (real-mode).

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;
;-Почему вы решили использовать именно ассемблер для этой цели?;;;;;;;
;-Говорить о преимуществах кодинга в том или ином ЯП, сравнивая лишь;;
;синтаксис и количество уже созданных библиотек — это своеобразная;;;;
;форма нацизма. В поисках преимуществ нужно опираться в первую;;;;;;;;
;очередь на развитие компилятора/интерпретатора. Особенно это;;;;;;;;;
;касается такого неокрепшего ума, как мой. Поэтому я выбрал FASM.;;;;;
;Говоря о ЯП более высокого уровня, нужно всегда учитывать опыт;;;;;;;
;других программистов, который заключен как в синтаксисе, так и в;;;;;
;работе определенной версии компилятора при решении различных типовых;
;задач. Ваш выбор — принимать этот опыт или нет. Я его отверг из-за;;;
;маниакальных соображений. Поэтому ассемблер: просто и глупо.;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;

3. ЗАГРУЗЧИК (real-mode)….

И вот тут уже и начинается наша ОС. Ну право же: винда начинается с MBR, линька с GPT, хотя загрузчиков куча, эти наиболее на слуху. Вообще, можно все тело ОС уместить на всю нулевую головку,то есть занять еще последующие 63 секторов,

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

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

— очень удобно, и я не один так считаю, наверное.

Ну вот, наше ТЗ начинает по-тихоньку формулироваться более конкректно:
Допустим, есть флешка FLASH. На нулевой сектор в первые 508 байт сторонним способом должен быть нанесен особый 16-битный код, поддерживающий real-mode, который быстренько переведет нашу систему в 64 бит и передаст управление 64-битному

коду, который начнется уже с 513 байта, и уже его дальнейшие манипуляции. Если нам не удастся «успеть» сделать такой подвиг в первые 508 байт, придется его отодвинуть в последующие после нулевого сектора. Теперь вопрос: допустим, нам это

удалось и 64 разрядная система ждет такой же 64-разрядной команды. Что дальше? Да, настало время пофилософить…

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;
;-Почему вы решили, что данные бредни «ОС на АСМ» интересны нам?;
;-Во-1х, я одинокий человек, и нуждаюсь хоть каком-то внимании.;
;Во-2х, я знаю, что это не 1-я и не последняя такая попытка.;;;;;
;Продемонстрированный мною опыт может быть полезен для тех, кто;;
;этим интересуется либо в рамках работы, либо саморазвития, ибо;;
;подводных камней в разработке высокопроизводительного ПО, пусть;
;встроенных решений или чисто для фана, на пути окажется немало,;
;а дети х86 архитектуры — так вообще лучшее место для отработки;;
;навыков поиска решений в, казалось, безвыходных ситуациях.;;;;;;
;В-3х, это, надеюсь, станет неплохим наглядным пособием по;;;;;;;
;анализу аппаратных возможностей и деликатному их использованию.;
;Так, люди смогут даже на другой архитектуре, создать некое;;;;;;
;подобие моей ОС, если она им так понравилась, без лишних;;;;;;;;
;скачиваний и последующего ругания разработчика, своими руками.;;
;В-4х, понятие совместимости весьма расплывчатое, ведь всегда;;;;
;можно поддерживать стандарт не только предложенными средствами.;
;Поэтому, если я отказываюсь от совместимости на уровне кода,;;;;
;это еще не значит, что я делаю абсолютно закрытую от мира ОС…;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;

Итак, ОС… Если делать всеядную и вездезапускаемую операционку, страдающую повышенной гиперактивностью и устойчивостью к смерти, то с ассемблером этот процесс улетит в десятилетия, а о компактности кода можно забыть. Ассемблер не умеет

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

Однозадачная DOS уже более похожа на необходимый идеал, но не совсем… Получается даже не операционная система, а некая эфимерная среда исполнения приложений в рамках аппаратного АПИ. Иными словами, мы создаем приложение, которое

оформляет как надо процессор и что-нибудь еще, а затем по указанию программиста или кода, запускает следующее ничем не связанное приложение, которое может впоследствии вызвать другое ничем не связанное приложение, и должно работать в

рамках некоторых правил, чтобы наш карточный домик не развалился.
Итак, правила: допустим, весь код приложения отображается в памяти непрерывным куском от НАЧАЛО-АДРЕС до КОНЕЦ-АДРЕС. Необходимо его минимально рабочее ядро разместить внутри уже новой программы так, чтобы все было, и нам за это ниче не

было. Вот и он — первый подводный камень, который будем решать сначала алгоритмически а потом и программно.
[продолжение следует]…

Понравилась статья? Поделить с друзьями:
  • Как написать свою операционную систему на python
  • Как написать свою операционную систему для телефона
  • Как написать свою новеллу
  • Как написать свою нейросеть на python
  • Как написать свою научную статью