Вход в Зоопарк ручных компьютеров
Вход > Палеонтологический музей > Маленькие истории > Palm OS Emulator
--

Кейт Роллин krollin@palm.com
Senior Software Engineer, 3Com Palm Computing Division
Palm OS Emulator

Эмулятор PalmOS — это программа, которая, как можно догадаться, позволяет эмулировать работу PalmOS не на Palm (например, Pilot 1000, Pilot 5000, PalmPilot, и Palm III) на настольном ПК в среде Windows или Macintosh. Он позволяет весьма полно и корректно эмулировать аппаратные средства, которые используются как основа для платформы Palm Computing, а кроме того, имеет несколько функций, которые полезны разработчикам ПО для отладки программ, рассчитанных на PalmOS.

Эмулятор PalmOS изначально создавался как инструментальное средство для разработчиков, хотя его запустить может каждый пользователь. Это означает, что вы можете, например, поиграть в свою любимую игру для Palm на ПК с Windows или Macintosh.

В начале было...
Эмулятор PalmOS начал жизнь как UN*X Amiga Emulator (UAE), который был разработан Берндом Шмидтом в Германии (www.freiburg.linux.де/~uae). И ПК серии Commodore Amiga и органайзеры Palm основаны на ЦП серии 680x0, так что исходная программа эмуляции ЦП процессора для UAE была использована как основа для подсистемы эмуляции процессоры в CoPilot.

В середине 1996 г. Грег Хьюджилл (www.hewgill.com) начал работы над проектом CoPilot, используя UAE как базу для эмулятора. К ядру подсистемы эмуляции ЦП 68000 он добавил поддержку ЦП 68328 Dragonball, который применялся в Palm, и других аппаратных средств Palm.

В сентябре 1996 г он выпустил первую бета-версию CoPilot 1.0. С течением времени он дорабатывал программу, добавляя новые функции, и реализовал возможность работать с более поздними версиями PalmOS, закончив работу над проектом с созданием CoPilot 1.0 b9, которая была опубликована в середине 1997 г. [дополнительную информацию об этом проекте можно найти в работе Грега «CoPilot: проект и Разработка», прим. ред.]

CoPilot отправляется в путь
Грег был заинтересован в том, чтобы его работа приносила пользу максимально возможному количеству разработчиков и в октябре 1996 г. он опубликовал исходные тексты CoPilot на своем Web-узле. Он предложил любому разработчику возможность перенести CoPilot на другие платформы, и его предложение было принято. CoPilot сегодня перенесен на Macintosh (две версии), Linux, BeOS, OS/ 2 и даже Windows CE.

Palm протягивает руку
В декабре 1997 г. я оставил группу разработки Newton и перешел в Palm Computing, в качестве инженера в группе средств разработки. Я был вторым сотрудником в этой группе и мне было что делать. Одной из этих задач было адаптировать CoPilot для разработки нашего будущего продукта — Palm III. По сути дела, CoPilot нужно было переделать так, чтобы он мог работать с ПЗУ, размером более 1 Мбайт, экраном с градациями серого, емкой динамической памятью и обработку новых регистров Dragonball, которые мы задействовали.

Работая с исходными текстами CoPilot 1.0b9 и с исходными текстами версии для Macintosh, которые предоставила компания Illume Software (members.aol.com/illumesoft/illume.html), особенности были достаточно быстро реализованы и стали доступны разработчикам для PalmOS.

CoPilot превращается в PalmOS Emulator
Переделывая CoPilot в рамках проекта Palm III, мы поняли, что эта программа имеет огромный потенциал. До того момента, Palm предлагала для разработки программ на ПК пользоваться Palm Simulator, комплект библиотек для CodeWarrior, которые позволяли компилировать из одних и тех же исходных текстов программы для Macintosh и Palm OS. Разработчики создавали свою программу для PalmOS, компоновали с библиотеками Simulator, которые позволяли запускать программу для PalmOS в среде Macintosh, запускали и отлаживали ее как обычную программу для Macintosh.

Метод предоставлял несколько преимуществ — например, минимальная длительность цикла «редактирование-компиляция-отладка», возможность отладки на уровне исходных текстов на языках высокого уровня, скорость программы и возможность создания «ознакомительных версий» программы, которые пользователь мог «посмотреть» на ПК. Однако, он был связан и с недостатками — например, подготовка и отладка двух версий — имитатор не обеспечивал полную эмуляцию Palm и, разумеется, необходимость иметь Macintosh. Мы подумали, что было бы правильно объединить преимущества Simulator и CoPilot, создав действительно мощное средство разработки для PalmOS, которое могло бы работать на разных платформах.

Мы поговорили с Грегом Хьюджиллом, (автор CoPilot для Windows), Крейгом Шеффилдом из Illume Software и Шмидтом Берндом (создателем UAE и ядра подсистемы эмуляции ЦП 68000) и договорились о разрешении продолжать разработку CoPilot, используя их работу как базу для разработки эмулятора PalmOS. Они поддержали нашу разработку более мощной версии эмулятора, особенно когда мы объявили, что исходные тексты будут доступны всем разработчикам.

Кроме того, в это время Palm Computing приняла решение о переходе на новую торговую марку. PalmPilot стал Palm III, PilotDebugger — PalmDebugger, CoPilot превратился в эмулятор PalmOS (PalmOS Emulator). В этой материале я буду обозначать названием CoPilot первые версии, разработанные Грегом, а «эмулятор PalmOS» — версии, которые создала Palm Computing.

