Время на прочтение
3 мин
Количество просмотров 38K
Всем привет! Сегодня мы напишем загрузчик, который будет выводить «Hello World» и запустим его на VirtualBox. Писать будем на ассемблере FASM. Скачать его можно отсюда. Также нам понадобится собственно VirtualBox и UltraISO. Перед тем как писать код, разберемся как загружаются операционные системы.
Итак, когда мы нажимаем большую кнопку включения на нашем компьютере запускается система, которая есть на любом компьютере — BIOS (Basic Input/Output System или базовая система ввода/вывода). Задача BIOS это:
- Обнаружить все подключенные устройства и проверить их на работоспособность. За это отвечает программа POST (Power On Self Test, самотестирование при включении). Если жизненно необходимое железо не обнаружено, то системный динамик (если таковой имеется) пропищит что-то непонятное и дальше загрузка не пойдет.
- Предоставить операционной системе функции для работы с железом.
- Считать самый первый сектор загрузочного устройства в нулевой сегмент оперативной памяти по смещению 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), к исходному коду которой вы можете обращаться в поисках вдохновения
Подпрограммы библиотеки 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 полно полезных подпрограмм — внимательно присмотритесь к их реализациям
Предыдущие статьи из серии «Школа ассемблера»:
- «Начинаем программировать на языке ассемблера: переход на уровень аппаратного обеспечения»
- «Школа ассемблера: условные инструкции, циклы и библиотеки»
- «Начинаем программировать на языке ассемблера»
Если вам понравилась статья, поделитесь ею с друзьями:
Создание мини-ОС
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/
— исходный код ОС
Установка и запуск
- Установить эмулятор QEMU (подробнее: https://www.qemu.org/download/)
sudo apt install qemu-kvm qemu
- Собрать кросс-компилятор 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
- Клонировать и собрать проект
git clone https://github.com/thedenisnikulin/os-project
cd os-project/src/build
make
- Запустить образ ОС с помощью эмулятора
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 это:
- Обнаружить все подключенные устройства и проверить их на работоспособность. За это отвечает программа POST (Power On Self Test, самотестирование при включении). Если жизненно необходимое железо не обнаружено, то системный динамик (если таковой имеется) пропищит что-то непонятное и дальше загрузка не пойдет.
- Предоставить операционной системе функции для работы с железом.
- Считать самый первый сектор загрузочного устройства в нулевой сегмент оперативной памяти по смещению 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 уже более похожа на необходимый идеал, но не совсем… Получается даже не операционная система, а некая эфимерная среда исполнения приложений в рамках аппаратного АПИ. Иными словами, мы создаем приложение, которое
оформляет как надо процессор и что-нибудь еще, а затем по указанию программиста или кода, запускает следующее ничем не связанное приложение, которое может впоследствии вызвать другое ничем не связанное приложение, и должно работать в
рамках некоторых правил, чтобы наш карточный домик не развалился.
Итак, правила: допустим, весь код приложения отображается в памяти непрерывным куском от НАЧАЛО-АДРЕС до КОНЕЦ-АДРЕС. Необходимо его минимально рабочее ядро разместить внутри уже новой программы так, чтобы все было, и нам за это ниче не
было. Вот и он — первый подводный камень, который будем решать сначала алгоритмически а потом и программно.
[продолжение следует]…