Разработка ядра операционной системы с нуля - одна из самых сложных и интересных задач в области системного программного обеспечения и компьютерной архитектуры. Сегодня она становится доступнее благодаря современным технологиям, таким как архитектура RISC-V, прошивка OpenSBI и язык программирования Zig. Они позволяют создавать минимальные, но функционально полные прототипы, демонстрирующие основные принципы работы OS и взаимодействия между слоями аппаратного и программного обеспечения. Архитектура RISC-V заслужила любовь разработчиков благодаря своей открытости и простоте. Это открытая инструкция архитектуры, для которой существует легкодоступная документация и реализуемые на практике примеры.
Ее структура легче для понимания по сравнению с более комплексными архитектурами, что делает ее идеальным выбором для образовательных и исследовательских проектов в области низкоуровневого программирования и операционных систем. OpenSBI - это специальный слой, работающий в режиме машины (M-mode) и предоставляющий интерфейс между аппаратным обеспечением и режимом ядра операционной системы (S-mode). Он облегчает управление периферией, настройку таймеров и вывод на консоль, освобождая разработчиков ядра от необходимости самостоятельно реализовывать эти базовые функции. Благодаря этому ядро сосредотачивается на логике управления потоками, планировании и системных вызовах. Язык Zig выбрался в качестве основного инструмента реализации прототипа ядра из-за своей простоты настройки и компиляции, особенно при кросс-компиляции под RISC-V.
Zig способствует лаконичному и читаемому коду, не требующему сложной установки инструментов или обширных зависимостей. Это позволяет сделать эксперимент воспроизводимым и удобным для разработчиков с разным уровнем опыта. Основная цель данного проекта - создание минимального ядра с возможностью тайм-шеринг планирования потоков на однопроцессорной системе. Это значит, что различные пользовательские потоки работают поочередно, так что каждый из них получает ограниченный временной слот процессорного времени. Управление переключением контекста между потоками осуществляется с помощью программируемого системного таймера и обработчика прерываний в режиме Supervisor.
Понятие потока в таких системах носит специфический характер. Поток - это отдельное логическое выполнение, имеющее собственный набор архитектурных регистров и стэк. Это обеспечивает виртуализацию ресурсов процессора для каждого потока, давая ощущение личности, работающей "на отдельном ядре". В отличие от процессов, которые виртуализируют память и обладают собственным адресным пространством, в данном эксперименте память остаётся общей между потоками, что упрощает реализацию, но требует дисциплины в использовании данных для избежания конфликтов. Используя аппаратную модель RISC-V, разработка осуществляется с учётом уровней привилегий.
Машинный режим (M-mode) служит наиболее низкоуровневой точкой, где выполняется OpenSBI. Супервизорный режим (S-mode) отвечает за выполнение ядра ОС, а пользовательский режим (U-mode) - за запускаемые потоки. Такая структура обеспечивает безопасность и разделение обязанностей: пользовательские программы не могут напрямую управлять оборудованием или системными ресурсами без разрешения ядра. Переключение контекста между потоками происходит в обработчике таймерных прерываний в режиме S-mode. При каждом срабатывании таймера текущие регистры и состояние процессора сохраняются в памяти, а затем добирается информация следующего потока для загрузки в процессор, после чего управление продолжается с момента прерывания.
Это достигается использованием строго регламентированного прологового и эпилогового кода на ассемблере, дополненного вызовами функций ядра, написанных на Zig. Важно отметить, что для корректного переключения контекста каждому потоку выделяется отдельный стэк. Стэк служит местом хранения локальных переменных и регистров при вызове функций. При переключении необходимо подгружать и сохранять именно эти данные, чтобы исключить взаимное искажение выполнения потоков. Отдельное хранение регистров процессора, включая расширенные управляющие регистры (CSRs), закрепляет уникальность состояния выполнения каждого потока.
Процесс создания потока в ядре заключается в статическом резервировании блоков стэка и подготовке регистров к старту пользовательской функции. В отличие от динамичного создания потоков в современных ОС, здесь все потоки определяются изначально, а функции потоков реализованы как бесконечные циклы, выполняющие повторяющиеся операции. Для коммуникации с ядром пользовательские потоки используют системные вызовы через инструкцию ecall. В частности, в прототипе реализован базовый системный вызов вывода на консоль, который позволяет потокам печатать свои идентификаторы. Запросы проходят в периферийный режим ядра, где обрабатываются и перенаправляются через OpenSBI или прямой доступ к UART на уровень аппаратуры.
Сетевая интеграция и многопроцессорность в данном эксперименте отсутствуют, однако выбранный подход легко расширяется с ростом сложности и потребностей системы. Кроме того, все части проекта сопровождаются подробными комментариями и кодом, что облегчает понимание для новичков и может служить базой для дальнейших исследований и практических занятий. Сборка и запуск системы реализуются через инструментарий Zig и эмулятор QEMU. Запуск под QEMU позволяет протестировать ядро в виртуальной среде практически без затрат времени и ресурсов. Для запуска требуется наличия прошивки OpenSBI - современного и поддерживаемого вариант для платформы riscv-virt.
Пример вывода системы демонстрирует циклический вывод сообщений от разных потоков, отражая корректность работы тайм-шеринг планировщика. Это наглядно подтверждает, что несмотря на один процессорный ядро, система создает видимость одновременной работы нескольких задач и изолирует их состояние друг от друга. Данный эксперимент является хорошей отправной точкой для любых разработчиков, заинтересованных в глубоких знаниях системных основ операционных систем. Он показывает сочетание аппаратных возможностей, методов межуровневого взаимодействия и современного программирования на Zig, что выгодно отличает его от привычных традиционных решений на C. Несмотря на упрощения и минимализм, подобный проект служит учебным пособием, раскрывающим фундаментальные механизмы планирования, работы с прерываниями, системой вызовов и структурой потоков.
Подобный опыт важен для понимания функционала современных ОС и разработки низкоуровневого кода для встраиваемых систем и новых процессорных архитектур. Таким образом, на базе RISC-V, OpenSBI и Zig возможно реализовать современное и простое в понимании ядро операционной системы с поддержкой тайм-шеринг многопоточности. Это открывает путь к дальнейшему развитию, включая внедрение динамического управления памятью, поддержки нескольких процессоров, системных драйверов и пользовательских интерфейсов. Такой опыт бесценен для студентов, исследователей и инженеров, стремящихся углубить знания в области системного программирования и архитектуры компьютеров. .