Новые возможности
После того, как были урегулированы юридические тонкости, мы начали переделывать программу. В этой статье вы найдете краткое обсуждение этих переделок.

Общая база исходных текстов
Каждый разработчик имеет собственный стиль программирования: Бернд, Грег и Крейг все работали по-разному. Когда Крейг переносил эмулятор из Windows на Macintosh, он кое в чем улучшал и так прекрасный эмулятор, разработанной Грегом. Он изменил оформление исходных текстов, использовал более описательные имена функций и переменных, добавив комментарии. Когда он завершил работу, портированная им версия CoPilot была функционально эквивалентна версии для Windows, но была реализована немного не так.

Первой задачей было объединить исходные тексты обеих программ. Поскольку я собирался разрабатывать и поддерживать редакции для Windows и для Macintosh, я решил, что будет правильно иметь возможность синхронно развивать PalmOS Emulator для всех платформ.

В итоге, я объединил исходные тексты, так, что 93 процента исходных текстов были идентичны (кроме исходных текстов, подготовленных в PowerPlant для Macintosh).

Свежие исходные тексты UAE
С тех пор как Грег использовал исходные тексты UAE в качестве основы для CoPilot в середине 1996 г., Бернд продолжал изменять UAE, закончив на версии 0.69, которую опубликовал в мае 1997 г. Поскольку я уже поигрался с исходными текстами, объединяя версии для разных платформ, я также решил корректировать и исходные тексты подсистемы эмуляции ЦП, использовав свежие исходные тексты UAE. Это позволило исправить несколько ошибок, кроме того, изменения, которые делал Бернд, предоставили возможность увеличить скорость работы подсистемы, которая эмулировала ЦП.

Инсталляция больших программ
Изначально, одна из основных проблем CoPilot состояла в невозможности загружать программы или базы данных из файлов, размером более 64 Кбайт. Причина — метод, который использовался в CoPilot для загрузки программы. CoPilot работал так:

  • выделял специальный сегмент памяти (по существу, добавлялось 64 Кбайт памяти к эмулируемому ОЗУ PalmOS);
  • оформлял его как 64-Кбайт блок (chunk) памяти (подобно тому, как работает функция MemPtrNew, когда вы выделяете себе память);
  • переносил туда содержимое файла PRC или PDB; и
  • инициировал вызов DmCreateDatabaseFromImage.

Проблема этого метода — он связан с необходимостью выделения куска памяти методами, совместимыми с MemPtrNew. В PalmOS OS 1.0 и 2.0 блок памяти не могли превышать 64 Кбайт. Использование этого метода означало еще и проблемы совместимости с PalmOS 3.0, где заголовок блока памяти занимал не шесть байт, а восемь. Этот прием просто не работал бы, когда CoPilot запускался бы с PalmOS 3.0.

Теперь используется иной метод. Вместо запуска DmCreateDatabaseFromImage, функциональность DmCreateDatabaseFromImage реализована в эмуляторе PalmOS. Когда пользователь инсталлирует файл PRC/PDB, то создается новая БД, основываясь на информации, содержащейся в заголовке файла, из файла считываются компоненты ЬЛ PalmOS и записываются в области ОЗУ БД эмулятора. Используя эту технику, эмулятор PalmOS перестал быть ограничен 64-Кбайт блоком памяти и перестал быть связан с форматом или размером заголовка блока памяти.

Внешний отладчик
Одно из преимуществ Palm Simulator в Macintosh — возможность работы с отладчиком, который позволяет отлаживать программы на языке высокого уровня при создании программы. По контрасту, редакция CoPilot для Windows имела только возможность отладки ассемблерных текстов с помощью отладчика, работающего в режиме командной строки, версия CoPilot для Macintosh не содержала отладчика.

В феврале 1998 г. мы пригласили Эрика Клонингера из Portable Computing Products, Марка Корри из Metrowerks и Кеннета Альбановски из Silver Hammer Group посетить нас, посмотрев на бета-версию PalmOS 3.0. Мы планировали поговорить о возможности интеграции эмулятора PalmOS и других отладчиков (например, отладчик Metrowerks и gdb).

На момент подготовки этой статьи (март 1998 г.) в целом поддержка внешних отладчиков работала. Предварительная версия Metrowerks Debugger позволяла отлаживать исходыне тексты программ, работающих на эмуляторе PalmOS. Наша тестовая версия «вскрытого» отладчика PalmDebugger позволяла отлаживать программы на ассемблере. аппликации на машинном языковом уровне. Скоро мы передадим исходные тексты Silver Hammer Group, которые они используют для адаптации редакции CoPilot для UNIX и пакетов gcc/gdb. Мы предоставим их и компании SoftMagic (разработчик программы Satellite Forms) и другим создателям инструментальных средств для PalmOS.

Гремлины
Одним из преимуществ, которые имели разработчики, работавшие на Macintosh — комплект утилит, которые не были доступны в редакции SDK для Windows. Один из них — «Гремлины». Это подпрограмма, которая пересылает серию псевдослучайных событие в очереди событий программы для PalmOS, что позволяет имитировать нагрузку, которую не способен создать пользователь. Если в программе есть ошибка, то серия событий, которая позволила ее определить может быть воспроизведена, позволяя разобраться с проблемой. Гремлины были реализованы в PalmDebugger и в Simulator, что делало невозможным использование такого полезного средства на других платформах.

