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

  • Tutorial
Сегодня они пишут 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. Запустить её, конечно, можно. Но загрузка ОС и печать приглашения на экран может занять несколько минут.

На сегодня всё. Счастливого всем хакинга.
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 37
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Это действительно супер-пупер-мега круто. Я думал меня сложно чем-то удивить на баше. Приятно узнать, что я ошибался :) А за советы по оптимизации отдельное спасибо
      • 0
        После мультиплеера на .bat меня уже даже линукс на JS не удивляет -_-
      • 0
        о_О вы — маньяк
        • +62
          Когда я был мелкий, у нас сосед из крышечек из под бутылок с тархуном сделал голую бабу. Я рад, что нашлись продолжатели этого великого дела!
          • +3
            Вы сделали мой день :))))))
            • +2
              Модель бабы получилась действующая в натуральную величину?
              • +8
                Достаньте руки из под стола! :-)
            • +4
              Не нашел, что же будет с эмулятором, если PC станет больше 65535.
              Тоже самое касается и SP, только с учетом того, что он вниз растет.
              • +3
                Исправил. Спасибо за умную мысль.
              • +12
                Ну вот, на JS эмуляторе Linux запустить в bash i8080 эмулятор в котором запустить программу работы с RSA. И это будет лучшим тестом на производительность процессора и браузера в целом.
                • +8
                  Нет. Это будет провалом теста на здравый смысл %)
                • +5
                  «На разработку эмулятора ушло около 8 часов»
                  После этой фразы я переосмыслил свою скорость разработки…
                  • +6
                    На самом деле задача решена слишком прямолинейно. В ней 80% копи-пасты с мелкими правками. Поэтому ничего удивительного в скорости решения нет.
                    Код программы, на самом деле можно сократить раза в 3-4. Не знаю, правда как оно скажется на скорости работы.
                    В чем возможность
                    Если расположить систему комманд i8080 в виде таблицы 16*16, то сразу станет видна битовая структура комманд. Старшие биты кода операции отвечают за производимое действие, младшие — за регистры, над которыми надо надругаться произвести операции.
                    • 0
                      Гораздо лучше — в виде куба. Там регистры по три бита занимают, а упоминаются в куче команд. В том числе именно поэтому в те времена восьмеричная система была такой популярной.
                      • +3
                        На самом деле, я в курсе, что можно сократить код в 3-4 раза. Что можно сделать его более компактным. Что можно представить MOV R,R как 01DDDSSS. Но это привело бы к значительному снижению производительности. Во-первых, нужно было бы добавить перевод байта в набор бит. Во-вторых, нужно было бы добавить дополнительные условия для определения кода функции, DDD и SSS. Не принимая во внимание затраты на перевод байта в набор бит, я сравнивал производительность двух if (а их меньше двух никак не выйдет) в цикле против одного case.

                        #!/bin/bash
                        func1 () {
                        :
                        }
                        
                        func2 () {
                        :
                        }
                        
                        a=1
                        b=0
                        for (( i=0 ; i<100000 ; i++ )) do
                        if (( a == 1 )) 
                        then
                        if (( b == 1 ))
                        then
                        func1
                        fi
                        else 
                        func2
                        fi
                        done
                        

                        real 0m1.203s
                        user 0m1.202s
                        sys 0m0.030s

                        #!/bin/bash
                        a=1
                        for (( i=0 ; i<100000 ; i++ )) do
                        case "$a" in
                        1) ;;
                        *) ;;
                        esac
                        done
                        

                        real 0m0.890s
                        user 0m0.890s
                        sys 0m0.030s

                        Иногда примитивные варианты производительнее.
                        И, да, ваш вариант реализовать можно еще быстрее, чем мой. Я проверял.
                        • 0
                          А зачем переводить в биты? Делаем «логическое и» и отсекаем все лишнее.

                          Тест скорости работы, как мне кажется немного не корректно построен. В первом случае текст программы длинней. Интерпретатору больше приходится по файлу программы бегать :). Это же не компилятор. Не знаю как баш, а виндусовый интепретатор читает и выполняет код построчно.

                          Используя битовые маски можно существенно сократить количество пунктов в «case». Обрабатывая группы команд (арифметические, логические, пресылок и т.д.). И как это скажется на производительности может показать только эксперимент.
                          • +2
                            Я делал разные тесты, и case все равно был быстрее, чем пара if-ов.
                            Без if я вижу только один вариант: в качестве регистров использовать массив, в случае с MOV получить номера в массиве регистра-приемника и регистра-источника — (( ( OPCODE & 2#00111000 ) >> 8 )) и (( OPCODE & 2#00000111 )), а затем обращаться к элементам этого массива по номерам. Но так будет гораздо медленнее, чем с case или if — этот случай я тоже проверял. Массивы работают заметно медленнее, чем просто переменные — чуть ли не в 5 раз. По крайней мере, на моей машине.
                    • +8
                      > Вызов expr или bc в цикле может замедлить вашу программу в несколько раз.
                      Неудивительно, если вы используете в первом случае 100к итераций а во втором 10к :)
                      • +1
                        Исправил. Спасибо. Все равно — гораздо быстрее работает без expr.
                      • +6
                        Мсье знает толк в извращениях.
                        • +9
                          Универсальный комментарий в этом блоге.
                          • +5
                            Ваш тоже.
                            • +5
                              Скоро и ваш станет.
                              • +5
                                Универсальная ветка!
                                • +4
                                  Ах, ализар, ализар… Или это не отсюда?
                          • +3
                            Пора уже этот коммент автоматически вставлять после каждой публикации в «Ненормальном программировании». Ну просто чтобы руками не набивать всякий раз :)
                            • +1
                              А еще «маразм крепчал» в блоге «Интеллектуальная собственность» и что-нибудь про Erlang в «Node.js».
                            • 0
                              Для этого человека предлагаю другой коммент! habrahabr.ru/blogs/artificial_intelligence/130877/#comment_4343678
                              Он все больше и больше заставляет меня комплексовать над заурядностью моего мышления!
                            • +4
                              Пора уже заводить отдельный блог — Извращения на Bash
                              • +1
                                После того, как я написал первые 7 программ под этот процессор, меня пытались разбудить ночью и я в беспамятстве бормотал про запись в регистры…
                                • +1
                                  Если вы хотите, чтобы ваш скрипт на bash выполнялся как можно быстрее

                                  … то перепишите его так, чтобы он запускался и работал под обычным sh?
                                  • 0
                                    В случае с эмулятором нужно править арифметику и реализовывать массивы самостоятельно, например, вот таким способом. После этого заработать должно в sh, а может и в ash.
                                  • 0
                                    Традиционно.
                                  • 0
                                    Нежно люблю 8080, порадовался. :)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое