Сообщения без ответов | Активные темы Текущее время: 28 апр 2024, 13:51



Ответить на тему  [ Сообщений: 2 ] 
 [АРХИВ] Эмуляция памяти 
Автор Сообщение
Сообщение 15 авг 2007, 16:00
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Автор: Wind - 27 Dec 2004.

Что подразумевается под эмуляцией памяти – это всего лишь трансляция адресов.
Эмуляцию памяти приходится вести параллельно эмуляции процессора.

Рассмотрим на примере:
Предположим, во время эмуляции процессора мы натыкаемся на инструкцию:
MOVLS R0, @R1 – эта команда произведет запись R0 по адресу,
который содержится в R1.

т.е. операция будет иметь вид на С.

memWrite32(reg[sh4.code>>8 & 0xf], reg[sh4.code>>4 & 0xf ]);

a реализация этой ф-ии такова.

void __stdcall memWrite32(u32 addr, u32 value) { (*memW32[addr >> 16])( addr, value); }

Разберем эту ф-ию:

У многих мог сразу возникнуть вопрос - почему __stdcall.
Во время написания динамического рекомпилятора нам не придется заботится о выравнивании стека после вызова ф-ий работы памяти и не сохранять регистры EBX, EBP, ESI, EDI. Напомню: __stdcall ф-ия сама удаляет из стека переданные параметры и не изменяет вышеперечисленные регистры.

Предположим, что addr = 0x8c000000 этот адрес у нас соответствует – адресу в памяти, т.е. при получении на входе этого адреса мы должны произвести запись в предварительно выделенный кусок памяти (16мб). Использование switch – case невозможно, представьте, если сколько понадобится case на 16мб, можно ввести проверки на попадание адреса в определенные пределы (в данном случае в предел 0x8c000000 - предел 0x8cffffff), но таких проверок тоже получится немало, а это скажется на скорости.

Поэтому мы просто выделим кусок памяти (в данном случае его размер 4мб), на него будет указывать переменная memW32. Размер каждой ячейки элемента равен 4байта (соответствует размеру адреса для х86-32). Все элементы memW32 предварительно проинициализируем адресами не обходимых ф-ий.

Для инициализации используем цикл:

for (i=0; i<0x0100; i++) memW32[i + 0x8C00] = WriteRam32;

Просто в элемент memW32 с номером 0x8C00+i запишем адрес ф-ии WriteRam32.
WriteRam32 функция произведет запись.

Почему номер элемента не равен 0x8C000000, а равен 0x8C00?
Давайте представим если бы адрес на входе равнялся 0xffffffff то получилось бы размер необходимого куска памяти только для трансляции адресов для ф-ий записи 32бит (для каждого способа свой кусок) был бы равен 4гб, а это предел 32 битных систем, да и ни одна современная операционная система не позволит выделить более 2гб памяти.
Поэтому приходится идти на небольшую хитрость мы предварительно сдвигаем на 16 вправо адрес и уже в этот элемент записываем адрес необходимой функции. Сдвиг может быть и другим все зависит от эмулируемой системы.
Примечание:
Современные процессоры содержат ММU, так вот если он используется необходимо прежде получить реальный адрес, в который требуется записать/считать данные, а уже потом его транслировать.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Последний раз редактировалось Keyman 15 авг 2007, 23:00, всего редактировалось 1 раз.



Сообщение 15 авг 2007, 16:05
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
simplexe
а для пс2 не в курсе как реализовывать? вот если (я в этом не шарю) память соньки разместить в оперативке (если так вообще можна), то производительность увеличется? вот в исходниках я посмотрел адрессацию:
RAM
---
0x00100000-0x01ffffff this is the physical address for the ram.its cached there
0x20100000-0x21ffffff uncached
0x30100000-0x31ffffff uncached & acceleretade
0xa0000000-0xa1ffffff MIRROR might...???
0x80000000-0x81ffffff MIRROR might... ????

scratch pad
----------
0x70000000-0x70003fff scratch pad

BIOS
----
0x1FC00000 - 0x1FFFFFFF un-cached
0x9FC00000 - 0x9FFFFFFF cached
0xBFC00000 - 0xBFFFFFFF un-cached
вот вроде здесь все подписано кто-нить объясните....
и еще вопрос эмуляция привода более менее понятна (пс2 опять же) а вот загрузка с *.исо и *.бин образов...они что переписали чейто драйвер под себя?

Organic
При эмуляции памяти её следует рассматривать как аппаратное устройство, наряду с эмуляцией другого железа. Конструкции наподобии memWrite32 выглядят "ненатурально", настоящее железо так не делает. Есть центральный процессор и шина адреса. По шине адреса передается физический адрес для чтения или записи и размер передаваемых данных. А контроллер памяти уже кумекает к чему относится данный адрес - к RAM, ROM или аппаратным регистрам.

Вот например как это происходит в Gamecube. Есть центральный процессор Gekko и чип со всем остальным "железом" - Flipper. Они связаны между собой шиной. Если представить Gekko как "черный ящик", то он может сделать следующие запросы по шине: читать или записать 8, 16, 32 или 64-разрядные данные; записать пакет размером 32 байта. В свою очередь Flipper передаёт по шине требуемые данные по указанному физическому адресу. На Си можно реализовать это так :
Код:

// передать данные CPU <- Flipper
void FlipperRead(int byteAmount, unsigned long PhysAddr, void *dataPtr)
{
if( RANGE(PhysAddr, 0, RAMSIZE) ) ... доступ к оперативке
else if( RANGE(PhysAddr, HW_BASE, HW_TOP) ) ... регистры аппаратуры
else if( RANGE(PhysAddr, 0xfff00000, 0xffffffff) ... ROM

else ... прерывание контроллера памяти (неверный физический адрес)
}

// передать данные CPU -> Flipper
void FlipperWrite(int byteAmount, unsigned long PhysAddr, void *dataPtr)
{
... аналогично, но только запись
}



Естественно кучу if..else if..else if условий можно заменить look-up таблицей, как предложил Wind.

Кроме того, эмуляция памяти и эмуляция трансляции адресов совсем разные вещи. Память это прерогатива контроллера памяти, а трансляция адреса делается центральным процессором. Поэтому не стоит смешивать два этих понятия. И эмулировать их тоже нужно отдельно.

Также Wind не упомянул про разный порядок байт на различных платформах. Если big-endian машина эмулируется на little-endian (и наоборот), то порядок байт в данных необходимо поменять перед использованием. Если чтение и запись данных размером более байта основана на функциях побайтового чтения/записи, то можно применить прикольный трюк, именуемый "munging" :
Код:

read_8 - это функция чтения байта из памяти.

unsigned long read_32(unsigned long addr)
{
unsigned long retval;
unsigned char *ptr = (unsigned char *)&retval;

ptr[0] = read_8(addr++ ^ 3);
ptr[1] = read_8(addr++ ^ 3);
ptr[2] = read_8(addr++ ^ 3);
ptr[3] = read_8(addr++ ^ 3);

return retval;
}



В данном случае перевертываются не сами данные, а адрес для доступа к ним (хитрым образом используется операция XOR). Хотя иногда бывает проще непосредственно прочитать всё слово а потом перевернуть в нем порядок байт (у x86 есть замечательная инструкция bswap).

Порядок байт необходимо учитывать в портирумых эмуляторах. Если эмулятор эмулирует только PSX и только на платформе x86, то порядком байт можно пренебречь (обе системы - little-endian).
0 error(s), 0 warning(s)

Wind (SMS)
Фанат

Тут с: 09.04.2004
Сообщения: 112 Добавлено: 17 Июн 2005 17:39 Заголовок сообщения:

Ну если уж вести речь о чтение/записи для машин имеющих разный порядок байт, то нужно использовать следующий подход:

void memWrite32(u32 mem, u32 value)
{
((u32 *) (memory - mem - 4))[0] = value;
}

void memWrite16(u32 mem, u16 value)
{
((u16 *) (memory - mem - 2))[0] = value;
}

void memWrite8(u32 mem, u8 value)
{
((u8 *) (memory - mem - 1))[0] = value;
}

Где:
memory указатель на конец соответсвующего куска выделеной памяти т.е. если запись производится в оперативную память то memory указывает на ее конец, если в видеопамять то memory указывает на ее конец и т.д.

mem смещение внутри блока

value даные, которые необходимо записать, соответсвующего размера.

Чтение даных произврдится анологичнвм образом.[/quote]

RomikB
Поясни пожалуйста последний пост.

Wind
Сложно это на словах объяснить, это надо самому увидеть, но все же попробую объяснить. Еще раз этот даный подход очень удобен если системы имеют разный порядок байт в слове. В результате трансляции адреса мы получаем указатель на конец блока отвечающего за соотвесвующий участок памяти т.е. если адрес соотвествует оперативной памяти то указатель будет равен адрес по которому выделен блок плюс размер опретивной памяти. В данном случае значение указателя находится в memory(это глобальная переменая), в mem смещение внутри блока. В результате если производится запись в начало то у нас запись произойдет в конец и наоборот если в конец то у нач в начало. В итоге будет перевернута вся память, а не один конкретный элемент.
Зачем все это нужно:
Лично мне неизвестно, как заставить компилятор использовать инструкции bswap и xchg в итоге запись/чиение превращается в кучу сдвигов и "или", а прииспользовании подхода описаного выше для записи достаточно двух команд.

RomikB
Хм. интересно, подумаю как нибудь над этим способом.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Показать сообщения за:  Поле сортировки  
Ответить на тему   [ Сообщений: 2 ] 

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 48


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by STSoftware for PTF (mod by Zeru-j).
Русская поддержка phpBB