Эмулятор PalmOS был расширен, и в нем завелись «Гремлины». Запуская нового «Гремлина» из пункта меню, пользователь мог выбрать несколько режимов:

  • номер (определял параметры инициализации генератора псевдослучайных чисел);
  • количество псевдослучайных событий, которые передаются в программу;
  • программа, которую нужно тестировать; и
  • параметры записи событий в журнал.

Реализация «Гремлинов» в редакции эмулятора PalmOS для Windows позволила многим разработчикам принять участие в программе сертификации качества программ.

Скорость
Проверяя, какие преимущества Simulator можно реализовать с эмуляторе PalmOS, мы нашли одно, которое было невозможно перенести в POSE — скорость. Программы, созданные с помощью библиотек симулятора — полнофункциональные программы для Macintosh, которые работают с максимальной скоростью, которую только можно реализовать для ЦП PowerPC. Работа программы в среде PalmOS на виртуальном процессоре эмулятора, где каждая машинная команда порождает запуск подпрограммы, которая записывает информацию в «регистры» и имитирует доступ к памяти. Даже на мощных моделях ПК, скорость работы эмулятора — примерно 2 млн. инструкция ЦА Dragonball в секунду. Эта скорость примерно соответствует скорости работы реального Palm, но она в десятки раз меньше, нежели работа программы на ПК.

Я применял два метода для ускорения работы POSE по сравнению с CoPilot. Первый метод основан на основном принципе увеличения скорости: если функция работает слишком долго, сделайте ее быстрее. Используя этот метод, я обнаружил множество мест, которые могли бы быть ускорены на несколько процентов. Кроме того, для ускорения POSE я использовал метод оптимизации, который мы применяли при разработке Newton: возьмите функцию, которая может быть логически разделена на несколько подпрограмм, для каждой из которой определяется частота запусков, после чего те, которые нужны не постоянно переносили в собственные функции, упрощая основную функцию. Результат — более компактные функции компилятор может эффективнее оптимизировать. Результат такой оптимизации — увеличение скорости примерно на 20%.

Использовался и другой принцип оптимизации: если подпрограмма работает долго, не применяйте. На практике это означает, что вам придется изменить алгоритмы, кэшируя промежуточные результаты и используя эмпирические знания о работе программы. Но в случае эмулятора PalmOS, мы можем использовать подход, связанный с имитацией ОС, вместо «полноценно» эмуляции, заменив исконные функции, более скоростными. В качестве примера можно отметить менеджер системных исключений. Когда PalmOS запускает системную функцию, скажем, MemPtrNew или DmCreateDatabase, компилятор задает в машинный код инструкцию TRAP $F, передавая идентификатор формы $Axxx. Когда программа запускает эту инструкцию, ЦП генерирует аппаратное исключение, заносит в стек счетчик команд и регистр статуса и переходит по адресу в нижней памяти, который хранится в векторе прерывания. PalmOS передает подсистеме обработки исключений адрес этого вектора. Таким образом, когда системное исключение TRAP $F, выполняется, запускается подсистема обработки прерываний. Эта подпрограмма затем использует значение счетчика команд из стека, использует его для выборки адреса очередной инструкции прикладной программы, которая размещена после инструкции TRAP и использует номер исходного адреса, чтобы передать потом выполнения на адрес в ПЗУ.

Процедура не очень длительная, но она занимает определенный период, причем, это происходит каждый раз, когда запускается функция PalmOS (а также когда функция ПЗУ запускает другую функцию в ПЗУ). Проще говоря, это происходит часто.

Для увеличения скорости, я реализовал несколько специальных алгоритмов, основанных на эмпирическом знании о работе PalmOS, когда она запускает функции. В принципе, это обход подсистемы управления прерываниями в эмуляторе и реализация этой операции своими руками. Теперь, когда эмулятор PalmOS сталкивается с инструкцией TRAP $F, вместо сложного пути условных переходов и использования функция подсистемы управления прерываниями, она реализует эту функцию на базе «подлинных» команд ЦП ПК. POSE:

  • записывает счетчик команд, который был передан в стек;
  • использует счетчик команд, выбирая адрес инструкции, которая будет размещена за инструкцией TRAP $F;
  • определяет массив указателей на функции ПЗУ, размещенные в нижней памяти;
  • записывает указатель на нужную функцию, основываясь на номере прерывания;
  • передает управление функции, изменяя счетчик команд.

Эмулятор PalmOS работает по этому алгоритму, заменяя эмулируемые инструкции обработчика прерываний своими, быстрыми, работающими на ЦП ПК. Скорость была увеличена на 12 процентов.

Для увеличения производительности функций ПЗУ, связанных с копированием областей памяти применялся новый прием. Подпрограммы MemSet, MemMove и RctPtInRectangle — часто используемые — были реализованы на базе инструкций ЦП ПК, в POSE, а с помощью эмуляции «родных» функций PalmOS.

