missdeer之程序的野望

哪怕出没于深沉的夜里,也要在自己的黑眸上映上无数朵美丽的桃花,如此方能不自伤,不自悲……

Entries for the ‘Emulator’ Category

游戏驱动(译)

  终于把这篇也整了,原文在这里,同样,还是有些没懂起的。
游戏驱动by Aaron Giles  可能关于MAME怎样工作的最常见的问题是:我应该怎么编写驱动?为了理解怎么编写驱动,你需要理解驱动是怎样连接到MAME中去的,以及众多的驱动相关的难点是什么。这篇文章向你提供在从最顶层的,驱动需要些什么的概况。以后的文章会讲述这些以外的细节。  开始,你得先看一下driver.c里的代码。实际上这个模块做的,只是生成了MAME支持的所有驱动的指针的列表。问题是每个驱动,需要选声明一下:extern struct GameDriver driver_puckman;extern struct GameDriver driver_puckmana; …  然后所有的驱动被声明了,我们要这样声明一个驱动的列表:const struct GameDriver *drivers[] = {&driver_puckman, &driver_puckmana, …, 0 };  使用C语言来做这事很烦人,因为它需要我们为每个驱动添加2个东西到driver.c,在顶部的一个声明和最底部的列表中的表项。实际上,在用一些好用的预处理魔法去简化这些事情前,我们已经这样做了好多年。如果你是一个C专家,你可能会指出来,也可能不会,没关系。重要的是,要把驱动添加到主列表中,你只要用特定的DRIVER宏这样声明一次就行了:DRIVER( puckman ) DRIVER( puckmana ) …  这样把表项插入到列表中,和其它表项一起,你的驱动就已经可以被MAME通过主驱动列表正式引用了!当然,这也是说你实际上需要一个GameDriver对象,用在MAME其它的地方,用相同的名字,用于成功地连接到应用中。那应该怎样正确地定义一个游戏驱动?  基本的GameDriver结构非常简单。它提供一个很基础的信息列表,关于这个驱动的,记录了绝大部分的描述性数据(游戏名,制造商,年份等)和指向其它用于描述游戏使用的硬件的结构的一些指针。比起你自己定义和使用GameDriver结构,你应该简单地使用宏来填充所有的细节内容。如果你看一下驱动目录中任何一个文件的底部,你会看到大量的这种宏用于描述驱动:GAME( 1980, puckman, 0, pacman, pacman, 0, ROT90, “Namco”, “PuckMan (Japan set 1)”, GAME_SUPPORTS_SAVE )GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, “Namco”, “PuckMan [...]

MAME的CPU调度(译)

  有好些地方看不懂,晕,凑合吧,以后如果明白了,再来改吧!更让我郁闷的是,自己的英语水平真是不敢恭维啊!其实Aaron Giles已经写得很简单了,几乎没有长句!唉!
