И года не прошло с момента публикации мной последней статьи*, как я... Я нет, кажется год как-раз прошел... Ну и ладно! Хабр, привет!
* тут такое дело - прошло :)
сейчас 17 мая, и я только заканчиваю работу над статьей.
SectorOS (SOS) – это небольшая операционная система (далее для удобства я буду использовать сокращения "ось" или "ОС"), написанная на ассемблере x8086, умеющая запускать пользовательские программы и предоставляющая для этих программ минимальный интерфейс взаимодействия со своей собственной файловой системой – SFS, но обо всем по порядку.
Важно
эта статья подразумевает что вы знаете достаточно об ассемблере и у вас не возникает вопросов "а что такое mov ax, [es:si]". Если же вы все равно не понимаете код я советую кинуть его в нейронку и попросить объяснить, я думаю она разжует его лучше, чем я.
Объяснять что такое прерывания и их функции я не буду.
Вдохновитель и идея
Если название этой статьи вам что-то напоминает, то вы, наверное читали статью SectorC: компилятор Си в пределах 512 байт. И я её обожаю: перечитывал раза 3, и каждый раз интересно.
Но, наверное, пол года назад я нашел прекрасный проект: x16-PRos, я влился в его комьюнити, и, пообщавшись с обитателями, внеся свой вклад в проект – написав ed (текстовый редастор) и чуть улучшив тамошний shell, – где-то на подкорке сознания ко мне закралась мысль: "надо написать свою ОС".
Я думаю, всем очевидно, что человек, который писал язык ради экономии 15 байт, просто так писать ось не будет... Но идей по "приколофикации" этой идеи у меня долго не было, пока, наконец, я не вспомнил о, уже вышеупомянутой, статье про SectorC – было решено: "Буду писать ось размером в сектор - SectorOS"
Небольшая table of contents
Состав ОС
Цикл ОС
Детали
Первые шаги SectorOS
Про SFS
Инициализация работы с SFS
Основной цикл терминала
Ввод пользователя
Splitting ввода пользователя
Запуск программы
Ошибки
Прерывания
int 0x23
int 0x21
int 0x22
Для желающих запустить
Итог
А что вообще можно назвать "операционной системой"?
Ну для начало нигде не написано "ОС - это то, что...", эти границы придется определять самим.
Я всегда считал, что "ОС - это то, что в том или ином виде может передать управление данными пользователю"... замудрено да?
Ну, например, ваши данные - это научная работа о корреляции числа файлов, имеющих расширение .rs в проекте, и длинной чулков его создателя. Скорее всего ваша работа будет проектом MS Word-а в формате .docx.
В таком случае инструментом для управления этими данными будет MS Word (ну или LibreOffice если длинна ваших чулок имеет коэффициент, равный двум)
Это конечно все классно, но давайте ближе к конкретному списку того, что я собираюсь реализовать (ну или хотя бы попытаться)
запуск программ с диска с возможностью передавать программам аргументы
создание, удаление, чтение и запись в файлы
управление питанием (выключение и перезагрузка)
Важно сказать, что под "реализацией ОС" я имею ввиду не только ядро, но и стандартный пак программ. На самом ядре будет лежать ответственность за запуск программ (то бишь shell), минимальную работу с файловой системой и... и все, больше в 512 байт впихнуть у меня не получилось
Не густо... Но у меня есть оправдание: по большому счету, сама 16-ти битная ОС - это лишь терминал, для запуска программ (ну и осуществляет работы с файловой системой конечно, хотя это и может лежать на программах), а все остальное - сами программы.
Если бы я писал программы для работы с графикой, датой/временем, сторонними устройствами эта статья бы вышла в... не вышла бы...
Цикл ОС
Для пользователя, когда он запускает комп, происходит следующее: экран очищаеться и... и все - он в терминале. Пользователь набирает условную строку
touch hello.txt
ОС вычленяет слово touch, ищет программу с таким же именем, загружает в память и передает на неё управление. После окончания программы ОС снова ждет ввод от пользователя - все как и в других ОС. Только вот "под капотом" все устроено гораздо интересней.
Пристегивайте ремни - я собираюсь разобрать все 300 строк кода SectorOS
Первые шаги SectorOS
Как и у большинства загрузчиков первым кодом, что выполняется в SectorOS является настройка сегментов, стека и загрузка прерываний (так же тут есть очистка экрана, чтобы пользователь не читал тирады о том как BIOS искал наш загрузчик):
cli ; настройка сегментов и стэка xor ax, ax mov ds, ax mov es, ax mov ax, 0x1000 mov ss, ax mov sp, 0xFFFE ; загрузка прерываний mov di, 0x21 * 4 mov si, int_table mov cx, 3 .set_ivt: lodsw stosw mov ax, cs stosw loop .set_ivt sti ; очистка экрана mov ax, 0x0600 mov bh, 0x0F xor cx, cx mov dx, 0x184F int 0x10 mov ah, 0x02 xor bh, bh xor dx, dx int 0x10 ; код ос, бла, бла, бла ; список прерываний в конце файла int_table dw int_get_file_text ; int 0x21 dw int_set_file_text ; int 0x22 dw int_get_file_header ; int 0x23
Ничего необычного, кроме, кода загрузки прерываний, ведь он немного минимизирован - загрузка производиться из подготовленного списка адресов (простые мувы занимали слишком много места в итоговом бинарнике), и того, что прерывания на всю ос 3 штуки, но про это мы ещё поговорим.
SectorOS File System (SFS)
Перед следующими главами важно поговорить о том, как SectorOS видит файлы - про файловую систему
Скорее всего вы знаете, что диск оперирует не байтами, а секторами - блоками по 512 байт. В SFS за несколькими первыми секторами закреплены четкие роли:
1-й сектор - сама SectorOS
2-й сектор - SFSDD (SectorOS File System Disk Data)
здесь пока что занято только одно слово (2 байта) - последний свободный сегмент, но не удивляйтесь такой растрате места - это ещё цветочки. Взамен размеру SectorOS пришлось сделать очень прожорливую файловую систему :)
3-й сектор - FHT (File Headers Table) - самое интересное
FHT - это массив заголовков файлов каждый заголовок представляется такой структурой
struct FileHeader { char file_name[11]; uint8_t file_type; uint16_t data_start_sec; uint16_t data_size; };
file_name - это null-padded название файла, то-есть оно должно не просто заканчиваться нулем, а все неиспользуемые символы должны быть нулями
file_type - уникальное свойство SFS - это буква, являющаяся маркером для системы и программ, например, если в этом поле 'E' - то этот файл может быть исполнен как программа
data_start_sec - это номер сектора (счет начинается с нуля), с которого начинаются данные. То-есть два файла не могут делить сектор, файл всегда начнется с нового сектора.
data_size - размер данных файла в байтах, ничего необычного.
Даже если размер файла - 12 символов, занимать он будет весь сектор
Давайте посмотрим на дамп диска:
00000400: 7368 7574 646f 776e 0000 0045 0300 2a00 shutdown...E..*. 00000410: 7265 626f 6f74 0000 0000 0045 0400 1000 reboot.....E.... 00000420: 6865 6c70 0000 0000 0000 0045 0500 7401 help.......E..t. 00000430: 6469 7200 0000 0000 0000 0045 0600 dc00 dir........E....
Здесь видно что по смещению 0x400 (третий сектор) находиться эта самая таблица.
мы видим название файла.
После которого нулями дополнены 11 байт, после 45 - это код символа 'E' в кодировки шрифта биоса, она означает что файл - это программа.
Далее идет два двух байтовых поля в lettle-endiane - номер сегмента данных и размер соответственно.
4-й сектор и выше - данные файлов
Давайте разберем примерчик и закончим с файловой системой: файл test.txt с текстом
Hello, its test text file for SectorOS Writen by Desvor. YOOOO!!!
Теперь сгенерируем диск .img с помощью python скрипта mksf.py (вынужден признать что его написала нейронка, тк мне было жутко лень), написав такой конфиг:
{ "size": "4K", // размер итогового диска "boot": "sos.bin", // файл бут-лоадер (в нашем случае самой SectorOS) "data": [ // данные диска { "type": "F", // тип файла "name": "test.txt", // имя файла на диске "data": "test.txt" // имя файла, откуда скрпит возмет данные для записи } ] }
Наконец откроем дамп сгенерированного диска с SFS и увидим следующее (с моими комментариями):
00000000: fa31 c08e d88e c0b8 0010 8ed0 bcfe ffbf .1.............. 1 сектор (номер 0) ... код SectorOS 000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U. 00000200: 0400 0000 0000 0000 0000 0000 0000 0000 ................ 2 сектор (номер 1) ^--- это номер следующего свободного сектора в lettle endiane 00000400: 7465 7374 2e74 7874 0000 00 46 0300 4700 test.txt...F..G. 3 сектор (номер 2) |-------------------------| ^- ^--- ^--- имя заполнено 0 до 11 байт тип номер сегмента с данными размер данных в байтах .. куча нулей 00000600: 4865 6c6c 6f2c 2069 7473 2074 6573 7420 Hello, its test 00000610: 7465 7874 2066 696c 6520 666f 7220 5365 text file for Se 00000620: 6374 6f72 4f53 0d0a 0d0a 5772 6974 656e ctorOS....Writen 00000630: 2062 7920 4465 7376 6f72 2e0d 0a59 4f4f by Desvor...YOO 00000640: 4f4f 2121 210d 0a00 0000 0000 0000 0000 OO!!!........... собственно вот и текст файла ... куча нулей
Инициализация работы с SFS
После того, как мы разобрали устройство SFS, давайте поговорим о подготовке системы к работе с SFS, но для начало я расскажу как нам, со стороны ассемблирятора получать данные с диска и писать их обратно.
За работу с диском отвечает прерывание 0x13, нам понадобятся всего две его функции - 0x42 (чтений) и 0x43 (запись). Обе эти функции работают со специальной DAP структурой, ей формат вот такой:
align 4 DAP_struct: .DAP_size db 0x10 .res db 0x00 .sec_num dw 1 .buf_ptr dw 0x0800 dw 0x0000 .sec_ptr dq 2
aling 4 - это выравнивание по 4 байтовой границе, тк это требуют некоторые BIOS.
DAP_size - это байт, хранящий размер DAP структуры, и это всегда 0x10 (это поле создавалось "на вырост", на случай добавления в структуру чего-то ещё, но этого так и не случилось)
res - зарезервированный байт для нужд BIOS (всегда 0)
sec_num - количество секторов для чтения
buf_ptr - обычный far указатель (первые два байта - смещение, вторые два - сегмент) на буфер "куда записывать" для 0x42, или "откуда записывать" для 0x43
sec_ptr - аж 8 байт, хранящих номер сектора с которого начнется работа (чтение или запись)
В таком виде DAP структура храниться изначально
Теперь можно и на код посмотреть
pop dx ; load FHT mov ah, 0x42 mov si, DAP_struct int 0x13 jc err_de mov word [DAP_struct.sec_ptr], 1 mov word [DAP_struct.buf_ptr], 0x0600 ; load SFSDD mov ah, 0x42 mov si, DAP_struct int 0x13 jc err_de mov [0x602], dl
Тут достаем dl (номер диска) и после комментария "load FHT" идет код загрузки 3-го (в DAP структуре номер сегмента указывается с нуля) сектора (наша таблица файлов).
ah - номер функции (в нашем случае чтение - 0x42)
dl - номер диска (его мы pop-нули ранее)
ds:si - DAP структура
Если при работе произошла ошибка связанная с диском, то прерывание 0x13 установить флаг CF и мы сможем это отловить переходом jc. (код err_de мы разберем чуть позже)
Далее мы меняем номер сектора на 1 (сектор информации о диске SFSDD) и буфер на 0x600
Из этого кода можно понять, можно понять, что FHT лежит по адресу 0x800, а до неё целые 512 байт места, ради 2 байт данных, занимает SFSDD сектор
Основной цикл терминала
Ввод от пользователя
После всех немногочисленных настроек SectorOS переходит в терминал:
для начала читаем строку от пользователя:
shell_loop: mov di, 0x500 xor cx, cx .read_char: xor ah, ah int 0x16 cmp al, 0x0D jz .done cmp al, 0x08 jz .backspace stosb mov ah, 0x0E int 0x10 inc cx jmp .read_char .backspace: test cx, cx jz .read_char mov ah, 0x0E int 0x10 mov al, ' ' int 0x10 mov al, 0x08 int 0x10 dec di dec cx jmp .read_char .done: mov byte [di], 0
К сожалению здесь нету ничего необычного, и разбирать здесь просто нечего (об этой проблеме этого проекта я поговорю в итогах).
Splitting строки
После прочтения команды в буфере по адресу 0x500 находиться, например, такая строка
touch hello.ext
Что бы запустить программу мы должны получить её имя из всей команды.
За это отвечает следующий код:
mov ax, 0x0E0A int 0x10 mov al, 0x0D int 0x10 mov si, 0x500 mov di, 0x580 .slice_loop: lodsb cmp al, ' ' jbe .slice_done stosb jmp .slice_loop .slice_done: xor al, al mov cx, 11 rep stosb
сначала мы переносим строку для красоты.
тут можно увидеть один прием, встречающийся по всему коду
mov ax, 0x0E0A
вместо
mov ah, 0x0E mov al, 0x0A
Очевидно, что второй вариант читаемей, но первая запись экономит нам целый байт!
Как бы это не звучало, но в моменты написания кода, не задумываясь о размере, это очень экономит время и силы
Идя далее по коду можно понять что копируем мы из 0x500 в 0x580, а следовательно команда может быть всего 64 байта длинной, тк все, что выше, будет затерто при копировании имени программы.
В конце мы видим один ход:
xor al, al mov cx, 11 rep stosb
Мы добавляем 11 байт нулей после копирования, тк, как мы помним, имя файла должно быть null-padded (почему это так мы поговорим, когда будет рассматривать прерывания), и делаем это минимальным способом - через префикс rep.
Запуск
После того, как мы узнали имя программы, которую нужно запустить, мы загружем её в память, передаем управление, и по окончанию, прыгаем в начало shell loop-а:
mov si, 0x580 mov dx, 0x2000 int 0x21 cmp ah, 1 jz err_fnf ja err_de cmp al, 'E' jnz err_wft mov ax, 0x2000 mov ds, ax mov es, ax call 0x2000:0x0000 xor ax, ax mov ds, ax mov es, ax jmp shell_loop
Для загрузки данных из файла в память используется собственное прерывание SectorOS 0x21. В ds:si оно принимает строку с именем файла для загрузки (в нашем случае это имя программы), а в dx - номер сегмента, в который нужно будет выгрузить текст программы.
После вызова прерывания в ah оно кладет код ошибки (0 - успешно чтение, 1 - File not found, 2 - Disk error), а в al - тип прочтенного файла.
В нашем случаает если тип не E - это не исполняемый файл и мы выводим ошибку Wrong file type.
Если же все в порядке, то мы настраиваем сегменты ds и es и вызываем код программы, после отработки которого, мы снова затираем es и ds.
Ошибки ОС
Что ж, во и время для разбора тех самых меток err_fnf, err_de, err_wft:
err_fnf: mov ax, 0x0946 ; 0x46 - F jmp general_err err_de: mov ax, 0x0944 ; 0x44 - D jmp general_err err_wft: mov ax, 0x0957 ; 0x57 - W general_err: mov cx, 1 mov bx, 0x04 int 0x10 mov ah, 0x0E int 0x10 mov al, 0x0A int 0x10 mov al, 0x0D int 0x10 jmp shell_loop
На самом деле мы видим тут достаточно популярный "патерн" для ассемблера:
Ошибки отличаются только сообщением (в нашем случает сообщение - это одна буква - F - "File not found", W - "Wrong file type", D - "Disk error"), а код вывода этого символа одинаков, поэтому мы можем менять только сам символ и прыгать на метку, где происходит вывод символа и возврат в shell_loop.
Тут, в силу компактности, в err_fnf, err_de и err_wft заполняеться весь ax, так как это компактней, чем если бы мы заполняли ah в general_error.
Собственно поговорим ещё о выводе символа: находимся мы в режиме VGA 80x25, а следовательно, функция 0x0E прерывания 0x10 не умеет выводить символы, разными цветами. Для того чтобы окрасить букву ошибки в красный мы будем использовать функцию 0x09 прерывания 0x10 - печать символа с атрибутами, но дело в том, что это прерывание не двигает каретку, следовательно нам нужно будет вызвать 0x0E для "подтверждения" символа и сдвига каретки.
вот код с моими обьяснялкиными:
general_err: ; в функции 0x09 в cx помещаеться количестао символов, которе нужно напечатать mov cx, 1 mov bx, 0x04 ; печатаем символ (ah (номер функции) и al (символ) ; были заполнены выше в одной из меток ошибок) int 0x10 ; теперь "подтверждаем" символ функцией 0x0E mov ah, 0x0E int 0x10 ; переносим строку mov al, 0x0A int 0x10 mov al, 0x0D int 0x10 ; домой, уолтер jmp shell_loop
Прерывания
По всем законам логики, начнем мы с последнего по номеру прерывания - 0x23 GetFileHeader
int 0x23 GetFileHeader
Это прерывание позволяет получить FileHeader.
В ds:si оно принимает null-padded имя файла.
Возвращает в ah код ошибки и в es:ds - FileHeader
Его величество код:
; int 0x23 ; in: ds:si - name of file ; out: ah - err code ; 0 - ok ; 1 - fnf ; es:di - file header ; destruct: es, ax, bx, cx, si, di int_get_file_header: xor ax, ax mov es, ax mov di, 0x0800 .search_loop: cmp byte [es:di], 0 jz .err_fnf push si push di mov cx, 11 repe cmpsb pop di pop si jz .search_done add di, 0x10 jmp .search_loop .search_done: xor ah, ah iret .err_fnf: mov ah, 1 iret
Ну первое же, что бросается в глаза в "оглавлении" это destruct. Прерывание? разрушает регистры?
Да! И тут вполне понятная причина: ОС написана для процессора 8086, а команд pusha и popa для сохранения всех регистров в пару байт в нем нет, они появились только в 80186, а если по отдельности пушить каждый регистр отдельно, то исходный бинарник раздуется до размеров, что llvm сможет позавидовать.
В коде все ещё ничего необычного нету, кроме, разве что, кода сравнения имен:
push si push di mov cx, 11 repe cmpsb pop di pop si jz .search_done
Использую я тут связку repe cmpsb, которая позволяет сравнивать байты из ds:si и es:si 11 раз (cx = 11) и пока флаг ZF равен нулю (то-есть два байта равны). Именно поэтому все имена должны быть дополнены нулями до 11 байт, чтобы мусор, лежащий после null-terminator-а не помешал сравнению. Такой код сэкономил мне около 12 байт, а это достаточно много для такого проекта.
int 0x21 GetFileText
Это прерывание уже нужно для чтения данных из файла в сегмент памяти.
Для начала давайте взглянем на заголовок:
; int 0x21 ; in: ds:si - name of file ; dx - segment to writing ; out: ah - err code ; 0 - ok ; 1 - fnf ; 2 - de ; al - file type ; bx - file size in bytes ; destruct: es=0, ds=0, si, di
В ds:si помещается null-padded имя файла,
В dx - номер сегмента, в который будет прочтен текст файла (да, прочесть его в произвольный буфер внутри сегмента нельзя, тк текст может занять весь сегмент, а проверку пересечения границ и тд делать было накладно по размерам бинарника).
Вернет прерывание нам код ошибки в ah, тип файла в al и в bx размер файла в байтах
И снова код:
int_get_file_text: int 0x23 test ah, ah jnz .err xor ax, ax mov ds, ax mov ax, [di + 12] mov [DAP_struct.sec_ptr], ax mov ax, [di + 14] add ax, 0x1FF mov cl, 0x09 shr ax, cl mov [DAP_struct.sec_num], ax mov word [DAP_struct.buf_ptr], 0 mov word [DAP_struct.buf_ptr + 2], dx mov ah, 0x42 mov dl, [0x602] mov si, DAP_struct int 0x13 jc .err_de mov al, [di + 11] mov bx, [di + 14] xor ah, ah iret .err_de: mov ah, 2 .err: iret
Здесь мы видим что прерывание из под себя вызывает другое прерывание - 0x23 для получения заголовка нужного файла.
Далее, занулив ds для более легкой адресации без постоянного es:di (тк это добавляет байт префикса к каждой команде), мы настраиваем DAP структуру. Единственное что тут стоит внимания это строки
mov ax, [di + 14] add ax, 0x1FF mov cl, 0x09 shr ax, cl mov [DAP_struct.sec_num], ax
Тут происходит заполнения поля sec_num - количество секторов для прочтения - как мы помним размер в заголовке файла указывается в байтах, и чтобы получить его в секторах по 512 байт мы реализуем следующую формулу:
511 надо добавить, так как, если просто делить на 512, при, например размере в 513 байт, деление даст нам 1 сектор, что неверно.
Так же в программировании разумней использовать сдвиг вправо на 9 (равносильно делению на 512). Тут же мы натыкаемся на ограничение процессора 8086 - возможность выполнять сдвиг только на значение из регистра cl, поэтому мы сначала пишем в cl 0x09, а потом выполняем сдвиг.
int 0x22 SetFileText
а вот и самое большое прерывание - 0x22. Для начала заголовочек:
; int 0x22 ; in: ds:si - name of file ; dx - segment for writing ; cx - size of text in bytes ; out: ah - err code ; 0 - ok ; 1 - fnf ; 2 - de ; destruct: es=0, ds=0, ax, cx, bx, si, di
Собственно на вход мы даем прерыванию:
ds:si - уже стандартно, имя файла
dx - сегмент для записи данных из него в файл
cx - размер данных для записи
А на выход получаем только ah - уже привычный нам код ошибки
А теперь к коду:
int_set_file_text: push cx int 0x23 pop cx test ah, ah jnz .err xor ax, ax mov ds, ax mov bx, [di + 14] add bx, 0x1FF push cx mov cl, 9 shr bx, cl pop cx test bx, bx jnz .bx_not_null inc bx .bx_not_null: mov [di + 14], cx add cx, 0x1FF mov cl, 9 shr cx, cl cmp cx, bx ja .need_alloc mov bx, cx jmp .write .need_alloc: push dx push bx mov [di + 14], bx mov cx, [0x600] mov [di + 12], cx add [0x600], bx mov ax, 0x0301 mov cx, 0x0002 mov dx, 0x0080 mov bx, 0x0600 int 0x13 mov ax, 0x0301 mov cx, 0x0003 mov dx, 0x0080 mov bx, 0x0800 int 0x13 pop bx pop dx jc .err_de .write: mov ax, [di + 12] mov [DAP_struct.sec_ptr], ax mov [DAP_struct.sec_num], bx mov word [DAP_struct.buf_ptr], 0 mov word [DAP_struct.buf_ptr + 2], dx mov ax, 0x4300 mov dl, [0x602] mov si, DAP_struct int 0x13 jc .err_de xor ah, ah iret .err_de: mov ah, 2 .err: iret
Много да? Ща разберемся.
Итак, сначала мы вызываем int 0x23, сохранив регистр cx (так как int 0x23 разрушает cx), и при ошибки просто возвращаем её:
push cx int 0x23 pop cx test ah, ah jnz .err
Далее идет блок проверок размера, ведь если размер текста, который мы хотим записать в файл, превышает размер этого файла в секторах на диске, нам нужно выделить новый блок секторов под новый текст.
mov bx, [di + 14] add bx, 0x1FF push cx mov cl, 9 shr bx, cl pop cx test bx, bx jnz .bx_not_null inc bx .bx_not_null: mov [di + 14], cx add cx, 0x1FF mov cl, 9 shr cx, cl cmp cx, bx jbe .need_alloc mov bx, cx jmp .write
Собственно, после зануления ds выше, мы копируем в bx размер файла из его FileHeader (его мы получили из прерывания 0x23) и переводим его в размер в секторах по знакомой нам формуле, сохраняя перед делением cx - ничего необычного.
на этом этапе важно, чтобы в bx не оказался 0 (что бывает, если мы создали новый файл), поэтому со строк 8 по строку 11 идет проверка.
Далее, независимо от результата bx мы сохраняем в поле размера файла размер из cx.
После, мы проводим преобразование размера уже для cx (размера текста для записи)
Теперь мы сравниваем два размера - bx и cx, если cx >bx, то мы должны перевыделить сектора для записи текста, в ином случае просто копируем cx в bx, для дольнейшего использования только регистра bx.
Собственно, если размер нового текста оказался больше нужного код прыгает на need_alloc:
.need_alloc: push dx push bx push cx mov cx, [0x600] mov [di + 12], cx pop cx add [0x600], cx ; запись SFSDD mov ax, 0x0301 mov cx, 0x0002 mov dl, [0x602] mov bx, 0x0600 int 0x13 ; запись FHT mov cx, 0x0003 mov bx, 0x0800 int 0x13 pop bx pop dx jc .err_de
Здесь мы сохраняем dx и bx, так как затронем их в коде, после чего на время сохраняем и cx, копируем в него значение первого же свободного сектора (храниться оно по аддресу 0x600) и записываем новое значение в FileHeader.
После достаем cx и прибавляем размер файла в секторах, чем теперь и является cx к значению первого свободного сектора.
После записи, мы должны записать на диск SFSDD и FHT. Для этого я использую более легкую функцию прерывания 0x10 0x03 - более простую версию 0x43 с DAP структурой. А поскольку, ax и dl не изменился, мы их не перезаписываем при записи FHT - простейшая экономия места.
Из этого кода следует, что сектора со старым текстом будут навсегда утеряны, пока вы ручками их не подвигаете :)
Ну и место, где происходит непосредственная запись в файл:
.write: mov ax, [di + 12] mov [DAP_struct.sec_ptr], ax mov [DAP_struct.sec_num], bx mov word [DAP_struct.buf_ptr], 0 mov word [DAP_struct.buf_ptr + 2], dx mov ax, 0x4300 mov dl, [0x602] mov si, DAP_struct int 0x13 jc .err_de xor ah, ah iret .err_de: mov ah, 2 .err: iret
Ээээм... ну тут действительно нечего говорить... Самый обычный код...
Для желающих запустить ОС
Немного информации о том, как её запустить и попытать на своем компе:
Запуск "с места"
Для запуска вам понадобиться: nasm, make, qemu, python - стандартный набор утилит, для OSdev-а.
Для быстрого запуска его отдельная make-задача:
make run
она вам соберет стандартный диск, саму ОС, все программы и запустит все через qemu.
Получения img диска
Если же вам нужен только диск в формате .img вы можете использовать задачу all:
make
после этого в рабочей директории появиться disk.img - как раз то, что вам нужно.
Настройка диска, добовления своей программы, или текстового файла
Если вы хотите добавить свой текстовый файл в систему, то откройте файл disk_example.sfs. Там вы увидите следующее:
{ "size": "10K", "boot": "sos.bin", "data": [ { "type": "E", "name": "shutdown", "data": "shutdown.bin" }, { "type": "E", "name": "reboot", "data": "reboot.bin" }, // куча программ { "type": "F", "name": "test.txt", "data": "test.txt" } ] }
тут вы должны добавить запись о своем файле:
{ "size": "10K", "boot": "sos.bin", "data": [ { "type": "E", "name": "shutdown", "data": "shutdown.bin" }, { "type": "E", "name": "reboot", "data": "reboot.bin" }, // куча программ { "type": "F", "name": "test.txt", "data": "test.txt" }, { "type": "F", "name": "my_SOS_file", // имя файла, которое будет видеть SectorOS "data": "my_file" // имя файла, текст из которого будет скопирован в файл в SectorOS } ] }
В начале файла вы можете видеть поля size и boot - это размер диска и файл, для размещения в первом секторе соответственно.
Если же вы написали программу, то вы также должны указать запись в .sfsd файле, но тип обязательно дожен быть 'E'. Также вы должны указать её в Makefile, для того, чтобы она автоматически компилировалась:
# это самое начало файла PROGRAMS := hello \ help \ dir \ memd \ rdsd \ cat \ shutdown \ reboot \ touch \ wrt \ del
Тут вы добовляете свою программу, которая должны находиться в папке programs/ и иметь расширение .asm, которое вы не указываете. на выходе получиться файл .bin.
Итог
Собственно из последней фразы в прерываниях можно выстроить весь итог - этот проект, в отличие от того же SectorC - это не набор интересных решений по оптимизации места. Это очень кастрированная ОС.
Я почти что вымучивал эту статью, так как, по факту в ОС нечего сокращать и упрощать, кроме файловой системы. И это грустна. У меня даже появилась идея, как можно реализовать директории в SFS, но я, к моменту этой идеи уже начал уставать от проекта, я банально на половине статьи уже не сильно был заинтересован этим проектом, да и писать особо не о чем было, а просто сделать статью, мол, "Глядите! я сделал ОС в секторе и... И все!" я не хотел. Плюс, обьяснять код, написанный на ассемблере, я просто не мог, тогда бы статья была бы на час прочтения, а тем, кто ассемблер уже знает, не составит труда прочитать код без моей болтовни - сам по себе он не очень сложный.
И в итоге эта статья мне не очень нравиться, я начал писать её, будучи уверенным в её интересности, а закончил с ощущением "Да наконец-то!". Поэтому я реально прошу прощения за отнятое у вас время на прочтение этой статьи.
Чуть позже, когда у меня снова появиться желание позаниматься этим проектом я сделаю директории, расширю стандартный пак программ, но писать ещё одну статью - излишне, дополнять эту - никто не прочитает.
Статью я писал ещё и с головными болями, слабостью, так что жду нового карантина :)
А ещё и ОГЭ скоро...
Чтож, а на этом я, пожалуй, закончу:
Большое спасибо создателю и перевотчику статьи об SectorC,
А сурсы SectorOS вы сможете потрогать туть,
Спасибо большое PRox-у за прекрасное комьюнити, которое натолкнуло меня на мысль об этом проекте.
Всем. Большое. Спасибо. За прочтение.