Журнал
Поскольку мы рассчитывали сделать POSE полезным разработчикам, то надо было предоставить информацию о работе программы, которая могла быть полезна для разработки. Наиболее популярная проблема в случае сбоя программы проста — что вообще произошло? В 90% случаев, первопричина аварии располагается, мягко говоря, не близко к месту, где ошибка проявилась. С возможностями записи информации, которые были реализованы в эмуляторе PalmOS, разработчики могли изучить журнал событий, который происходили перед ошибкой. Разработчик может просмотреть события в очереди событий программы и те, которые программа обрабатывала. Он может просмотреть список вызовов функций ПЗУ. В конце концов, он может просмотреть дамп памяти в виде ассемблерного листинга, до ошибки.

Если я вдохновлюсь, то в одном из будущих версий, добавлю возможность «отката», так, чтобы все состояние эмулятора было зафиксировано и постепенно отменено, чтобы разработчик мог изучить в «замедленном режиме» события и функции, которые происходили перед ошибкой.

Управление памятью
Первый момент для реализации прикладной программы — понять, что она работает в дружественной среде. То есть, нужно понять, что программа работает и работает как надо, реагируя и на предполагаемые и на неожиданные события. Затем надо проверить, как она работает в негостеприимной среде. Эмулятор PalmOS позволяет это проделать и даже предоставляет несколько большие возможности. Например, эмулятор контролирует динамическую память, когда программа завершается, определяя утечки памяти. Обнаружив проблему, POSE выдает сообщение. Или, например, POSE позволяет определить некорректную работу со стеком, когда подпрограмма возвращает управление вызывающей функции, стек ниже текущего указателя вершины очищается, что упрощает поиск ошибок, связанных с использованием данных, которых в стеке уже нет. Наконец, эмулятор обнаруживает переполнение стека.

Тестируем программы
Palm OS Emulator — это не только инструмент для разработчиков. Пользователи программ для PalmOS могут использовать его для проверки корректности программ. Palm Computing заинтересована в том, чтобы программы для PalmOS были надежны (для этого и созданы такие инструментальные средства, как «Гремлины» и программы сертификации ПО). В PalmOS Emulator были реализованы средства, с которыми можно отловить ошибки, возможно, не проявляющиеся на современных моделях, но которые могут «всплыть» в будущих. POSE проверяет доступ к нижней памяти, регистрам ЦП, видеопамяти. Попытка программы использовать какой-либо некорректный прием будет обозначена предоставлением пользователю информации о нарушении программой рекомендаций про разработке для PalmOS (следовать рекомендациям по разработке для PalmOS — правильная идея, отладчик PalmOS проверяет некоторые некорректные действия и в будущем их, наверное, будет больше; см. «Руководящие принципы для разработчиков»).

Поскольку POSE проверяет ПО, пользователь может инсталлировать программу, например, скопированную из Интернет, запустить их на эмуляторе PalmOS и проверить, как они работают и нет ли проблем при интенсивной работе. Если программа работает, пользователь знает, что покупка этой программы — хорошая трата денег.

Прочее
Мы проектируем много новых функций, например, загрузка файлов ПЗУ и сеансов просто «перетаскиванием». В каталоге AutoLoad можно будет разместить файлы, которые автоматически будут загружаться в POSE при запуске эмулятора. Синхронизация с Palm Desktop, профилировщик программ, который позволяет упростить оптимизацию, возможность «отката», когда POSE сохранит состояние ЦП и памяти (так то, их можно будет восстановить в любой момент) — это и многое другое.

Внутренняя организация POSE для Windows и Macintosh разрабатывалась на базе UAE и CoPilot. Для разработчиков, которые интересуются, как POSE работает, мы подготовили обзор, который идет ниже.

Эмулятор ЦП
Я упомянул раньше, что 93% исходных текстов POSE разделяется между версиями для Windows и Macintosh. Большая половина в этих 93% принадлежит подсистеме эмуляции ЦП. Эмулятор ЦП прост: считываем код операции из памяти и выполняем его. Фактически, вот отрывок из цикла эмуляции ЦП в POSE:

while (gCPUState == kCPUState_ Running)
{
uae_ u32 opcode = nextiword ();
(* cpufunctbl[opcode]) (opcode);
}

Этот обрезок реализует простой алгоритм, который приводится выше: выбирается функцией nextiword код операции из ячейки памяти, заданной в регистре PC, затем использует этот код операции как индекс в таблице 65,536 указателей, по которым выбирается адрес и подпрограмма обработки инструкции.

Но эмулятор ЦП в то же время очень сложен — есть масса кодов операций и много небольших деталей, которые надо учитывать, эмулируя их. Изменения статусных битов, работа с памятью нужно проверить и реализовать, обработать нестандартные ситуации, прерывания, работу супервизора, условные операторы обработаны — достаточно, чтобы свести разработчика с ума.

Я уверен, Бернрд Шмидт мыслил так же. Первая версия UAE (версия 0.1, доступная на упомянутом прежде Web-узле USE) было тщательно проработанным вручную эмулятором. Так как разработка UAE продолжалась, Бернрд реализовал идею «компилятора эмулятора» ЦП.

Этот компилятор работает с небольшими текстовыми файлами, содержащими описание всех кодов операции 680x0. Затем, он использует информацию из них для генерации обработчиков кодов операций. Результат: 16 файлов исходной программы, содержащих тысячи обработчиков инструкций ЦП и другой файл, содержащий огромный массив cpufunctbl, с указателями.

Вот пример:

1101 rrr0 zzss sSSS: 00: XNZVC:NN: ADD. z s, Dr

