ZX Murmulator - одноплатный ультрадешевый микрокомпьютер на основе платы Raspberry Pi Pico (далее "пика"), которая, в свою очередь, основана на микроконтроллере - RP2040.
RP2040 - одна из наиболее известных двухъядерных реализаций ARM Cortex-M0+ с 264 КБ встроенной SRAM памяти и от 2-ух до 16-ти МБ flash-памяти подключаемых по QSPI интерфейсу, распаянной на плате пики. Данный микроконтроллер легко гонится до 400 МГц без какого либо радиатора, не смотря на свои штатные 133. Что позволяет запускать на нём достаточно прожорливые задачи.
В предыдущей статье (https://habr.com/ru/articles/839960/) я упомянул, что для Мурмулятора в настоящее время разрабатывается собственная операционная система. Возникает вопрос - зачем микрокомпьютеру с 264 КБ памяти вообще понадобилась ОС?
Основная задача любой операционной системы - предоставить приложениям возможность унифицированного способа доступа к ресурсам оборудования, а пользователю - запускать и останавливать работающие приложения.
На самом деле, почти все прикладные задачи Мурмулятора можно решать без всякой ОС - достаточно установить бут-лоадер (см. https://github.com/xrip/pico-launcher), который позволит управлять прошивками, загружая их с SD-карточки. Основной недостаток бут-лоадера - невозможность редактирования конфиг-файлов эмуляторов легко решается путём его дописывания. Но если понадобится что-то ещё? Можно на каждый чих писать отдельную прошивку, или модифицировать существующую. Т.е. Murmulator OS (далее - просто МОС) должна решить эту проблему - дать более гибкий способ расширения функциональности.
Что ещё полезного может предоставить операционная система? - Унификация доступа к оборудованию. В настоящее время, каждая отдельная прошивка содержит все необходимые для функционирования приложения драйвера устройств, т.е. на лицо явная избыточность. Если ОС будет содержать все нужные драйвера, то приложение сможет сконцентрироваться только на своей функциональности, а остальное запрашивать у ОС.
Ещё одним дополнительным профитом наличия для устройства какой-то ОС является возможность использования данного устройства для обучения детей основным концепциям информатики. А учитывая, что изначально Мурмулятор - платформа игровая, для которой существует множество эмуляторов ретро-компьютеров, то можно познакомить детвору и с историей развития вычислительной техники, показать игры 80-ых и начала 90-ых годов прошлого века различных платформ, стран и концепций.
Не стоит также забывать, что Мурмулятор - ультрабюджетное решение, т.е. позволить себе такое устройство может практически кто угодно. Добыть PS/2 клавиатуру и VGA-монитор - в наше время тоже задача крайне бюджетная. Т.е. в этаком "offline-режиме", ребёнку необходимо будет только где-то заполучить файлы прошивок (эмуляторы) и ретро-игры к ним. Думаю, что наши бизнесмены, что продают готовые мурмуляторы, вполне могут предоставлять их с SD-карточкой, на которой уже будет записана вся коллекция. Ну, это уже не относится к самой ОС.
Когда я только задумался о написании ОС к Мурмулятору, была мысль, что можно создавать целые компьютерные классы мурмуляторов, но просмотрев (по-диагонали) программу обучения информатике в школе, понял - поздно, т.к. оно уже никому не нужно, там учат окошки по десктопу таскать и в экселе таблички править. Ну, может, программа ещё изменится, и основы таки начнут преподавать. Или оно пригодится в ретро-компьютерных клубах. А есть такие? Если нет - дарю бизнес-идею ))
Вернёмся к самой ОС. Выбирая ядро для будущей ОС, я перелопатил кучу уже готового кода, в котором меня что-то да не устраивало. Ближе всего к требуемым параметрам оказалась FreeRTOS (https://github.com/FreeRTOS/FreeRTOS-Community-Supported-Demos/tree/3d475bddf7ac8af425da67cdaa2485e90a57a881/CORTEX_M0%2B_RP2040), единственным недостатком которой является её достоинство - она очень простая и при этом крайне несамодостаточная. Т.е. нельзя скачать где-то дистрибутив данной ОС под RP2040 и поставить на какое-то оборудование, пусть даже несовместимое с Мурмулятором. FreeRTOS, в данном случае - встраиваемое решение, т.е. если вам понадобилась многозадачность, то она вам её обеспечит, но вот с файловыми операциями, драйверами клавиатуры, видео-подсистемы, звука и любого чиха - это всё на вас.
Зачем вообще мне понадобилась многозадачность? Вон, древние MS DOS или Windows 3.1 вполне без неё обходились, и ничего. Тут очень сложно пояснить человеку, который никогда не писал программы под ОС с добровольной многозадачностью. Скажу кратко - это крайне утомительно и крайне ненадёжно. Нет никакой гарантии, что твоя программа получит доступ к ресурсу вовремя. Т.е. тебе бы в буфер саундкарты что-то записать надо, но нет - программа форматирования дискеты в это время заняла все ресурсы и не отдаёт, а саундкарта в это время выводит одну ноту, т.к. новой нотой её никто не снабдил. В общем, я выбрал себе ядро будущей ОС, и принялся активно обшивать его драйверами доступных устройств.
Ещё пару слов надо сказать про ARM Cortex M0+ - это ядро не имеет полноценного MMU - только примитивный MPU и малополезный для нас, в данном случае, XIP. Т.е. о виртуальной памяти можно сразу забыть. Попытка её построить на одном MPU приведёт к таким тормозам, что можно просто установить эмулятор IBM PC XT и получить аналогичную производительность.
Отсутствие выделенного адресного пространства на процесс с отсутствием трансляции адресов - приговор неперемещаемым программам. Т.е. после компиляции у нас обычно получается объектный файл, который ещё ни к каким адресам в памяти не привязан (кроме адресов оборудования), а вот после линковки - всё, адреса прибиты гвоздями к получившемуся коду. Причём уже нельзя разделить, какие адреса привязаны "по-делу", т.к. ссылаются на адреса какого-то оборудования, а какие "просто так", потому что линковщик так захотел, при этом часть адресации будет нормальной - относительной.
Переписывать линковщик - задача явно не моего уровня (или потребует несоразмерного времени), поэтому было принято решение от него избавиться. Совсем. Возможно, в будущем, я напишу линковщик под МОС, как и компилятор под неё, и всё остальное... но пока-то его нет (подходящего). Соответственно, результатом компиляции программы в объектный файл на данном этапе мы и удовлетворимся. Теперь задача ОС - расположить где-то в свободной памяти код из объектника, разрезолвить адреса и передать управление функции main
.
Возникает вопрос - а как программа будет обращаться к API самой ОС? Ну, поскольку ОС - обычная .uf2 прошивка, в которой адреса функций таки можно прибить гвоздями к определённым адресам, задав их в .ld файле, проблемы как-бы и нет, но это крайне громоздкое и неудобное решение - прописывать для каждой функции АПИ её адрес. Лучше сделать таблицу адресов функций (указателей на них), её начало прибить к определённому адресу, а наполнение пусть сам линкер резолвит.
В этом случае, для приложений достаточно предоставить заголовочный файл, в котором будут конструкции типа:
#define M_OS_API_SYS_TABLE_BASE ((void*)(0x10000000ul + (16 << 20) - (4 << 10)))
static const unsigned long * const _sys_table_ptrs = (const unsigned long * const)M_OS_API_SYS_TABLE_BASE;
inline static int kill(uint32_t task_n) {
typedef int (*fn_ptr_t)(uint32_t);
return ((fn_ptr_t)_sys_table_ptrs[244])(task_n);
}
что позволит не заботиться о реальных адресах функций, они хранятся в той самой таблице указателей, которая прибита гвоздями к нужному адресу.
Надо заметить, что из-за особенностей Cortex-M0+ все приложения МОС будут разделять общую память, а это обозначает снижение надёжности всей системы. К сожалению, я так и не придумал хорошего решения данной проблемы. Может быть, позже я к этому вернусь и попробую задействовать существующий в процессоре MPU для недопущения обращения к "чужой" памяти, но пока этой функциональности просто нет, и при разработке программ это необходимо учитывать.
Также необходимо учитывать, что если какая-то программа совершит нечто "запрещённое", что приведёт к HardFault ядра процессора, то никакая FreeRTOS тут уже не поможет - ядро остановится - т.е. повиснет. У меня имеются ряд идей по перехвату HardFault и дальнейшему удалению контекста задачи, но это также пока не реализовано.
Аналог SIGKILL для задач тоже пока не реализован, вместо этого имеется аналог SIGTERM, что предполагает обработку данного сигнала самой программой (для этого имеется предопределённый хэндлер "signal", на который и надо вешать логику выхода из задачи).
Есть вероятность, что с выходом Raspberry Pi Pico 2, на чипе RP2350 (2 x ARM Cortex-M33) все проблемы можно будет решить штатной виртуализацией, и этот кусок реализации ОС придётся выкинуть. Но это уже будет другой Мурмулятор (2.0?)...
Исходники МОС можно изучать тут: https://github.com/DnCraptor/murmulator-os
Чуть позже выложу ещё статью про МОС с точки зрения пользователя.