MAME的CPU调度by Aaron Giles
第1部分  MAME中多CPU游戏是以循环的方式进行调度的。循环执行的顺便在机器驱动中根据CPU的顺便严格定义。没有办法改变这个顺序,然而,你可以通过挂起CPU或调整调度粒度来影响调度。那部分内容会在第2部分讨论。  调度器依靠定时器系统工作,定时器系统知道什么时候下一个定时器被调度。所有调度都是在定时器被触发时发生。类似的,当CPU运行时,定时器从来不被触发。这点很重要。  调度器查询定时器系统,找出什么时候下一个定时器被触发。然后它轮循每个CPU,计算CPU需要多少个时钟周期才会到达那个时刻,再运行CPU那么多个时钟周期。当CPU执行完后,CPU核心会返回实际上执行了多少个时钟周期。这些信息被累计,并转换为“本地CPU时间”,这是为了统计是否多用了CPU核心时间,或者过早退出CPU核心。  例如我们说CPU #0是14MHz,CPU #1是2MHz。我们还说我们从0时刻(对两个CPU来说都是本地时间为0)开始,有个定时器在150ms后触发(time=0.000150)。  循环逻辑会启动CPU #0,计算它需要多少时钟周期才能达到0.000150时刻。我们从0时刻开始,我们至少需要运行150µs。0.000150 × 14,000,000 = 2100时钟周期。它就调用CPU运行函数执行2100个时钟周期;当函数返回,它知道多少个时钟周期实际被执行了。我们假设它返回它实际运行了2112个时钟周期。(CPU核心一般会过量使用,因为很多指令会占用多于1个时钟周期。)2112个时钟周期转换为CPU #0的本地CPU时间是0.000150857(2112/14,000,000)。  现在轮到CPU #1运行。0.000150 × 2,000,000 = 300时钟周期。所以我们调用执行(300个时钟周期)并返回300个时钟周期。CPU #1的本地时间现在是0.000150。  现在,两个CPU都已经执行过了,它们的本地时间都大于或等于目标时间0.000150。所以调度器调用定时器系统让它处理定时器。完成后,它再次查询什么时候下一个定时器会触发。假设它很精确地在150µs后触发,time=0.000300。  再回到调度器,我们再次启动循环。CPU #0需要运行(0.000300 – 0.000150857) × 14,000,000 = 2088时钟周期到达本地时间0.300。注意我们统计了上次执行过的所有时钟周期数。所以我们调用执行(2088),然后返回,是2091。这时本地时间就是0.000150857 + 0.000149357 = 0.000300214。  轮到CPU #1。还是(0.000300 – 0.000150) × 2,000,000 = 300时钟周期。调用执行(300),返回302个时钟周期。记录CPU #1的本地时间是0.000150 + 0.000151 = 0.000301.  再一次,两个CPU都已执行了,它们的本地时间都大于或等于0.000300,所以我们联系定时器系统让它运行它的定时器。这个过程贯穿了整个系统的执行。  这里有些事要注意。第一次循环后,CPU #0的本地时间稍微比CPU #1的本地时间提前一点。经过第二次循环后,刚好相反。因此你不能保证在任何时候一个CPU会比其它的是要提前点还是延后点。  而且,需要记住的是每个CPU有它各自的本地时间。定时器系统也会有一个“全局时间”。全局时间一般是所有CPU本地时间中最小的那个。当调用定时器系统时使用哪个时间,取决于当前哪个CPU上下文是活动状态的。如果某CPU上下文是活动的(一般只有当CPU在运行时才是;例如在读/写回调函数),然后所有定时器操作都把这个“当前时间”作为CPU本地时间,统计当前时间片内CPU执行的所有时钟周期数。如果某CPU上下文不是活动状态(所有其它时刻;例如,在定时器回调中),这时“当前时间”就是全局时间。  全局时间在每个时间片结束,调度器调用定时器系统处理定时器前被更新,而且全局时间可用来分派定时器。  有很多内容这篇文章没提到的,包括提前退出,挂起CPU,自旋(spinning)和让步(yielding)。下一篇文章会讲到所有相关的这些细节如果适用到定时系统中。
第2部分  文章的第1部分讲到了MAME在运行多CPU时使用的循环调度算法的基础知识。可以总结为:  1. 决定什么时候下一个定时器被触发  2. 轮循每个CPU:    1) 计算这个时间和CPU本地时间的差值    2) [...]

MAME的资源管理(译)

  打算研究一下MAME,所以先研究一下Aaron Giles写的几篇文章,英文的看着不是很舒服,所以草草地翻译了一下。这次放出第一篇《Resource Management in MAME》,原文在这里。
