Pull to refresh

Эмулятор i8080 на bash

Reading time4 min
Views7.5K
Сегодня они пишут xonix, а завтра напишут на баше отдельную операционную систему с фреймворком и СУБД.

Наконец, завтра наступило. В bash (после некоторого допиливания) можно запустить серьезную ОС, например, CP/M. А для CP/M определенно есть СУБД, компиляторы и многое другое.



Почему не нужно и зачем нужно делать эмуляторы на bash, работающий эмулятор i8080 на bash и несколько советов по ускорению работы bash-скриптов -

Почему не нужно делать эмуляторы на bash


Bash работает неторопливо. Даже для такого простого процессора, как i8080, едва ли скорость эмуляции будет хотя бы 1% от реальной. На моем Celeron с частотой 2.2ГГц эмулятор работает со скоростью примерно 100 операций в секунду, в то время как реальный процессор i8080 на 2.5МГц отрабатывает 600 тысяч операций в секунду.

Другая важная проблема: в bash невозможно работать с двоичными данными. Если вы хотите вывести в порт или считать из файла символ с кодом 0x01, то bash — явно не тот язык, который вам нужен.

Третья проблема. Разработка эмулятора — это задачка не на час и не на два. Не стоит писать эмулятор без редактора с подсветкой синтаксиса и без отладчика. Когда я начала «сочинять» свой эмулятор, то думала: «А, нужно сделать всего-навсего 84 команды! Ерунда какая!». Дойдя до 51го кода операции, я наконец осознала, что команда != код операции, и что предстоит реализовать еще 200 кодов. Если бы я сразу учла, что кодов операций за две сотни, то не стала бы писать эмулятор. А раз уж начала писать — пришлось доделывать.

Зачем нужно делать эмуляторы на bash


Затем, что это супер-пупер-мега круто. Затем, что это дает огромный опыт программирования на bash. Затем, что программы в эмуляторе будут выполняться очень-очень медленно, и можно будет уследить за изменением флагов и регистров.

Советы по ускорению кода на bash


Скорость выполнения кода для эмулятора критически важна. Если вы хотите, чтобы ваш скрипт на bash выполнялся как можно быстрее, то:
  • не используйте expr и bc, если это возможно;
  • считывайте файлы командой readarray, а не построчно командой read;
  • замените все A=$(( A + 1 )) на (( ++A ));
  • не изобретайте велосипеды, аналогичные существующим командам bash;
  • старайтесь не использовать знак $.

Вызов expr или bc в цикле может замедлить вашу программу в несколько раз.
$ time ( for ((i = 0; i < 100000; ++i)); do echo `expr 1 + 2` ; done ) 
real	0m17.000s
user	0m13.261s
sys	0m8.061s

$ time ( for ((i = 0; i < 100000; ++i)); do echo "$(( 1 + 2 ))" ; done ) 
real	0m3.980s
user	0m2.371s
sys	0m0.237s


Считывание файлов в массив командой readarray дает иногда огромный выигрыш по сравнению с командой read в цикле (зависит от длины файла), и уж точно гораздо симпатичнее выглядит.
$ time ( for ((i = 0; i < 10; ++i)); do while read line ; do  rom[${tmpcnt}]=$line ; (( ++tmpcnt )) ; done < bas.e80  ; done )
real	0m6.888s
user	0m5.516s
sys	0m0.336s

$ time ( for ((i = 0; i < 10; ++i)); do readarray -t rom < bas.e80 ; done ) 
real	0m0.146s
user	0m0.048s
sys	0m0.004s


Команда инкремента работает быстрее команды сложения.
$ time ( for ((i = 0; i < 100000; ++i)); do a=$(( a + 1 )) ; done ) 
real	0m4.489s
user	0m3.692s
sys	0m0.108s

$ time ( for ((i = 0; i < 100000; ++i)); do (( ++a )) ; done ) 
real	0m4.053s
user	0m3.296s
sys	0m0.140s


Для перевода чисел из шестнадцатеричной системы счисления в десятеричную я использовала свою функцию:
hex2dec () {
uw=`expr index "0123456789ABCDEF" "${1:0:1}"`  
lw=`expr index "0123456789ABCDEF" "${1:1:1}"`  
res=$(( ( ${uw} - 1 ) * 16 + ${lw} - 1 ))
}

Учитывая то, что я говорила про expr, можно догадаться: работала эта функция не очень быстро. Добрый mkot подсказал мне простой способ перевода чисел из одной системы счисления в другую:
hex2dec () {
define -i res
res="16#$1"
}

Благодаря всего лишь одной этой замене скорость эмуляции возросла в три раза. Поэтому читайте Advanced Bash-Scripting Guide внимательнее, и не придумывайте тормозных велосипедов.

Заключительный совет. Старайтесь не использовать знаки $ там, где это возможно, потому что доллары США негативно влияют на свободную оболочку:
$ time ( for ((i = 0; i < 100000; ++i)); do a=$(( $a + 1 )) ; done ) 
real	0m5.155s
user	0m4.828s
sys	0m0.088s

$ time ( for ((i = 0; i < 100000; ++i)); do a=$(( a + 1 )) ; done ) 
real	0m4.489s
user	0m3.692s
sys	0m0.108s


Эмулятор i8080


На разработку эмулятора ушло около 8 часов. Вероятно, в нем не обошлось без багов (особенно переживаю за логические функции), но в целом он работает, и довольно успешно. Часть советов по оптимизации я применила в эмуляторе, а часть — нет, с надеждой, что дополнительные правки ускорят выполнение программ в эмуляторе незначительно. К сожалению, эмулятор не поддерживает работу с портами и прерываниями, зато в нем есть функция «Вывод символа на экран» из программы Монитор для Радио 86РК.

К сожалению, исходный код эмулятора на сегодняшний день (1 июня 2017) утерян.

Запускать следующим образом:
$ ./emu.sh program.e80


На экран будет выведено содержимое регистров и флагов.

Программы следует записывать в шестандцатеричном виде, по одному байту в строке.
Пример программы, выполняющей пустой цикл 256 раз:
3D
C2
00
00
76


Любителям программировать в машинных кодах наверняка поможет краткое руководство из виртуального компьютерного музея.

Ах да, насчет CP/M. Запустить её, конечно, можно. Но загрузка ОС и печать приглашения на экран может занять несколько минут.

На сегодня всё. Счастливого всем хакинга.
Tags:
Hubs:
+78
Comments37

Articles