*Если параметры пользовательской программы указывают на часть памяти на странице 3 (последняя страница), существует конфликт, поскольку ядро всегда будет отображать свою страницу оперативной памяти внутри этой той же страницы во время Syscall. Таким образом, он переназначит страницу пользователя 3 на страницу 2 (третья страница), чтобы получить доступ к параметрам программы. Конечно, в случае, если параметры являются указателями, они будут изменены, чтобы позволить им указывать на новый виртуальный адрес (другими словами, указатель будет вычтен на 16 КБ, чтобы позволить ему указывать на страницу 2).
Чтобы иметь возможность порта 8-битной ОС на основе Z80 на основе Z80, которые не имеют организованного MMU/памяти, как показано выше, ядро имеет новый режим, который можно выбрать через menuconfig : No-MMU.
В этом режиме код ОС по -прежнему будет отображаться в первых 16 КБ памяти, от 0x0000 до 0x3FFF , а остальное, как ожидается, будет ОЗУ.
В идеале, 48 КБ оперативной памяти должно быть нанесено на карту, начиная с 0x4000 и будет подняться до 0xFFFF , но на практике можно настроить ядро, чтобы ожидать меньше, чем это. Для этого две записи в menuconfig должны быть настроены соответствующим образом:
KERNEL_STACK_ADDR : это знаменует собой конец области барана ядра, и, как утверждает его название, будет нижней частью стека ядра.KERNEL_RAM_START : Это знаменует собой начальный адрес оперативной памяти ядра, где будут сохранены стек, все переменные, используемые ядром и драйверами. Конечно, он должен быть достаточно большим, чтобы хранить все эти данные. Для получения информации текущий размер секции BSS ядра составляет около 1 кб. Глубина стека зависит от реализации целевых драйверов. Распределение 1 КБ для стека должно быть более чем достаточно, если на нем нет (больших) буферов. В целом выделение не менее 3 КБ для ядра ОЗУ должно быть безопасным и защищенным от будущего.Подводя итог, вот диаграмма, чтобы показать использование памяти:
Что касается пользовательских программ, адрес стека всегда будет установлен на KERNEL_RAM_START - 1 от ядра до выполнения. Это также соответствует адресу его последнего байта, доступного в его полезном адресном пространстве. Это означает, что программа может определить размер доступной оперативной памяти, выполнив SP - 0x4000 , который дает в сборке:
ld hl, 0
add hl, sp
ld bc, -0x4000
add hl, bc
; HL contains the size of the available RAM for the program, which includes the program's code and its stack.
Z80 представляет несколько регистров общего назначения, не все из них используются в ядре, вот охват каждого из них:
| Зарегистрировать | Объем |
|---|---|
| AF, BC, DE, HL | Система и приложение |
| Af ', bc', de ', hl' | Прерывание обработчиков |
| Ix, iy | Приложение (не используется в ОС) |
Это означает, что ОС не будет изменять регистры IX и IY, поэтому их можно свободно использовать в приложении.
Альтернативные регистры (имена, за которыми следуют ' ), могут использоваться только в обработчиках прерываний 1 . Приложение не должно использовать эти регистры. Если по какой -то причине вам все равно придется их использовать, пожалуйста, рассмотрите возможность отключения прерываний в течение времени, которое они используются:
my_routine:
di ; disable interrupt
ex af, af' ; exchange af with alternate af' registers
[...] ; use af'
ex af, af' ; exchange them back
ei ; re-enable interrupts
Имейте в виду, что отключение прерываний слишком долго может быть вредной, так как система не будет получать никакого сигнала от аппаратного обеспечения (таймеры, клавиатура, GPIOS ...)
Z80 предоставляет 8 различных сброшенных векторов, так как система всегда должна храниться на первой виртуальной странице памяти, все они зарезервированы для ОС:
| Вектор | Использование |
|---|---|
| $ 00 | Сброс программного обеспечения |
| $ 08 | Syscall |
| 10 долларов | Прыгает по адресу в HL (можно использовать для вызова HL) |
| 18 долларов | Неиспользованный |
| 20 долларов | Неиспользованный |
| 28 долларов | Неиспользованный |
| 30 долларов | Неиспользованный |
| 38 долларов | Зарезервировано для прерывания режима 1, используемой целевой реализацией |
Когда пользовательская программа выполняется, ядро выделяет 3 страницы ОЗУ (48 КБ), считывает двоичный файл для выполнения и загружает его, начиная с виртуального адреса 0x4000 по умолчанию. Этот виртуальный адрес точки записи настраивается через menuconfig с помощью Option KERNEL_INIT_EXECUTABLE_ADDR , но имейте в виду, что существующие программы больше не будут работать без перекомпилирования, потому что они не перемещаются во время выполнения.
Как описано ниже, exec SYSCALL принимает два параметра: двоичное имя файла для выполнения и параметр.
Этот параметр должен быть строкой с нулевым концевым, который будет скопирован и передана в двоичный файл, чтобы выполнить через регистры DE и BC :
DE содержит адрес строки. Эта строка будет скопирована в пространство памяти новой программы, обычно на вершине стека.BC содержит длину этой строки (так, за исключением нулевого байта). Если BC равен 0, DE должен быть отброшен пользовательской программой. Система опирается на Syscalls для выполнения запросов между пользовательской программой и ядром. Таким образом, это должен быть способ выполнения операций на оборудовании. Возможные операции перечислены в таблице ниже.
| Численность | Имя | Парамет 1 | Парамет 2 | Парамет 3 |
|---|---|---|---|---|
| 0 | читать | U8 Dev | U16 BUF | U16 размер |
| 1 | писать | U8 Dev | U16 BUF | U16 размер |
| 2 | открыть | u16 имя | U8 Флаги | |
| 3 | закрывать | U8 Dev | ||
| 4 | DSTAT | U8 Dev | U16 DST | |
| 5 | статистика | u16 имя | U16 DST | |
| 6 | искать | U8 Dev | U32 смещение | U8 откуда |
| 7 | ioctl | U8 Dev | U8 CMD | U16 Arg |
| 8 | Мкдир | u16 путь | ||
| 9 | черт возьми | u16 путь | ||
| 10 | Кердир | u16 путь | ||
| 11 | opendir | u16 путь | ||
| 12 | Риддир | U8 Dev | U16 DST | |
| 13 | rm | u16 путь | ||
| 14 | устанавливать | U8 Dev | u8 Письмо | U8 fs |
| 15 | Выход | u8 код | ||
| 16 | исполнительный | u16 имя | U16 Argv | |
| 17 | сумасшедший | U8 Dev | U8 NDEV | |
| 18 | заснов | U16 Продолжительность | ||
| 19 | STITITION | U8 ID | U16 время | |
| 20 | gettime | U8 ID | U16 время | |
| 21 | SetDate | U16 Date | ||
| 22 | GetDate | U16 Date | ||
| 23 | карта | U16 DST | U24 SRC | |
| 24 | менять | U8 Dev | U8 NDEV |
Пожалуйста, проверьте раздел ниже для получения дополнительной информации о каждом из этих вызовов и их параметров.
Примечание . Некоторые Syscalls могут быть невыразимы. Например, на компьютерах, где каталоги не поддерживаются, Syscalls, связанные с каталогами, могут быть опущены.
Чтобы выполнить SYSCALL, номер операции должен храниться в реестре L , параметры должны храниться в соответствии с этими правилами:
| Имя параметра в API | Z80 Регистр |
|---|---|
| U8 Dev | H |
| U8 NDEV | E |
| U8 Флаги | H |
| U8 CMD | C |
| u8 Письмо | D |
| u8 код | H |
| U8 fs | E |
| U8 ID | H |
| U8 откуда | A |
| U16 BUF | DE |
| U16 размер | BC |
| u16 имя | BC |
| U16 DST | DE |
| U16 Arg | DE |
| u16 путь | DE |
| U16 Argv | DE |
| U16 Продолжительность | DE |
| U16 время | DE |
| U16 Date | DE |
| U24 SRC | HBC |
| U32 смещение | BCDE |
И, наконец, код должен выполнить RST $08 (пожалуйста, проверьте сброс векторов).
Возвращенное значение размещено в A. Значение этого значения является специфичным для каждого вызова, пожалуйста, проверьте документацию соответствующих подпрограмм для получения дополнительной информации.
Чтобы максимизировать совместимость с пользовательскими программами с усердным 8-битным ядром ОС, независимо от того, было ли ядро скомпилировано в режиме MMU или NO-MMU, ограничения параметров Syscalls одинаковы:
Любой буфер, переданный в Syscall
Другими словами, если буфер buf размера n расположен на виртуальной странице i , его последний байт, указанный buf + n - 1 , также должен быть расположен на той же странице i
Например, если read Syscall с помощью:
DE = 0x4000 и BC = 0x1000 , параметры верны , потому что буфер, указанный DE , подходит на страницу 1 (от 0x4000 до 0x7FFF )DE = 0x4000 и BC = 0x4000 , параметры верны , потому что буфер, указанный DE , подходит на страницу 1 (от 0x4000 до 0x7FFF )DE = 0x7FFF и BC = 0x2 , параметры неверны , потому что буфер, указанный DE, находится между страницей 1 и Page2.exec Несмотря на то, что 8-битная ОС рев-это операционная система монозадачи, она может выполнять и сохранить несколько программ в памяти. Когда программа A выполняет программу B благодаря exec Syscall, она должна предоставить параметр mode , который может быть либо EXEC_OVERRIDE_PROGRAM , либо EXEC_PRESERVE_PROGRAM :
EXEC_OVERRIDE_PROGRAM : эта опция сообщает ядру, что программа A больше не нужно выполнять, поэтому программа B будет загружена в том же адресном пространстве, что и программа A. Другими словами, программа B будет загружена в тех же страницах оперативной памяти, что и программа A, она будет перезаписать ее.EXEC_PRESERVE_PROGRAM : эта опция сообщает ядру, что программа A необходимо хранить в RAM, пока программа B не завершит выполнение и вызовет Syscall exit . Для этого ядро выделяет 3 новых страницах памяти ( 16KB * 3 = 48KB ), в которых он хранит недавно загруженную программу B. После выхода программы B ядро освобождает ранее выделенные страницы для программы B, Remaps Program A Memory Pages и возвращает руку для программы A. Если необходимо, можно вернуть значение B. Глубина дерева выполнения определена в menuconfig , благодаря опции CONFIG_KERNEL_MAX_NESTED_PROGRAMS . Он представляет максимальное количество программ, которые можно хранить в ОЗУ за один раз. Например, если глубина составляет 3, программа A может вызовать программу B, программа B может вызвать программу C, но программа C не может вызвать какую -либо другую программу. Однако, если программа вызывает exec с EXEC_OVERRIDE_PROGRAM , глубина не увеличивается, поскольку новая программа для загрузки переопределяет текущую. Таким образом, если мы вернем предыдущий пример, программа C может вызвать программу, если и только если она вызывает exec syscall в режиме EXEC_OVERRIDE_PROGRAM .
Будьте осторожны, при выполнении подпрограммы, всей таблице открытых устройств (включая файлы, каталоги и драйверы), текущий каталог и регистры ЦП будут переданы .
Это означает, что если программа откроет файл с дескриптором 3, программа B наследует этот индекс и, таким образом, также сможет читать, писать или даже закрывать этот дескриптор. Взаимно, если B откроет файл, каталог или драйвер и выходит без закрытия, программа A также будет иметь к нему доступ. Таким образом, общее руководство, которое следует следовать, заключается в том, что перед выходом программа всегда должна закрывать дескрипторы, которые она открыла. Единственный момент, когда таблица открытых устройств и текущего каталога сброшен, - это когда исходная программа (программа A в предыдущем примере) выходит. В этом случае ядро закроет все дескрипторы в таблице открытых устройств, повторно откроет стандартный ввод и вывод и перезагрузит начальную программу.
Это также означает, что при вызове exec SYSCALL в программе сборки на успех все регистры, кроме HL, должны считаться измененными, поскольку они будут использоваться подпрограммой. Так что, если вы хотите сохранить AF , BC , DE , IX или IY , их нужно выдвинуть в стек перед вызовом exec .
Все Syscalls задокументированы в файлах заголовка, предусмотренных как для сборки, так и для C, вы найдете этот файл заголовка в каталоге kernel_headers/ проверьте его файл README для получения дополнительной информации.
Водитель состоит из структуры, содержащей:
SER0 , SER1 , I2C0 и т. Д. Не разрешены не ASCII.init , называемый когда сапоги ядра.read , где параметры и возвратный адрес такие же, как в таблице Syscall.write , так же, как указано выше.open рутины, такой же, как указано выше.close рутины, такой же, как указано выше.seek , так же, как указано выше.ioctl , такой же, как указано выше.deinit , называемый при выгрузке драйвера.Вот пример простой регистрации драйвера:
my_driver0_init:
; Register itself to the VFS
; Do something
xor a ; Success
ret
my_driver0_read:
; Do something
ret
my_driver0_write :
; Do something
ret
my_driver0_open :
; Do something
ret
my_driver0_close :
; Do something
ret
my_driver0_seek :
; Do something
ret
my_driver0_ioctl :
; Do something
ret
my_driver0_deinit :
; Do something
ret
SECTION DRV_VECTORS
DEFB "DRV0"
DEFW my_driver0_init
DEFW my_driver0_read
DEFW my_driver0_write
DEFW my_driver0_open
DEFW my_driver0_close
DEFW my_driver0_seek
DEFW my_driver0_ioctl
DEFW my_driver0_deinit Регистрация драйвера состоит в том, чтобы поместить эту информацию (структуру) в раздел под названием DRV_VECTORS . Заказ очень важен, так как любая зависимость от водителя должна быть разрешена во время компиляции. Например, если водитель зависит от драйвера A B то структура B должна быть помещена до A в разделе DRV_VECTORS .
При загрузке компонент driver будет просмотреть весь раздел DRV_VECTORS и инициализировать драйверов один за другим, вызывая их рутину init . Если эта процедура возвращает ERR_SUCCESS , драйвер будет зарегистрирован, а пользовательские программы могут открыть его, читать, записать, ioctl и т. Д.
Драйвер может быть скрыт к программам, это удобно для драйверов дисков, к которым должен быть доступ только слой файловой системы ядра. Для этого процедура init должна вернуть ERR_DRIVER_HIDDEN .
Поскольку связь между приложениями и аппаратным обеспечением выполняется с помощью SYSCALLS, описанных выше, нам нужен слой между пользовательским приложением и ядром, который будет определять, нужно ли нам вызвать драйвер или файловую систему. Прежде чем показать иерархию такой архитектуры, давайте поговорим о дисках и водителях.
Разные слои можно увидеть так:
Блок -схема TD;
приложение (пользовательская программа)
VFS (виртуальная файловая система)
DSK (дисковый модуль)
DRV (реализация драйвера: видео, клавиатура, сериал и т. Д.)
fs (файловая система)
sysdis (диспетчер Syscall)
HW (оборудование)
время (модуль времени и даты)
mem (модуль памяти)
загрузчик (модуль загрузчика)
App -syscall/rst 8 -> sysdis;
sysdis-GetDate/Time-> Time;
sysdis-mount-> dsk;
sysdis -> vfs;
sysdis-map-> mem;
sysdis -exec/exit -> loader;
VFS -> DSK & DRV;
dsk <--> fs;
fs -> drv;
DRV -> HW;
8-битная ОС рвени поддерживает до 26 дисков одновременно. Диски обозначены письмом, от A до Z. Ответственность водителя диска - решить, где установить диск в системе.
Первый диск, A , особенный, так как это тот, где система будет искать предпочтения или конфигурации.
В приложении может быть path :
my_dir2/file1.txt/my_dir1/my_dir2/file1.txtB:/your_dir1/your_dir2/file2.txt Несмотря на то, что ОС полностью поддается рому и не нуждается в какой-либо файловой системе или диске для загрузки, как только она попытается загрузить начальную программу, называемую init.bin по умолчанию, она проверит диск по умолчанию и запросить этот файл. Таким образом, даже самое основное хранилище нуждается в файловой системе или что -то подобное.
Первая «файловая система», которая уже реализована, называется «Rawtable». Как говорится в его имени, он представляет последовательность файлов, а не каталогов, в устройстве хранения, в любом случае. Предел размера имени файла такой же, как у ядра: 16 символов, включая необязательные . и расширение. Если мы хотим сравнить его с кодом C, это будет массив структур, определяющих каждый файл, за которым следует содержимое файла в одном и том же порядке. Исходный код Packer Packer Romdisk доступен в packer/ в корне этого репо. Проверьте его readme для получения дополнительной информации об этом.
Вторая файловая система, которая также реализована, называется Zealfs. Его основная цель - быть встроена в очень маленькие стера, от 8 КБ до 64 КБ. Он читается и доступно для записи, он поддерживает файлы и каталоги. Больше информации об этом в выделенном репозитории.
Третья файловая система, которая была бы неплохо иметь на 8-битной ОС в усердной 8-битной ОС FAT16. Очень известный, уже поддерживаемый почти всеми рабочими системами настольных компьютеров, которые можно использовать на Compactflash и даже SD-картах, это почти обязательно. Это еще не было реализовано, но спланировано. FAT16 не идеален, хотя он не адаптирован для небольшого хранения, поэтому необходимы Zealfs.
8-разрядная 8-битная ОС основана на двух основных компонентах: ядро и целевой код. Одно ядро ничего не делает. Цель должна реализовать драйверы, некоторые макросы MMU, используемые в сценарии ядра и линкера. Скрипт линкера довольно прост, он перечисляет разделы в том порядке, в котором они должны быть связаны в окончательном двоичном ассемблере z80asm .
Ядро в настоящее время использует следующие разделы, которые должны быть включены в любой сценарий линкера:
RST_VECTORS : содержит векторы сбросаSYSCALL_TABLE : содержит таблицу, где хранится обычный адрес Syscall i i должен быть выровнен на 256SYSCALL_ROUTINES : содержит диспетчер SYSCALL, называемый из вектора сбросаKERNEL_TEXT : содержит код ядраKERNEL_STRLIB : содержит подпрограммы, связанные с строкой, используемые в ядреKERNEL_DRV_VECTORS : представляет собой массив драйверов для инициализации, проверьте раздел драйвера для получения более подробной информации.KERNEL_BSS : содержит данные, используемые кодом ядра, должны быть в ОЗУDRIVER_BSS : не используется непосредственно ядром, оно должно быть определена и используется в драйверах. Ядро установит его на 0 с. Как уже говорилось ранее, 8-битная компьютерная поддержка все еще остается частичной, но достаточно для работы программы командной строки. ROMDISK создается до создания ядра, это делается в script.sh , указанном в target/zeal8bit/unit.mk .
Этот сценарий составит программу init.bin и внедряет ее в Romdisk, который будет объединен с составленной бинарной ОС. Последний двоичный файл может быть непосредственно вспыхнут до норки.
Что еще должно быть реализовано, в любом конкретном порядке:
Быстрый компьютер модели I-I-i-a Port to TRS-80, чтобы показать, как портировать и настроить 8-разрядную 8-битную ОС для целей, у которых нет MMU.
Этот порт довольно прост, поскольку он просто показывает баннер загрузки на экране, не более того. Для этого реализован только видео драйвер для текстового режима.
Чтобы иметь более интересный порт, необходимо было бы реализовать следующие функции:
init.bin /romdisk может быть только для чтения, поэтому можно хранить в ПЗУПорт к агонному свету EZ80, написанного и поддерживаемого Шоном Сиджнстра. Не стесняйтесь использовать эту вилку для конкретных ошибок/запросов. Это использует ядро не MMU и реализует большинство функций, которые поддерживает 8-битная компьютерная реализация.
Этот порт требует загрузчика, чтобы двоичный файл был сохранен и выполнен в правильном месте. Бинарник - Осбутц, доступный здесь.
Обратите внимание, что порт использует режим терминала для упрощения ввода/вывода клавиатуры. Это также означает, что функция даты недоступна.
Другие примечательные функции:
Для порта 8-битной версии OS MMU на другой машине убедитесь, что у вас сначала есть Mapper Mapper, которая делит адресное пространство Z80 64 КБ на 4 страницы 16 КБ для версии MMU.
Для порта No-MMU 8-битная ОС убедитесь, что RAM доступна по виртуальному адресу 0x4000 и выше. Наиболее идеальным случаем, имеющим ПЗУ, является первым 16 КБ для ОС и ОЗУ в оставшихся 48 КБ для пользовательских программ и ядра.
Если ваша цель совместима, следуйте инструкциям:
Kconfig в корне этого репо и добавьте запись в параметры config TARGET и config COMPILATION_TARGET . Возьмите то, что уже присутствуют в качестве примеров.target/ для вашей цели, имя должно быть таким же, как указано в новой опции config TARGET .unit.mk Это файл, который должен содержать все исходные файлы для сборки или включить.unit.mk , чтобы сделать это, вы можете make следующие переменные:SRCS : Список файлов, которые должны быть собраны. Как правило, это драйверы (обязательные)INCLUDES : каталоги, содержащие файлы заголовков, которые могут быть включеныPRECMD : команда Bash, которая будет выполнена до того, как ядро начнет строитьPOSTCMD : команда Bash, которая будет выполнена после завершения ядраmmu_h.asm , который будет включен ядром для настройки и использования MMU. Проверьте target/zeal8bit/include/mmu_h.asm чтобы увидеть, как он должен выглядеть.zos_disks_mount , содержащим файл init.bin , загруженный и выполненный ядром на загрузке.zos_vfs_set_stdout .Для получения полной панели, пожалуйста, проверьте страницу выпуска.
Взносы приветствуются! Не стесняйтесь исправлять любую ошибку, которую вы можете увидеть или встретиться, или реализовать любую функцию, которую вы считаете важной.
Внести свой вклад:
(*) Хорошее послание коммита следующим образом:
Module: add/fix/remove a from b
Explanation on what/how/why
Например:
Disks: implement a get_default_disk routine
It is now possible to retrieve the default disk of the system.
Распределено по лицензии Apache 2.0. См. Файл LICENSE для получения дополнительной информации.
Вы можете использовать его для личного и коммерческого использования, шаблон, присутствующий в каждом файле, не должен быть удален.
Для любого предложения или запроса, вы можете связаться со мной в контакте [AT] Zeal8bit [dot] com
Для запросов функций вы также можете открыть проблему или запрос на тягу.
Тем не менее они не должны рассматриваться как нелетущие. Другими словами, обработчик прерываний не должен предполагать, что данные, которые он написал, в любом альтернативном регистре, будут храниться до следующего времени, когда они будут вызваны. ↩