MAME的资源管理by Aaron Giles
  如果你在写一个驱动,或者给驱动添加状态保存功能,这时了解MAME是如何管理资源的就很重要。  以前,MAME根本不跟踪资源的使用情况的:如果你调用了malloc(),你就需要free();如果你调用了timer_alloc(),就需要调用timer_free()等等。因为标准命令行版本的MAME一次只运行一个游戏,所以当你退出时即使留下一堆已分配的资源也没有什么实际影响。当然,那些移植版本如MacMAME―允许你在不退出的情况下停止一个游戏开始另一个游戏―会因为内存不足或其它资源分配后却不回收等问题引起程序崩溃。  在内核中使用优秀的资源管理有很明显的理由:它只使用不多的代码,并且不会很快地改变。而,在几百个驱动中用好资源管理,简直是个噩梦。驱动通过提供回调函数hook住主系统。下面列表中是一些通用驱动回调函数: DRIVER_INIT VIDEO_START/VIDEO_STOP MACHINE_INIT/MACHINE_STOP  大多数的驱动在MACHINE_INIT和VIDEO_START中分配资源,并且理论上假设它们也会在MACHINE_STOP和VIDEO_STOP回调函数中释放这些资源。实际上,这并不是一定的,这让人很头疼。而且,一些系统会在DRIVER_INIT中分配内存,但却没有比较合适的地方可以释放这些内存。我假设我们应该添加个DRIVER_STOP回调函数,但是最后却是另外一个不同的方案被采纳了。  如果你从逻辑上考虑一下,99%的情况下,你会希望在MACHINE_STOP释放在MACHINE_INIT中分配的资源。类似的,你想要在VIDEO_STOP中释放所有在VIDEO_START中分配的资源。所以,相比要求每个驱动去编写代码完成释放资源,为什么不由内核来简单地跟踪资源分配,并在适当的时候自动释放呢?这样确实可以工作得很好。  如果你看一下这些回调函数的调用顺序,它应该像这样(用伪码表示):init{DRIVER_INITVIDEO_STARTreset:{MACHINE_INITrun-the-game-until-exit-or-resetMACHINE_STOP}if we-exited-due-to-reset thenloop back to reset:VIDEO_STOP}exit
  你应该会注意到我在上面的伪码中使用了一组花括号。这会给你一点提示,应该怎样自动跟踪资源的分配-这很像在C/C++中使用局部变量。当离开这个作用域,所有被跟踪的资源在该作用域中被分配的应该自动回收。当你运行到MACHINE_STOP和VIDEO_STOP间的闭花括号,内核会释放所有从前面的开花括号以来分配的资源,包括所有在MACHINE_INIT中分配的东西,同样也会回收内核在那段时间分配的资源。类似的,当你运行到第二个闭花括号时,所有被跟踪的在DRIVER_INIT和VIDEO_START中分配的资源都会被释放。请注意我说的被跟踪的资源。只有有些资源是被跟踪的,另外一些需要显式释放。例如定时器,总是被跟踪的。任何时候你在MACHINE_INIT中分配了一个定时器,它会在你退出内部作用域时被释放。实际上,通过这种方式来跟踪,定时器是不需要显式调用timer_free(),你需要依赖于资源跟踪来处理。  最明显的不被自动跟踪的资源是内存。原因是当调用malloc()后,当离开该作用域时还是有可能需要保留该内存不被释放。你可通过使用auto_malloc()替换malloc(),手动使MAME跟踪内存。auto_malloc()像malloc()那样工作除了它会在离开当前作用域时自动释放。这里你必须小心点,因为如果你在MACHINE_INIT中使用了auto_malloc()并保存了该指针在一个全局变量中,当游戏重启时MACHINE_INIT会被重新调用,那块内存会被释放。你的全局变量仍然是相同的指针值,但内存已经不存在了。正确的作法是不要使用保存在全局指针中的值,而只要简单地每次在MACHINE_INIT回调函数中都调用auto_malloc()就可以了。  另一种通用资源需要被手动跟踪的是位图。有个叫auto_bitmap_alloc()的函数可以创建位图,并在离开当前作用域时回收。  最后一种被跟踪的“资源”是新近才加的:保存状态注册。根据最近的系统的修改,你可以只注册数据到需要被保存到外部作用域(DRIVER_INIT/VIDEO_START)。这是因为如果你点击F3重启游戏,MACHINE_INIT会被调用多次,而保存状态系统不能处理重复注册(我知道,这并不难,但有其它一些更复杂的东西)。为了管理这些,当你退出一个作用域,所有你在该作用域内注册的保存的状态数据会丢失,意味着你需要再注册一次。像大多数情况下,你在MACHINE_INIT回调函数中注册,当游戏重启后你会被再一次调用MACHINE_INIT,这并不是说你需要做什么特别的,只要简单地像什么事都没发生过一样注册一遍,它就能工作得很好。  在之后的文章里,我会讲到关于保存状态的系统的更多细节,所以如果上面这些听起来有点迷糊,很快就能得到更多的认识。

  
Get Adobe Flash playerPlugin by wpburn.com wordpress themes