Введение
В этой публикации я рассказываю про gbaunix, забавный эксперимент, в ходе которого я запустил древнюю версию операционной системы UNIX в симуляторе на популярной (во время публикации, в 2004 году — прим. перев.) портативной консоли. А именно, UNIX 5 издания (вышел в 1974 году, 39 лет назад) на Nintendo Game Boy Advance. Это может быть интересно разработчикам homebrew для Геймбоя, студентам-айтишникам со специализацией по ОС, эмуляторам или компиляторам, гикам-юниксоидам.
Нинтендо присутствует на рынке игр с 1889 года (sic!). Геймбой — это бренд целой линейки портативных консолей, которая весьма успешно продается и по сей день. С момента выпуска в 1989 году было продано 175 млн штук. В этой статье рассматривается Game Boy Advance, сокращенно GBA, и его аналог GBA SP.
Железо:
- 32-битный процессор ARM (RISC), работающий на частоте 16.78 МГц
- 32-битный процессор ARM (RISC), работающий на частоте 16.78 МГц
- 8-битный процессор Z80 (CISC), частота 4.2 или 8.4 МГц. Добавлен для совместимости со старыми Геймбоями, у них это был центральный процессор
- 4 16-битных таймера
- 4 канала DMA
- Цветной TFT экран, разрешение 240x160
- Стереозвук через наушники
- 10 кнопок
- Последовательный порт
- Интерфейс для картриджа GamePak
В консоли есть несколько блоков памяти:
- 16 KB BIOS ROM
- 256 KB внешней RAM, распаянной на плате (EWRAM)
- 32 KB внутренней RAM, на кристалле ЦП (IWRAM)
- 1 KB RAM для фона и палитры спрайтов
- 96 KB Видео RAM
- 1 KB RAM для атрибутов объектов
- До 32 MB ROM картриджа
- До 64 KB SRAM картриджа, опционально
Следует заметить, что у ROM картриджа есть два дополнительных отображения в адресном пространстве консоли. Кроме того, картридж может содержать несколько банков памяти с различным объемом или таймингами.
GBA может загружаться с обычного картриджа, перезаписываемого флеш-картриджа, другого GBA в режиме мастера или вообще с компьютера по кабелю. Эта функциональность зашита в BIOS.
Железо консоли в основном доступно через хорошо документированное адресное пространство. Различные регистры ввода-вывода мапятся на адреса памяти. Дополнительно к этому, BIOS содержит множество функций, доступных через софтовые прерывания.
Лирическое отступление про архитектуру ARM
Нинтендо — лидер на рынке приставок, а ARM — лидер на рынке RISC-процессоров. Сейчас АРМы присутствуют почти во всей электронике. Интел рулит на десктопах, это да. Но процессоры для РС составляют весьма малую долю общего производства. В 2003 году было произведено 782 млн устройств на АРМах. В целом, доля этой архитектуры в районе 75%.
Чаще всего АРМ употребляют в одном предложении со словами «встроенный», «производительный», «дешевый», «маложрущий», «RISC». АРМ лицензирует архитектуру непосредственно производителям процессоров, в том числе и таким большим, как Интел, Эппл, Самсунг.
Первый АРМ был разработан в Acorn Computers Limited в середине 80-х. Тогда это расшифровывалось как «Acorn RISC Machine». Самая первая версия архитектуры АРМ, ARMv1, поддерживала 26-битную адресацию и была весьма медленной. Первый процессор этой архитектуры, ARM1, был периферийным процессором в микрокомпьютере ВВС и прототипе рабочей станции Archimedes. И это был один из первых процессоров архитектуры RISC. Основные особенности: отложенное ветвление, регистровые окна, каждая инструкция выполняется за один такт.
В Apple пытались применить АРМы в начале 90-х. В сотрудничестве с Acorn они запустили новую компанию, Advanced RISC Machines, Limited. Еще одним соучастником стал VLSI Technology. Расшифровка аббревиатуры поменялась. Новый процессор назывался ARM6, архитектура — ARMv3. Появилась полностью 32-битная адресация. Этот процессор использовался в Apple Newton.
В Геймбое используется процессор ARM7TDMI, архитектура версии ARMv4T.
ARM7TDMI — это популярный встраиваемый 32-битный процессор со слегка ограниченной функциональностью. У него нету кэша и MMU. Обозначение расшифровывается так:
Процессор ARM7
Поддерживает набор 16-битных инструкций Thumb
Поддерживает Debug прямо в железе
Имеет встроенный блок 32-битного уМножения, результат — 64-битный
Есть EmbeddedICE для отладки
В ядре ARM7TDMI одна 32-битная шина для данных и инструкций. Данные могут быть размером 8, 16, 32 бита. Только инструкции загрузки, сохранения и обмена могут иметь доступ к памяти. Есть 31 32-битный регистр общего назначения, 6 регистров статуса, сдвиговый регистр, блоки арифметики и умножения. Не все регистры доступны одновременно. Например, в режиме ARM доступны 16 общих и 1 или 2 статусных регистра. Есть 7 режимов работы процессора: обычный для выполнения программ, и 6 специальных. Это быстрое прерывание, прерывание, супервизор, остановка, неопределенное состояние и системный режим. Также поддердиваются два набора инструкций — ARM и Thumb.
Геймбой поддерживает 4 канала DMA. Процессор умеет два типа прерываний — обычные IRQ и быстрые FIQ, но в консоли используются только обычные.
ARM7 имеет простой трехстадийный конвейер:
Команда извлекается из памяти и помещается в очередь
Команда декодируется
Команда выполняется. При этом происходит чтение из регистров, вычисление результатов и запись из в регистры.
Таким образом, в любой момент времени одна команда выполняется, следующая декодируется, а через одну — извлекается.
Относительный недостаток RISC-процессоров — относительно объемный код. Это увеличивает объем программы и ведет к потере эффективности кэша, излишнему трафику памяти и потреблению энергии. Для встраиваемых приложений это особенно плохо. Для уплотнения кода разработали архитектуру Thumb.
Thumb — это сжатая до 16 бит система 32-битных команд ARM. Поддерживается набор самых ходовых инструкций. Работают они с теми же 32-битными регистрами. Процессоры с поддержкой Thumb оснащены декодером в конвеере, который преобразует их в обычные ARM-инструкции. Разница получается примерно такой:
Обычно используется смесь команд ARM и Thumb. Тип исполняемой команды указывается специальным флагом. В Геймбое есть 32 Кб быстрой памяти прямо на кристалле. Обычно, оттуда выполняется критичный по скорости код. Все остальное уместно скомпиоировать в Thumb и выполнять из медленной памяти картриджа.
habrahabr.ru/post/92494 — полезное чтиво вообще про ARM. — прим. перев.
ARM
Нинтендо — лидер на рынке приставок, а ARM — лидер на рынке RISC-процессоров. Сейчас АРМы присутствуют почти во всей электронике. Интел рулит на десктопах, это да. Но процессоры для РС составляют весьма малую долю общего производства. В 2003 году было произведено 782 млн устройств на АРМах. В целом, доля этой архитектуры в районе 75%.
Чаще всего АРМ употребляют в одном предложении со словами «встроенный», «производительный», «дешевый», «маложрущий», «RISC». АРМ лицензирует архитектуру непосредственно производителям процессоров, в том числе и таким большим, как Интел, Эппл, Самсунг.
Первый АРМ был разработан в Acorn Computers Limited в середине 80-х. Тогда это расшифровывалось как «Acorn RISC Machine». Самая первая версия архитектуры АРМ, ARMv1, поддерживала 26-битную адресацию и была весьма медленной. Первый процессор этой архитектуры, ARM1, был периферийным процессором в микрокомпьютере ВВС и прототипе рабочей станции Archimedes. И это был один из первых процессоров архитектуры RISC. Основные особенности: отложенное ветвление, регистровые окна, каждая инструкция выполняется за один такт.
В Apple пытались применить АРМы в начале 90-х. В сотрудничестве с Acorn они запустили новую компанию, Advanced RISC Machines, Limited. Еще одним соучастником стал VLSI Technology. Расшифровка аббревиатуры поменялась. Новый процессор назывался ARM6, архитектура — ARMv3. Появилась полностью 32-битная адресация. Этот процессор использовался в Apple Newton.
В Геймбое используется процессор ARM7TDMI, архитектура версии ARMv4T.
ARM в Геймбое
ARM7TDMI — это популярный встраиваемый 32-битный процессор со слегка ограниченной функциональностью. У него нету кэша и MMU. Обозначение расшифровывается так:
Процессор ARM7
Поддерживает набор 16-битных инструкций Thumb
Поддерживает Debug прямо в железе
Имеет встроенный блок 32-битного уМножения, результат — 64-битный
Есть EmbeddedICE для отладки
В ядре ARM7TDMI одна 32-битная шина для данных и инструкций. Данные могут быть размером 8, 16, 32 бита. Только инструкции загрузки, сохранения и обмена могут иметь доступ к памяти. Есть 31 32-битный регистр общего назначения, 6 регистров статуса, сдвиговый регистр, блоки арифметики и умножения. Не все регистры доступны одновременно. Например, в режиме ARM доступны 16 общих и 1 или 2 статусных регистра. Есть 7 режимов работы процессора: обычный для выполнения программ, и 6 специальных. Это быстрое прерывание, прерывание, супервизор, остановка, неопределенное состояние и системный режим. Также поддердиваются два набора инструкций — ARM и Thumb.
Геймбой поддерживает 4 канала DMA. Процессор умеет два типа прерываний — обычные IRQ и быстрые FIQ, но в консоли используются только обычные.
ARM7 имеет простой трехстадийный конвейер:
Команда извлекается из памяти и помещается в очередь
Команда декодируется
Команда выполняется. При этом происходит чтение из регистров, вычисление результатов и запись из в регистры.
Таким образом, в любой момент времени одна команда выполняется, следующая декодируется, а через одну — извлекается.
Thumb
Относительный недостаток RISC-процессоров — относительно объемный код. Это увеличивает объем программы и ведет к потере эффективности кэша, излишнему трафику памяти и потреблению энергии. Для встраиваемых приложений это особенно плохо. Для уплотнения кода разработали архитектуру Thumb.
Thumb — это сжатая до 16 бит система 32-битных команд ARM. Поддерживается набор самых ходовых инструкций. Работают они с теми же 32-битными регистрами. Процессоры с поддержкой Thumb оснащены декодером в конвеере, который преобразует их в обычные ARM-инструкции. Разница получается примерно такой:
- Экономится 35% объема кода
- Надо на 40% больше команд
- Код на 40% медленней для 32-битной памяти
- Но на 60% быстрее для 16-битной
- Энергопотребление на 30% меньше
Обычно используется смесь команд ARM и Thumb. Тип исполняемой команды указывается специальным флагом. В Геймбое есть 32 Кб быстрой памяти прямо на кристалле. Обычно, оттуда выполняется критичный по скорости код. Все остальное уместно скомпиоировать в Thumb и выполнять из медленной памяти картриджа.
habrahabr.ru/post/92494 — полезное чтиво вообще про ARM. — прим. перев.
Высокоуровневая архитектура gbaunix
gbaunix — это UNIX 5 издания, запущенный на Геймбое. Для этого используется SIMH, симулятор разных антикварных компьютеров, написанный на С. SIMH может эмулировать много чего еще, но здесь используется только PDP-11. Для Геймбоя существует несколько тулчейнов С, поэтому получилось малой кровью портировать SIMH на Геймбой. Архитектура получившейся системы показана ниже:
Картридж gbaunix представляет собой конкатенацию рантайма симулятора и образа диска с ОС. Остаток места можно оставить пустым, или занять чем-нибудь полезным.
Симулятор представляет собой минимально модифицированный SIMH. Специфичные для Геймбоя особенности реализованы в отдельном уровне абстракции.
TTY вывод.
gbaunix имитирует текстовый терминал для SIMH. Вывод перенаправляется на выполняемую на Геймбое процедуру, которая форматирует его и выводит на экран. Поддерживается скроллинг.
printf()
разбивается на sprintf()
в буфер и отображение его на экране.TTY ввод.
Сейчас вменяемой поддержки ввода вообще нет. Можно только выполнить определенную при компиляции последовательность shell-команд. Она задается в файле
gba/gba_kbd.h
. При работе UNIX кнопка Start подает следующую команду из списка на выполнение. Есть возможность следить за нажатиями кнопок, опросом или через прерывание. Пример:/* gba/gba_kbd.h */
const char *gba_kbdinput[] = {
"unix\r",
"root\r",
"chdir /work\r",
"ls -l\r",
"./fact 100\r",
"cat hanoi.c\r",
"./hanoi\r",
"./hanoi 3\r",
"chdir /tmp\r",
"echo 'main() { printf(\"Hello, World!\\n\"); }' \
> hello.c\r",
"cc hello.c\r",
"./a.out\r",
... /* more commands */
NULL,
};
Файловая система.
Образ диска занимает 2.5 Мб, он помещается только в ROM картриджа. Хотя эта память и read-only, необходимо как-то обрабатывать запись в нее. Для этого при каждой операции записи создается буфер в RAM. Все операции чтения и записи сначала проверяются на попадание в какой-либо буфер. По мере роста количества таких буферов происходит их слияние. Симулятору подсовывается виртуальное stdio.
Память.
Симулируемому PDP-11 доступно 128 Кб памяти из доступных в железе 256 Кб EWRAM. Есть некоторая оптимизация работы с памятью наподобие использования DMA.
Прочее.
Здесь всякий код типа инициализации среды исполнения, TTY, прерываний, файловой системы итд.
Разработка
gbaunix работает как на эмуляторе Геймбоя, так и на железной консоли. Для запуска в железе понадобится флеш-картридж и программатор для него.
Гораздо более удобно пользоваться эмулятором Гемйбоя, особенно если есть желание повозиться с исходниками. gbaunix пока очень условно оптимизирован, и очень медленно работает на железной консоли. Эмулятор позволит работать с максимально доступной скоростью, а это сэкономит много нервов.
Моя среда разработки развернута под Mac OS X. Используется эмулятор Boycott Advance и тулчейн devkitARM, собранный из сырцов. Проверял на реальной консоли с флеш-картриджем.
Лирическое отступление об истории UNIX
Здесь был большой текст, не имеющий прямого отношения к тематике статьи, но слишком любопытный, чтобы от него избавиться. Вынес в отдельный пост — habrahabr.ru/post/194160
Демонстрация работы gbaunix, с комментариями
Пятое издание, июнь 1974
Я использую пятое издание, потому что это самая старая версия, для которой существуют в электронном виде загружаемые образы системы и исходный текст ядра. Его мы попробуем перекомпилировать, потому что это труЪ.
При включении питания, gbaunix покажет немного информации о железе PDP-11 и приглашение загрузчика —
@
. Если нажать Start
, очередь команд выдаст имя ядра для загрузки — unix
. Загрузка на железном Геймбое занимает около двух минут. После загрузки появляется приглашение залогиниться.Точка с запятой в приглашении (;login это еще и бумажный журнал, выпускаемый USENIX Association) необходима для популярного в начале 1970-х терминала
Teletype model 37
. Она переводит его в полнодуплексный режим. Все другие терминалы, включая наш GBA TTY, просто выводят символ на экран.Даже у столь ранней версии UNIX есть файлы блочных и символьных устройств.
RK0
это первый диск, /dev/mem
— отображение памяти системы для отладки с дебаггером и накладывания патчей на горячую.glob
, сокращение от global
, это внешняя по отношению к шеллу команда для раскрытия метасимволов *
и ?
в аргументах команд. glob
раскрывает метасимволы и вызывает нужную команду. Если совпадений нет, выдается традиционная ошибка No match
.Любопытно, что некоторые команды наподобие
mkfs
спрятаны в /etc
, подальше от случайного вызова кривыми руками. dc
— это калькулятор с обратной польской нотацией. Это первая программа, запущенная на PDP-11, еще до создания версии UNIX для этого компьютера.Типичное ядро пятого издания занимает меньше 26 Кб. Шелл занимает 5738 байт, а
/init
— всего 1972 байта. Также есть минимально приличный скрипт /etc/rc
. /etc/update
обновляет суперблок файловой системы каждые 30 секунд, для уменьшения потерь при сбое. Подробный вывод команды ls
включает в себя права доступа, количество ссылок, владельца, размер в байтах, время последнего изменения и имя.К эпохе пятого издания уже была создана богатая инфраструктура разработки с поддержкой множества языков программирования. Например, Algol-68, APL, Ассемблер, BASIC, C, FORTRAN, M6, PASCAL, Snobol, TMG. В принципе, gbaunix позволяет программировать прямо на Геймбое на нескольких языках (это в теории, в реальности это как минимум неудобно из-за отсутствия клавиатуры). В поставку входят компиляторы/интерпретаторы для С, ассемблера PDP-11, BASIC, шелла и Фортрана. Алгол я тоже пробовал, но в образ диска его не добавлял. Для примера показываю Ханойские башни.
Больше скриншотов можно посмотреть на специальной странице со скриншотами
Последующие издания
Шестое издание (май 1975) оставило след в истории, потому что от него взяли свое начало BSD и Xenix. Джон Лайонс написал свои знаменитые "Комментарии Лайонса к 6-й версии UNIX, с исходным кодом". Кроме того, это самая ранняя полностью сохранившаяся версия UNIX. Документация к пятому изданию утеряна, от четвертого и более ранних не осталось почти ничего. Начиная с шестого издания, развитие UNIX-систем заметно оживилось. Потом были седьмое издание в январе 1979, восьмое в феврале 1985, девятое в сентябре 1986 и десятое в октябре 1989.
BSD
На Геймбое можно запустить еще несколько систем, поддерживаемых в SIMH. Но это все более и более сложно, и упирается в доступную оперативную память. Пара скриншотов с BSD 2.9:
Оптимизация gbaunix
У gbaunix есть предпосылки для экспериментов, влияющих на скорость работы. Но это потребует перекомпиляции.
Кртитческий код в IWRAM
В gbaunix есть примеры компиляции кода для ARM и хранения его в IWRAM. Вероятно, полезней всего это будет для кода симуляции процессора.
DMA
Геймбой позволяет работу с памятью несколькими способами, с разной производительностью и ограничениями. gbaunix использует DMA3 (общего назначения) для функций
memcpy()
и memset()
. Более того, BIOS содержит функции для копирования и заполнения памяти через софтверные прерывания. В целом, gbaunix поддерживает тонкую настройку работы с памятью.Кэширование
Как я уже говорил, gbaunix эмулирует stdio и виртуальный диск, представляя память картриджа как файл UNIX. Поскольку железо не позволяет писать в ROM, приходится обходиться буферами, как сказано выше. gbaunix умеет предзагружать в буферы фрагменты диска. Это позволяет заметно ускорить загрузку системы.
Перекомпиляция ядра UNIX
Перекомпиляция ядра не дает каких-либо заметных преимуществ для gbaunix, но это в любом случае занимательно. Хотя бы для сравнения с этой же процедурой для современных систем. Мы можем ограничить поддержку железа ядром и тем самым сэкономить немного места.
Оценим размер исходного кода пятого издания.
Заголовки: 418 строк в 13 файлах
C: 7222 строк в 43 файлах (включая драйвера для периферии)
Ассемблер: 1080 строк в 2 файлах
Я даже и не пытался компилировать ядро прямо на Геймбое. Это займет неопределенно долгое время, и у нас закончится память под буферы. Да и вообще, аппаратура для такой задачи слишком маломощная.
Следюущая последовательность команд предполагает, что есть рабочая инсталляция пятого издания, на реальном железе или на симуляторе, и исходник ядра в каталоге по умолчанию —
/usr/sys
Удаляем библиотеки, возможно оставшиеся от прошлой компиляции:
# chdir /usr/sys
# rm -f lib1
# rm -f lib2
Перед началом компиляции надо изучить и правильно указать параметры в файле
/usr/sys/param.h
. Если компилятор выдает ошибку "
undefined KISA0
", надо добавить определение в /usr/sys/seg.h
:# echo '\#define KISA 0172340' >> /usr/sys/seg.h
Собственно компиляция:
# chdir ken
# cc -c -O *.c
...
# ar vr ../lib1 main.o alloc.o iget.o prf.o rdwri.o \
slp.o subr.o text.o trap.o sig.o sysent.o clock.o fio.o \
malloc.o nami.o pipe.o sys1.o sys2.o sys3.o sys4.o
...
Компиляция драйверов и прочего. Можно частично отключить:
# chdir ../dmr
# cc -c -O *.c
...
# ar vr ../lib2 *.o
Конфигурация системы и линковка. На выходе получается бинарник ядра:
# chdir ../conf
# cc mkconf.c
# mv a.out mkconf
# echo rk | ./mkconf
# cc -c c.c
# as l.s
# mv a.out l.o
# as mch.s
# mv a.out mch.o
# ld -x l.o mch.o c.o ../lib1 ../lib2
# mv a.out rkunix
rkunix
— это и есть свежескомпилированное ядро. Если положить его в корневой каталог /rkunix
, и при загрузке дать команду rkunix
вместо unix
, то оно и загрузится.Идеи и предложения
Краткий список перспективных идей. Интерес большей частью академический
Нативный порт UNIX на Геймбой
Портирование UNIX на Геймбой было бы хорошим упражнением для студентов в рамках курса по операционным системам. Производительность должна резко вырасти. Древние ОС достаточно малы, чтобы поместиться в голове одного человека. Шестое издание состоит из 44 файлов:
14 заголовков C
28 файлов с кодом на C
2 файла с ассемблером.
Все вместе содержит меньше 9000 строк кода. Это с драйверами устройств. Ассемблера там около 10%.
На мой взгляд, задача вполне посильная. Могут быть трудности с ассемблерным кодом и портированием компилятора.
Улучшение производительности
Хотя я и попытался ускорить работу где это возможно, но до окончательного решения вопроса еще далеко. Можно начать с кода симуляции процессора (
pdp11_cpu.c
) — он выполняется самую большую долю времени. Механизм ввода
Сейчас gbaunix просто выполняет зашитую при компиляции очередь команд. Для приближения к реальности необходим способ ввода команд пользователем. Как вариант, можно предложить виртуальную клавиатуру, нормально работающую с терминалом.
Эмуляция оригинального Макинтоша на Геймбое
Первый Макинтош не отличался особо продвинутым железом: 16-битный процессор с частотой 8 МГц, 128 Кб RAM, 64 Кб ROM, нет ни кэша, ни прерываний, флоппи-диск объемом 400 Кб, монохромный экран с разрешением 512х342. У Геймбоя железо все-таки помощнее, кроме экрана. В принципе, можно создать или портировать легковесный эмулятор Макинтоша. Нехватку экрана компенсировать прокруткой, имитировать запись на диск буферами итд.
Другой вариант — портировать эмулятор 8088 для запуска древней версии ДОСа.
Где скачать
gbaunix-0.0.tar.bz2
Следут заметить, что для запуска надо иметь образ диска RK05. Он не входит в комплект, но его можно скачать с сайта PDP Unix Preservation Society, если согласиться с условиями лицензионного соглашения.
http://minnie.tuhs.org/PUPS/
Использование
Если хочется просто посмотреть, то можно обойтись без перекомпиляции и слепить в одну кучу рантайм симулятора и образ диска:
% cat unixv5.tmp disks/unixv5.dsk > unixv5.gba
unixv5.gba
— это готовый к запуску образ картридж. Его можно использовать и с эмулятором, и с железной консолью.Если есть желание перекомпилировать gbaunix (очень рекомендуется, собственно это единственный способ получить fun от процесса), то понадобится среда кросс-компиляции для архитектуры ARM. Возможно, понадобится поправить Makefile. Образ RK05 должен быть под именем
disks/unixv5.dsk
. После этого, теоретически должно хватить одной команды make
.Что еще почитать на эту тему
Исходники, бинарники и документацию старых версий UNIX
Тексты Денниса Ритчи
Документация к SIMH