Это запись файла описания ЦП для машинной инструкции ADD. Слева находиться описание битов кода операции: 0 и 1 интерпретируются как значения для регистра счетчика команд, z — представляет размер регистра, s — биты типа адресации. Следующее биты описания коды операции задают ЦП на котором код операции должен работать (00 — ядро 68000, прямой потомок которого — Dragonball). Затем описывается использование регистра статуса. Последний код задает метод адресации операндов.

Вот пример функции, которая создается по шаблону этой операции:

void REGPARAM2 CPU_ OP_ NAME(_ d000)( uae_ u32 opcode)
/* ADD */
{
uae_ u32 srcreg = (opcode & 7);
uae_ u32 dstreg = (opcode >> 9) & 7;
{{ uae_ s8 src = m68k_ dreg( regs, srcreg);
{ uae_ s8 dst = m68k_ dreg( regs, dstreg);
{{ uae_ u32 newv = (( uae_ s8)( dst)) + (( uae_ s8)( src));
{ int flgs = (( uae_ s8)( src)) < 0;
int flgo = (( uae_ s8)( dst)) < 0;
int flgn = (( uae_ s8)( newv)) < 0;
ZFLG = (( uae_ s8)( newv)) == 0;
VFLG = (flgs == flgo) && (flgn != flgo);
CFLG = XFLG = (( uae_ u8)(~ dst)) < (( uae_ u8)( src));
NFLG = flgn != 0;
m68k_ dreg( regs, dstreg) =
(m68k_ dreg( regs, dstreg) & ~0xff) | (( newv) & 0xff);
}}}}}}}

Эта функция извлекает исходные и целевые регистры, закодированные в трех первых битах кода операции (соответствующих параметру SSS в описании кода операции в файле определений), затем целевые регистры (закодированные в трех битах RRR в описании инструкции). Затем значения регистров считываются (массив из 16 целочисленных переменных предусматривается в эмуляторе ЦП). Две величины складываются, биты регистра статуса изменяются, новая величина сохранена в целевом регистре.

Обращение к ОЗУ
Дополнительно к приемам оптимизации, которые рассматривались выше, надо заметить, что нужно иметь в виду правило «скорость против размера». Нередко вы можете уменьшить свои функции и сократить алгоритмы, но обычно при этом уменьшается скорость работы. Равно справедливо и обратное: вы можете ускорить программу, позволяя ей съесть столько памяти, сколько она сможет (до тех пор, пока вы не достигнете точки, где дисковая активность, порожденная работой ОС с виртуальной памятью практически остановит вашу программу).

Функция cpufunctbl, которую мы рассматривали в разделе, посвященном основному циклу эмуляции ЦП — практический пример. Используя 256-Кбацт таблицу, которая содержит 64 указателя, мы можем обрабатывать инструкции ЦП, быстро передавая указатели соответствующим подпрограммам. И обращение к ОЗУ работает примерно так же. Адресное пространство в 4 Гбайт разделяется на 65,536 сегментов ("банк» в исходных текстах UAE), каждый по 64 Кбайт. Каждый из этих банков доступен с помощью специальных функций. Когда необходимо считать информацию из «ОЗЦ», PalmOS Emulator использует верхние 16 битов адреса (номер банка) как индекс на 64-Кбайц таблицу. Каждая запись в ней — четырехбайтовый указатель на набор функций, управляющих этим банком. PalmOS Emulator затем извлекает функцию, подходящую для нужного действия и запускает ее.

Простой пример разъясняет это описание. Допустите что мы обращаемся первые два банка (первые 128 Кбайт из 4-Гбайт адресного пространства) как к обычному ОЗУ. Сначала, мы создаем набор функций, чтобы рассматривать кусок памяти как область памяти с прямым доступом:

uae_u32 RAMBank_GetLong (uaecptr iAddress)
{
return do_get_mem_long((
(char*) gRAM_Memory) + iAddress);
}
uae_u32 RAMBank_GetWord (uaecptr iAddress)
{
return do_get_mem_word(
(( char*) gRAM_Memory) + iAddress);
}
uae_ u32 RAMBank_ GetByte (uaecptr iAddress)
{
return do_ get_ mem_ byte(
(( char*) gRAM_ Memory) + iAddress);
}
void RAMBank_ SetLong (uaecptr iAddress, uae_ u32 iLongVal-ue)
{
do_ put_ mem_ long(
(( char*) gRAM_ Memory) + iAddress, iLongValue);
}
void RAMBank_ SetWord (
uaecptr iAddress, uae_ u32 iWordValue)
{
do_ put_ mem_ word(
(( char*) gRAM_ Memory) + iAddress, iWordValue);
}
void RAMBank_ SetByte (
uaecptr iAddress, uae_ u32 iByteValue)
{
do_ put_ mem_ byte(
(( char*) gRAM_ Memory) + iAddress, iByteValue);
}
int RAMBank_ ValidAddress (
uaecptr iAddress, uae_ u32 iSize)
{
int result = (
iAddress + iSize) <= (uae_ u32) gRAMBank_ Size;
assert( result);
return result;
}
uae_ u8* RAMBank_ GetRealAddress (uaecptr iAddress)
{
return (uae_ u8*) &((( char*) gRAM_Memory)[ iAddress]);
}

В функциях:

  • uae_u32 — беззнаковая 32-разрадная величина (аналогично uae_u16 и uae_u8);
  • uaecptr — тип UAE для указателей памяти (родственный типу void*);
  • gRAM_Memory — блок памяти с которой мы распределяем с помощью malloc, когда программа запускается;
  • do_get_mem_long и проч. — функции. для обхода узких мест при работе с памятью.

После того, как последовательная установка функций будет определена, они группируются вместе в структуру данных назвавшую AddressBank:

AddressBank gRAM_Bank =
{
RAMBank_ GetLong,
RAMBank_ GetWord,
RAMBank_ GetByte,
RAMBank_ SetLong,
RAMBank_ SetWord,
RAMBank_ SetByte,
RAMBank_ GetRealAddress,
RAMBank_ ValidAddress
};

Указатель на конкретный адрес банка затем заносится в массив из 65,536 указателей AddressBank.

gMemory_ Banks[ 0] = &gRAM_ Bank;
gMemory_ Banks[ 1] = &gRAM_ Bank;

После инициализации массива таким методом, для доступа к ОЗУ используется несколько макросов, которые скрывают детали выбора конкретного банка, выбор нужной функции и ее запуск.

Поддержка Dragonball
Многое из того, о чем мы говорили пока, было основано на изучении эмулятора, разработанного Бернрдом Шмидтом. Эта эмуляция позволяет реализовать общие черты ЦП серии 680x0. Грег Хьюджилл реализовал средства для работы регистров ЦП Dragonball 68328.

В ЦП Dragonball есть набор регистров отображаемых на память, которые предназначены для управления некоторыми аппаратными средствами. Например, эти регистры можно использовать для управления ЖК-экраном, последовательный ввод/вывод, доступ к часам реального времени и «предупреждениям». Регистры размешаются в 4-Кбайт области памяти, начинающейся с адреса 0xFFFFF000. Доступ к ним в PalmOS Emulator реализован и контролируется специальным набором функций семейства AddressBank. Даже после того, как я работал над PalmOS Emulator несколько месяцев, я изумлялся полнотой средств работы с Dragonball, которые реализовал Грег в CoPilot. Кроме базовых средств эмуляции UART, он реализовал возможность работы последовательного ввода-вывода, перенаправляя вызовы API на функции Win32. Изучая примеры битовых масок и работу прерываний, он реализовал средства эмуляции пера, так. что CoPilot превращал координаты мыши информацию о «прикосновениях» на ЖК-дисплее. Задавая значения счетчиков, он сумел заставить PalmOS корректно «засыпать». И он делал это не используя доступ к исходным текстам PalmOS, то, что лично я нахожу чрезвычайно удобным, я забываю, как правильно надо задавать тот или иной бит и пытаюсь сделать нечто с ПЗУ.

Обновление экрана
Естественно, вся эта изнурительная работа бесполезна, если мы не можем видеть результат. При работе в среде эмулятора, PalmOS записывает данные в виртуальный буфер ЖК-дисплея, но изменение его содержимого ровным счетом ничего не означает с точки зрения ПК. на котором работает эмулятор. Нам необходим метод для перемещения содержания видеобуфера в окно ОС ПК.

Метод, использованный в PalmOS Emulator OS тупой, но работает. От 10 до 20 раз в секунду, PalmOS Emulator прерывает работу текущей эмулируемой инструкции ЦП и переключается на подпрограммы прорисовки видеобуфера. С помощью полезной функции memcmpy, не только сравнивается содержимое видеопамяти с тем, что было сохранено в ней прежде, но и корректирует предыдущую копию видеоОЗУ, обновляя ее новым содержимым. Результат memcmpy — булево значение, которое показывает, изменялось содержание буфера ЖК-дисплея экрана с тех пор как в последний раз сравнивали их, или нет. Если это происходило, то содержимое видеопамяти преобразовывается в растровое изображение, которое отображается на экране ПК в окне эмулятора. На первый взгляд этот метод кажется не очень красивым. Я думал, что лучше будет изменить функции семейства AddressBank, чтобы обнаруживать попытки доступа к видеопамяти, задавать признак «перерисовать экран», при наличии изменений в видеоОЗУ. Но это как оказалось, было не столь эффективно, как я сначала подумал. Функции работы с банками памяти вызываются часто. Проверка на доступ к видеопамяти как выяснилось, представляет собой более заметную нагрузку, нежели я ожидал изначально. Результат — грубый метод оказался довольно эффективным, по сравнению с красивым. Другой метод, которым я тоже попытался воспользоваться — перехват графических функций и задание признака «перерисовать» в них.

Я избегаю использовать в PalmOS Emulator информацию о внутренней структуре PalmOS насколько возможно; имея таблицу с жестко прописываемыми адресами графических функций нарушается этот принцип. Если API когда-либо будет изменяться, я должен был бы выпускать новый эмулятор. Единственный метод, который я мог бы использовать еще — воспользоваться недокументированной системной функцией ScrDrawNotify. Если некоторая переменная, размещенная в нижней памяти, имеет значение «истина», все графические функции вызывают функцию ScrDrawNotify перед завершением, передавая ей информацию о границах изменившейся области на экране. Я предполагал, что мог бы задать это значение, прерывать функции чтобы запустить ScrDrawNotify и задавать признак «перерисовать» каждый раз. когда это происходило. Это было теорией. Но, так как сказал некий мудрый человек, «в теории, теория и практика — одно и то же. На практике, это не так». Пока метод с использованием ScrDrawNotify кажется наиболее продвинутым, однако, я не стал его использовать по нескольким причинам. Во-первых, это требует введения в эмулятор специальных «знаний» о существовании и размещении этой переменной. Мало того, что она не документирована, ее размещение разнится в PalmOS 2.0 и PalmOS 3.0. Задание признака «перерисовать» работает только при использовании программ, работающих только с API, и вызывает проблемы совместимости с программами, которые работают с видеопамятью. Palm Computing не одобряет такой практики разработки, но нельзя отрицать факт, что такие программы существуют. В конце этих исследований, я решил оставить в покое метод перерисовки, связанный с memcmpy, раз он работает и работает нормально. Профилирование программы показывает, что перерисовка экрана занимает крайне мало времени, практически, на границе точности измерения, так, что практически нет смысла оптимизировать его.

Перехват системных функций
Для того, чтобы предоставить возможность ввод данных с клавиатуры и звуковые сигналы, Грег разработал метод перехвата системных функций. Прерывая функцию EvtGetEvent, Грег подставлял в очередь событий запись с данными о нажатых пользователем на клавишах в окне CoPilot, оформленными как запись функции EvtGetEvent и передавал результат подпрограмме, которая запрашивала EvtGetEvent. Все, что знала подпрограмма, которая инициировала запрос к EvtGetEvent — произошло событие, структура данных события заполнена надлежащим образом. Откуда оно взялось и кто его инициировал — какая разница? Аналогично, с помощью перехвата SndDoCmd он перехватывал попытки программ работать с Sound Manager и эмулировал нужные платформно-зависимые операции. Смотря на параметры переданные функции SndDoCmd, CoPilot вызывал функции звукового API Windows, проигрывая звуки примерно так же, как это делает PalmOS.

Метод перехвата функций ПЗУ очень прост и основан на технике, которая использовалась при ускорении обработки прерываний. Когда программа вызывает функцию ПЗУ, она выполняет инструкцию TRAP $F. CoPilot прерывает эмуляцию этой инструкции, записывает номер функции, который располагается следом за ней, затем проверяет нужно ли надо ли как-то изменять поведение этой функции. PalmOS Emulator развивает этот метод перехвата системных функций несколько глубже. Он заменяет некоторые функции ПЗУ, идентичными функциями, но реализованными в исполнимом файле эмуляторе. Такой прием предполагает полную замену перехваченной функции. Работы с ПЗУ не происходит вообще. Эмулятор также перехватывает системные функции, чтобы генерировать события «Гремлинов» в ОС. Например, System Manager использует функцию SysEvGroupWait когда в ОС не происходит событий, которые могли бы заинтересовать прикладную программу. Как правило, SysEvGroupWait блокирует текущий поток, предоставляя возможность работать другим потокам или переключая Palm в «сон» до первого события, для которого машинка должна «проснуться».

При работе «Гремлина», PalmOS Emulator перехватывает SysEvGroupWait и посылает события. Затем SysEvGroupWait работает как обычно. Поскольку в очереди событий появляется событие, которое мы только что вставили, SysEvGroupWait немедленно завершается, позволяя передать его прикладной программе. Эти примеры предполагают перехват функций «с головы», вставку процедур обработки до того, как функция ПЗУ завершится (если ПЗУ она вообще запускается). Но есть случаи, когда нужно перехватить управление после того, как функция отработает. Перехватывая функцию «с хвоста» мы прерываем потом исполнения после того, как желаемая системная функция завершится. Нужен несколько более продвинутый способ, поскольку при таком подходе нет четкого признака, что системные функции завершились. Когда системная функция вызывается, то мы видим инструкцию TRAP $F, которую легко найти и перехватить. Когда системная функция передает управление программе, которая инициировала как запуск, она просто выполняет инструкцию RTS, которая ничем не выделяется среди других случаев ее использования. Чтобы перехватить функции после того, как системная функция завершится, RTS требует небольшой доработки. Предварительно мы перехватываем инструкцию TRAP $F, которая означает обращение к системным функциям. Затем, вместо эмуляции инструкции TRAP $F, значение регистра счетчика команд заносится в стек (что означает, перехват управления при работе инструкции RTS), PalmOS Emulator сохраняет значение регистра PC в таблице и заносит указатель на инструкцию TRAP $E в стек. Эта системная функция превращается в исправленный «хвост». Когда системная функция доходит до инструкции RTS, регистр PC показывает на инструкцию TRAP $E. Мы уже знаем метод перехвата инструкции TRAP, это просто и PalmOS Emulator перехватывает управление легко и проч. В обработчике инструкции TRAP $E PalmOS Emulator изменяет значение регистра PC, занося в него указатель из ранее подготовленной таблицы, затем выполняет любые необходимые при перехвате действия. Такой метод используется для перехвата EvtGetEvent, когда записывается извлеченные события, когда используется режим записи события в журнал.

Вызов системных функций
Прерывание системных функций — только половина общей картины весьма занятных отношений PalmOS и PalmOS Emulator. Благодаря перехвату мы можем передавать управление от эмулируемого ПЗУ PalmOS в двоичный код PalmOS Emulator. Чтобы картина была завершенной, нужен метод, который позволял бы PalmOS Emulator запускать функции PalmOS. Метод, который был применен в PalmOS Emulator — это несколько переработанная прием, придуманный Грегом для CoPilot. Идея в том, чтобы прекращать цикл работы эмулятора инструкций ЦП в некоторой удобной и безопасной позиции (Грет прерывал процесс эмуляции на инструкциях TRAP $F), сохраняем состояние регистров ЦП, заносим нужные параметры в стек и затем задаем инструкцию TRAP $F с номером нужной системной функции. Затем, управление снова передается в основную функцию точно так же, как это происходит в случае перехвата «хвоста» подпрограммы: мы храним обратный адрес инструкции TRAP (в этом случае, инструкции TRAP $D) на стеке перед вызовом системной функции. Когда системная функция передает управление вызывающей программе, запускается инструкция TRAP $D, PalmOS Emulator перехватывает эту ситуацию и наводит порядок, записывая в регистры ЦП те значения, которые были там до вызова системной функции. При таком методе, мы можем запускать системные функции, не мешая нормальной работе PalmOS.

PalmOS Emulator использует этот прием постоянно. Например, как уже говорилось, программы инсталлируются из файлов .PRC с помощью создания базы данных из файла PRC. Это проделывается вызовом в эмуляторе подпрограммы PalmOS DmCreateDatabase и DmNewResource.

История продолжается
В этой статье, я обсудил история PalmOS Emulator, от первых разработок Грега Хьюджгилла, Бернрда Шмидта и Крейга Шеффилда и основные принципы, на базе которых он был реализован. Вы также увидели, как PalmOS Emulator развивается. PalmOS Emulator в настоящее время находится в процессе развития. Я предполагаю, что в течение ближайшего года будут появляться новые редакции программы, в которых появятся новые функции и реализуется возможность эмуляции новых моделей ручных ПК на базе PalmOS. Если вы хотите принять участие в развитии PalmOS Emulator, то можете подписаться на список рассылки на ls.palm.com и просмотреть информацию по нескольким ссылкам:

Сайт Palm Computing, предназначенный для разработчиков: www.palm.com/developers/

Список рассылки Palm Computing: ls.palm.com

Web-узел Грега Хьюгила: www.hewgill.com

Web-узел Illume Software: members.aol.com/illumesoft/illume.html

Web-узел UAE: www.freiburg.linux.de/~uae

Руководящие принципы для разработчиков
Мариц Шарп, менеджер по техническому сопровождению разработчиков Palm Computing, поделился с нами некоторыми принципами, которые может использовать разработчики ПО для PalmOS. Эти приемы предоставляют вам метод для создания надежных и совместимых с будущими версиями PalmOS программ.

Один метод разработки надежных программ — разработка защищенного кода, в который добавляется множество вызовов ErrNonFatal-DisplayIf, которые в процессе отладки позволяют проверить ваши предположения о том, как работает программа. Много проблем можно поймать таким образом, эти дополнительные вызовы не замедляют вашу программу. Вы можете сохранить наиболее важные проверки в коммерческой версии, используя подпрограмму ErrFatalDisplayIf. И можно проверить несколько других потенциальных проблем:

  • Считывание и запись в память по нулевому указателю или в область нижней памяти; не забывайте проверять результат функций MemSet, MemMove и проч., дабы убедиться, что используются корректные указатели (если вы можете реализовать более полную проверку — это даже лучше). Кроме тог, можно проверить, что указатели на ваши структуры данных или передаваемые функциям PalmOS также не NULL. В отладочной версии будет разумно перекрыть вызовы MemMove (и подобных) с помощью директивы препроцессора #define для проверки корректности параметров.
  • Распределение объектов нулевой длины: это неправильно, нельзя задавать объекты с нулевой длиной или менять длину буфера до NULL. PalmOS 2.0 и предыдущие редакции допускали это, но будущие редакции ОС могут не позволять таких приемов.
  • Предположения о дисплее: начало видеопамяти, размер и количество пикселов на бит не заданы навечно; они могут измениться. Не перехватывайте функции работы с окнами и графикой. Если вы собираетесь работать с аппаратными средствами без ОС, обходя API, сохраните состояние и верните ОС в первоначальное состояние в конце программы.
  • Доступ к глобальным переменным и программирование аппаратных средств: адреса глобальных переменных могут изменится, используйте документированные функции API или прекратите работу вашей программы, если версия ОС. которую вы определили, не соответствует той, в которой вы тестировали ее. Кроме того, будущие модели могут работать на ином ЦП.
  • Не переполняйте стек: создавая множество локальных переменных или одну, но большую, вы можете столкнуться с весьма сложной для отладки ошибкой, связанными с повреждением динамической памяти. Максимальная емкость стека — 2 Кбайт, как можно реже используйте переменные, которые заносятся в стек.
  • Интегрированные программы могут измениться: формат и размер «предпочтений» (и данных) и проч. Если вы используете знание о структуре данных стандартных программ, то рекомендуем блокировать работу программы при запуске в среде некорректной версии PalmOS.

Мы объединили преимущества Palm Simulator и CoPilot, разработав просто убойный инструмент для PalmOS-разработчиков, который не зависит от той платформы, на которой основаны их ПК.

Перепечатано из журнала Handheld Systems, май/июнь 1998
© 1998 Creative Digital Publishing Inc. Все права сохраняются.

РекламаRambler's

Allbest.ru

RB2 Network

RB2 Network
--

Просим при воспроизведении материалов этого сайта, делать ссылку на Зоопарк ручных компьютеров
Copyright © 1999-2000 Зоопарк ручных компьютеров