成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何編寫計算機模擬器

開發 項目管理
一個典型的模擬器,應該重現原有的系統設計,并實做每個子系統的功能,在不同的模組。這樣做,首先除錯會比較容易,因為問題會被獨立在各自的模組裡。其次模組化,可以讓你在別的模擬器,重復使用你的模組。

早期在 PC 上寫模擬器的牛人,Marat Fayzullin 是其中之一。1997 年,他就已經開發出 fMSX 模擬器,并且以這篇文章《How to write a computer emulator?》分享他的知識。中文翻譯的網頁已經不存在了,可惜。

下面是閱讀后的整理:

綱要:

●什麼可以被模擬?

●什麼是 emulation,它跟 simulation 有什麼不同?

●模擬有專利的硬件,是合法的嗎?

●什麼是直譯式的模擬器,跟編譯式的模擬器有何不同?

●我想寫一個模擬器,我該從何開始?

●我該用哪一種程序語言?

●我從哪裡可以得到想模擬的硬體的資訊?

實現:

●如何模擬一個 CPU?

●如何存取被模擬的內存?

●周期性的運作有哪些?

程序技巧:

●如何優化 C 程序碼?

●什麼是高低字節順序?

●如何讓程序具可移植性?

●為何我要模組化我的程序?

什麼可以被模擬?

基本上,任何東西有微處理器在裡面,就可模擬。當然,只有那些可以跑程序裝置,我們才有興趣模擬。包括:電腦、計算機、游樂器、大型電動、其他……

必須特別註明,你可以模擬任何電腦系統,即是事非常復雜的系統(譬如 Amiga 電腦),但是執行效率可能很低。

什麼是 Emulation,它跟 Simulation 有什麼不同?

Emulation 模擬裝置內部的硬體,Simulation 是模擬裝置內部的功能。舉例來說,一個程序模擬小精靈大型電動的硬體,然后執行小精靈的 ROM,就是個 emulator。一個小精靈的 PC 游戲,就是個 simulator。

模擬有專利的硬體,是合法的嗎?

這是個灰色地帶,只要你不是透過不合法的管道,拿到硬體的資訊,就應該不違法。但是很清楚知道,跟模擬器一起散佈有著作權的系統 ROM(例如 BIOS),是違法的。

什麼是直譯式的模擬器,跟編譯式的模擬器有何不同?

模擬器有三種設計的方式,這些設計也可以混用,來達到最好的效果。

直譯式

模擬器一個位元又一個位元的,從內存讀取代碼,然后解碼,執行對應的暫存器、內存、輸出入的命令。通用的演算法如下:

  1. while(CPUIsRunning)  
  2. {  
  3.   Fetch OpCode  
  4.   Interpret OpCode  

這種設計的好處是,容易除錯,容易移植,容易同步(你只需要計算過了多少 CPU 周期,然后讓你模擬的其他部份,跟 CPU 同步)。

這種設計明顯的弱點,就是執行效率很差。執行直譯會花很多 CPU 時間,你會需要很快的電腦,才能有不錯的執行速度。

靜態編譯式

這種技術,就是把一支你要模擬的系統的代碼,編譯成你的電腦的的匯編語言。編譯的結果,通常是一支你的電腦的普通執行檔,不需要額外的工具就可以執行。靜態編譯,聽起來很美好,但通常不可行。例如,你就無法靜態編譯會自我修改的代碼,因為這種代碼只有執行時,才會知道內容是什麼。為瞭解決上述的問題,或許需要混用直譯器,或是動態編譯編譯器。

動態編譯式

動態編譯基本上跟靜態編譯一樣,但動態編譯發生在程序執行時。動態編譯是在執行到 CALL 或 JUMP 時才編譯,取代一開始就編譯一整個程序。為了增加執行效率,這種技術常常結合靜態編譯。你可以讀,動態編譯式麥金塔模擬器的作者 Ardi,的這篇動態編譯白皮書學到更多

我想寫一個模擬器,我該從何開始?

想要寫一個模擬器,你必須懂程序設計,以及數位電子。如果懂得匯編語言,會更好。

1. 選一種程序語言

2. 找到所有被模擬硬體的所有資訊

3. 寫 CPU 模擬,或是選用一個現成的 CPU 模擬程序

4. 寫個粗略的其他周邊硬體的模擬,至少要一部分

5. 在這個時候,寫個內建除錯器,讓你可以暫停模擬,檢查程序執行的結果。你也會需要一個被模擬 CPU 的匯編語言反組譯器。如果找不到現成的,就自己寫一個。

6. 試著用你的模擬器執行程序

7. 用除錯程序跟反組譯器,看看程序到底在干麼,然后根據此修改你的模擬器

我該用哪一種程序語言?

最常被用到是 C 跟匯編語言,各有優缺點。

匯編

+ 通用,可以產生速度快的程序碼
+ 可以直接使用暫存器,來映射被模擬的暫存器
+ 很多匯編語言指令,可以對應到被模擬的匯編語言指令
- 程序是不可移植的,換句話說,你的模擬器,不能在別種 CPU 上跑
- 很難除錯跟維護

C 語言

+ 可移植性,所以可以在不同的作業系統上跑
+ 相對容易除錯跟維護
+ 對硬體的不同假設,可以很快的測試
- 通常 C 語言的程序比匯編語言的程序慢

要寫模擬器,對所選擇的語言,瞭解得很透徹,是絕對必要的。因為模擬器的程序很復雜,你要優化你的模擬器,讓它跑得越快越好。電腦模擬器程序,絕對不是你越來學習程序語言的專案。

#p#

我從哪裡可以得到想模擬的硬體的資訊?

下列地方,你會想去看一看:

●網路新聞群組

comp.emulators.misc

這個新聞群組,討論模擬器一般的問題。許多模擬器作者會訂閱,雖然裡面雜音很多。如果要貼問題到這個新聞群組,記得先看 c.e.m FAQ 常見問題。

comp.emulators.game-consoles

跟 comp.emulators.misc 一樣,不過這個新聞群組,專攻電視游樂器的模擬器。如果要貼問題到這個新聞群組,記得先看 c.e.m FAQ 常見問題。

comp.sys./emulated-system/

comp.sys.* 新聞群組階層,專攻特定的電腦系統。你閱讀這些新聞群組,可以得到有用的技術資料。典型的例子:

  1. com.sys.msx MSX / MSX2 / MSX2+ / TurboR 電腦  
  2. comp.sys.sinclair Sinclair ZX80/ZX81/ZXSpectrum/QL  
  3. comp.sys.apple2 Apple ][ 

如果要發問題到這個新聞群組,記得先看 FAQ

alt.folklore.computers

rec.games.video.classic

FTP

WWW

如何模擬一個 CPU?

首先,如果你需要模擬一個標準的 Z80 或 6502 CPU,你可以使用 Marat Fayzullin 所寫的 CPU 模擬器 當然有些限制。

對那些想要自己寫 CPU 模擬核心,或是對其中的運作原理感性趣的人,我提供一個用 C 寫的范例架構如下,在真正的實做,你或許會考慮略過其中部份,或添加新的部份。

  1. Counter=InterruptPeriod;  
  2. PC=InitialPC;  
  3.    
  4. for(;;)  
  5. {  
  6.   OpCode=Memory[PC++];  
  7.   Counter-=Cycles[OpCode];  
  8.    
  9.   switch(OpCode)  
  10.   {  
  11.     case OpCode1:  
  12.     case OpCode2:  
  13.     ...  
  14.   }  
  15.    
  16.   if(Counter<=0)  
  17.   {  
  18.     /* Check for interrupts and do other */ 
  19.     /* cyclic tasks here                 */ 
  20.     ...  
  21.     Counter+=InterruptPeriod;  
  22.     if(ExitRequired) break;  
  23.   }  

首先我們指定 CPU 周期記數器 (Counter),以及指令位址記數器 (PC)

  1. Counter=InterruptPeriod;  
  2. PC=InitialPC; 

Counter 紀錄了到下一次系統中斷發生,還剩多少個 CPU 周期。注意當 Counter 過其實,系統中斷不必然發生。你可以利用他來處理其事情:像是時鐘同步,更新螢幕的掃瞄線等。等等,我們會討論這些。PC 則紀錄了CPU 會從那個內存位址,讀取下次的執行的指令。

在我們給這些設定初始值之后,然后開始進入主循環:

  1. for(;;)  

主循環也可以寫成這樣:

  1. while(CPUIsRunning)  

CPUIsRunning 是個布林值,這樣寫有個好處,你可以在任何時候,設 CPUIsRunning=0,來終止主循環。然而在每個循環檢查這個變數,會花不少的 CPU,而我們應該盡量減少花費 CPU。同時,不要寫成下面這樣子:

  1. while(1)  

因為這樣寫,編譯器產生代碼,去檢查 1 為 “真” 或 “假”,你不會希望在主循環的每個循環,都去執行這多餘的動作。

現在我們在主循環內,第一件事,就是去讀下一個執行碼,然后修改程序位址記數器。

  1. OpCode=Memory[PC++]; 

注意,這是最簡易的方式,來模擬讀取內存,但并非永遠可行。更通用的方式,來存取內存,稍后會提到。

在提取操作碼后,會從 CPU 周期計數器,扣掉這個指令所需的周期數。

  1. Counter-=Cycles[OpCode]; 

Cycles[] 表內放的是每個操作碼,所需要的周期數。要特別注意,有些指令(例如條件式跳躍,或是呼叫副程序),需要的周期數,是跟操作后面緊接的參數而變動。這個可以在執行指令碼時調整。

現在該是解譯操作碼,然后跟著執行的時候了:

  1. switch(OpCode)  
  2.  { 

有一個錯誤的觀念,認為 switch 語句是沒有效率的,因為會被編譯成 if () …… else if () …….. 語句。這只有在 case 數量很少的 switch 語句,才會被這樣編譯。當有 100 到 200 個 case 的時候,switch 語句通常會被翻譯成 jump 表格,jump 表格,其實蠻有效率的。

有其他兩種替代方案,可以用來解譯操作碼。第一種方法,是建一個函式表,然后呼叫對應的函式。這種方式,比用 switch() 沒效率,因為呼叫函式,有額外的開銷。第二種方式,是建一個位址的表格,然后使用 goto 語句。這種方式,稍比用 switch() 有效率一點,但這種方式,只適合用在編譯器支援未預定位址表格。其他的編譯器,不會允許你這樣定義表格。

在成功解譯并執行一個操作碼后,這時候該去檢查有沒有任何系統中斷發生。這時候,你也可以執行任何需要跟系統時鐘同步的工作。

  1. if(Counter<=0)  
  2.  {  
  3.  /* Check for interrupts and do other hardware emulation here */ 
  4.  ...  
  5.  Counter+=InterruptPeriod;  
  6.  if(ExitRequired) break;  
  7.  } 

有關周期性的工作,后面會提到。

注意,我們并非直接指定 Counter=InterruptPeriod,而是執行 Counter+=InterruptPeriod,這樣會讓周期的計算更精確,因為有時候,Counter 會變成負數。

同時,注意這

  1. if(ExitRequired) break

這個語句如果在每個循環都執行,成本太高,所以只有在中斷發生時才檢查。這樣就可以在 ExitRequired=1 時,停止模擬,但又不會花太大的成本。

#p#

如何存取被模擬的內存?

模擬內存存取最簡單的方式,就是把它當成一個攤平的位元組或字元組陣列。如此,存取內存,就是一件微不足道的事情:

  1. Data=Memory[Address1]; /* Read from Address1 */ 
  2. Memory[Address2]=Data; /* Write to Address2 */ 

這種簡易的作法,并非永遠可行,原因如下:

●分頁式的內存 ?內存空間,可能被切成小塊,變成可以切換的頁,就是所謂的 banks。例如常見的,小內存位址空間( 64 KB),所使用的擴充內存。

●映射的內存 ?這塊內存空間,可以用數個不同的位址來存取。例如你寫資料到位址 $4000,然后你在位址$6000,及位址 $8000,你也可以讀到。

ROM 的讀取保護 ?有些存到卡夾的軟體(例如 MSX 的游戲),就算你寫到 ROM,回傳成功,事實上 ROM 上的資料也不會改變。這麼做,是為了做軟體保護。為了讓這樣的軟體,可以在你的模擬器運行,你需要把 ROM 設成唯讀。

內存映射到 I/O ?系統可能有 I/O 裝置,映射到內存位址。存取這樣的內存位址,會產生特殊效果,所以必須被追蹤。

要成功處理上述問題,我們引進幾個函式:

  1. Data=ReadMemory(Address1); /* Read from Address1 */ 
  2. WriteMemory(Address2,Data); /* Write to Address2 */ 

所有特殊的處理,包括內存分頁,內存映射,I/O 的處理,等等,都在函式內處理。

ReadMemory() 跟 WriteMemory() 對模擬器造成很大的 CPU 負擔,因為它們執行的非常頻繁。因此這些函式必須寫得越有效率越好。這裡有一個存取分頁式內存的例子:

  1. static inline byte ReadMemory(register word Address)  
  2.  {  
  3.  return(MemoryPage[Address>>13][Address&0x1FFF]);  
  4.  }  
  5. static inline void WriteMemory(register word Address,register byte Value)  
  6.  {  
  7.  MemoryPage[Address>>13][Address&0x1FFF]=Value;  
  8.  } 

注意那個 inline 關鍵字,它會指示編譯器,直接把這些函式碼,直接插入程序中,以取代函式呼叫。如果你的編譯器,不支援 inline 或是 _inline,試著改把這些函式,宣告成 static,有些編譯器(例如 Watcom C)優化時,會把短的函式,變成 inline 函式。

同時要記住,通常 ReadMemory() 的呼叫次數,是 WriteMemory() 的好幾倍。所以盡量把程序碼放到 WriteMemory(),讓 ReadMemory() 保持簡單。

關於內存映射的一個小註記:

之前說過,被映射的內存,寫入一個位址,可以在其他位址讀取。這個功能,可以實做在 ReadMemory(),但是通常我們不這樣做,因為 ReadMemory() 比 WriteMemory() 更頻繁被呼叫。更有效率的方式,是實做內存映射到 WriteMemory()函式。

周期性的運作有哪些?

周期性的運作,是被模擬的機器,固定一段時間,就會執行的工作,例如:

  • 屏幕更新
  • VBlank 跟 HBlank 系統中斷
  • 更新時鐘
  • 更新聲音參數
  • 更新鍵盤跟搖桿狀態
  • 其他

為了要模擬這樣的運作,你要替它們綁上固定的周期。例如 CPU 假設以 2.5 MHz,并且以 50 Hz 更新顯示(PAL 系統),所以 VBlank 系統中斷,就會每 5000 CPU 周期,發生一次。

2500000/50 = 50000 CPU cycles

現在,假設整個螢幕是(包含 VBlank)是 256 條掃瞄線,實際上只有 212 條顯示(44 條在 VBlank),我們得到一條掃瞄線 195 個 CPU 周期,更新一次。

50000/256 ~= 195 CPU cyles

然后,我們應該產生一個 VBlank 系統中斷,然后在 VBlank 期間不做任何事情。

(256-212)*50000/256 = 44*50000/256 ~= 8594 CPU cycles

小心計算每個周期性運作所需的 CPU 周期,然后使用他們的最大公約數,作為中斷檢查的周期,然后綁定給每個周期性運作。

如何優化 C 程序?

首先,很多執行效率的增進,只要選對編譯器的編譯選項,就有了。根據我的經驗,下面的編譯選項,可以給你的最佳的執行速度:

  1. Watcom C++ -oneatx -zp4 -5r -fp3  
  2. GNU C++ -O3 -fomit-frame-pointer  
  3. Borland C++ 

如果你發現,這三個編譯器,更好的優化參數,或是其他的編譯器的優化參數,請讓我知道。

●一些關於把循環攤平的筆記

雖然說,把循環攤平的這個優化選項,看起來是有用的。這個選項,會把短的循環,攤平成線性的語句。但我的經驗告訴我,開啟這個選項,執行效率并不會提升太大,反而在某些情況下,程序反而會出現異常。

優化 C 程序碼,比選擇編譯器選項,還難搞。跟執行你的程序的 CPU 有很大關系。有一些通用的規則,可以適用在所有 CPU。但別把它們當成真理。

●使用分析程序

用分析工具來執行你的程序(第一個就想到 GPROF),或許可以發現你從沒懷疑的神奇事情。你會發現毫不起眼的程序,頻繁的被執行,拖慢整個程序。優化這些程序碼,或是用匯編語言改寫,可以讓你的程序執行效率飛耀。

●不要用 C++

不要用任何非用 C++ 不可的架構。C++ 跟純 C 比起來,額外的開銷比較大。

●整數的類型

盡量用你的 CPU 支持的整數類型。舉例 int 對比 short 或 long,這會減少編譯器產生不同整數行別的轉換。

●寄存器配置

盡量減少在程序區塊配置太多變數,并且宣告他們為 register (大部分的編譯器已經會自動把變數變成 register)。特別是有很多通用暫存器的 CPU (PowerPC)這個優勢,就比有專屬暫存器(Intel 8086)來的強。

●攤平小循環Unroll small loops

如果你剛好有小循環會執行好幾次,把小循環攤平成線性執行的程序,是好主意。對照前面提到的編譯器自動攤平選項。

●算術移位 vs. 乘除法

盡量用算術移位,如果你乘或除一個數是 2 的 n 次方(J/128==J>>7),算術移位在大多數的 CPU 都比較快。另外用位元的 & 來求餘數(J%128==J&0x7F)。

#p#

什麼是高低字節順序?

所有的 CPU 通常都根據它們如何儲存資料到內存,分為幾個等級。除了非常特殊的種類,絕大多數的 CPU 分成兩個等級:

High-endian CPU 先存放 higher byte of word。例如,在這樣的 CPU 你存放 0×12345678,內存的內容會長像這樣:

  1. 0  1  2  3 
  2. +--+--+--+--+  
  3. |12|34|56|78|  
  4. +--+--+--+--+ 

Low-endian CPU 先存放 lower byte of word。上述了例子,內存內容會看起來完全不一樣。

  1. 0  1  2  3 
  2. +--+--+--+--+  
  3. |78|56|34|12|  
  4. +--+--+--+--+ 

典型 High-endian 的 CPU 有 6809,摩羅托拉 680×0 系列,PowerPC,及昇陽的 SPARC。Low-Endian 的 CPU 有 6502,及其后代 65816,及 zilog Z80,絕大多數 Intel CPU (8086,8088),DEC alpha 等。

當我們寫模擬器時,必須注意到,你模擬的 CPU,及執行你的模擬器的 CPU 的高低字節。舉例,我們想要模擬 low-endian 的 Z80,Z80 會先存 lower byte of word。如果你用的也是 low-endian 的 CPU,例如 intel 8006,那麼完全不需要特別處理。但是如果你用的是 high-endian 的 CPU,例如 PowerPC,這時候,要存放 16 bit 的 Z80 資料到內存,就會有問題。如果你的程序,必須兩種高低字節順序的 CPU 都能跑,問題就更復雜了。

一種解節高低字節順序的作法如下:

  1. typedef union  
  2.  {  
  3. short W; /* Word access */ 
  4. struct /* Byte access... */ 
  5.  {  
  6.  #ifdef LOW_ENDIAN  
  7.  byte l,h; /* ...in low-endian architecture */ 
  8.  #else 
  9.  byte h,l; /* ...in high-endian architecture */ 
  10.  #endif  
  11.  } B;  
  12. } word; 

可以看到,可以用 w 存取整個字節。而每次如果你需要存取個別 byte,用 B.l 及 B.w,來對應高低位元組。

如果你的程序,要在跨平臺編譯,在程序開始執行前,你也許會想要測試,是否編譯有設定正確的 endian 旗標。這裡有如何測試的程序碼。

  1. int *T;  
  2. T=(int *)"\01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";  
  3.  if(*T==1) printf("This machine is high-endian.\n");  
  4.  else printf("This machine is low-endian.\n"); 

如何讓程序具可移植性?

尚未撰寫。

為何我要模組化我的程序?

大多數的計算機系統,是由幾塊比較大的芯片所組成,各自執行一部分的系統功能。有 CPU、顯示控制器、聲音產生器、及其他。有些芯片,有自己的內存,及周邊的硬件。

一個典型的模擬器,應該重現原有的系統設計,并實做每個子系統的功能,在不同的模組。這樣做,首先除錯會比較容易,因為問題會被獨立在各自的模組裡。其次模組化,可以讓你在別的模擬器,重復使用你的模組。電腦的硬體,其實標準化成度很高,你可以在不同型號的電腦,發現相同的 CPU,相同的顯示控制器。為某個芯片,模組化寫一次模擬的程序,會比你每次都重寫來的容易。

譯文鏈接:Shun-Yuan Chou 的博客(譯者是臺灣人,原譯文為繁體,大部分術語已轉換成簡體,可能少數術語有遺漏)

本文來自:http://blog.jobbole.com/32268/

責任編輯:林師授 來源: 伯樂在線
相關推薦

2009-09-14 09:59:19

CCNA模擬器介紹CCNA

2011-11-17 13:28:35

云計算超級計算機

2009-09-04 16:05:08

2011-05-27 13:36:30

Android SDK

2011-07-26 09:32:08

iPhone 模擬器

2010-01-27 13:37:15

2009-09-17 09:11:59

CCNA實驗模擬器CCNA

2018-10-24 14:00:54

華為

2018-10-18 14:47:13

華為

2014-04-10 09:40:51

System 360計算機計算機系統

2021-01-27 14:18:17

量子計算傳統計算量子機器

2021-02-20 20:55:06

USB接口總線

2013-05-29 16:01:21

Android開發Android模擬器移動開發

2009-08-20 10:55:59

2009-08-07 10:53:24

JUNOS配置

2012-06-20 10:40:36

量子計算機

2011-05-26 11:06:37

Android模擬器

2015-09-30 11:22:19

計算機大數據

2023-10-11 18:30:39

Web系統程序

2010-01-27 18:27:07

Android模擬器應
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91视视频在线观看入口直接观看 | 色综网 | 国产精品亚洲精品久久 | 日韩欧美在线观看 | 极品的亚洲| 亚洲精品白浆高清久久久久久 | 成人欧美一区二区三区黑人孕妇 | 免费大黄视频 | 国产精品久久毛片av大全日韩 | 日本精品一区二区三区视频 | 欧美福利 | 91在线精品秘密一区二区 | 国内毛片毛片毛片毛片 | 日韩中文字幕在线视频观看 | 日操夜操 | 国内精品一区二区三区 | 久久久精品国产 | a黄视频| 国产亚洲日本精品 | 91香蕉视频在线观看 | 欧美二区在线 | 久热国产在线 | 成人av网站在线观看 | 国产激情一区二区三区 | 99精品久久久 | 91.com在线观看| 网址黄 | 国际精品鲁一鲁一区二区小说 | 视频一二区 | 国产伦精品一区二区三区视频金莲 | 亚洲精品久久嫩草网站秘色 | 久久久久一区 | www.亚洲免费 | 日韩www | 欧美精品v国产精品v日韩精品 | 国产精品国产精品国产专区不卡 | 九九久久久 | 国产精品 欧美精品 | 久久精品一区 | 国产乱码精品一区二区三区中文 | 亚洲综合在 |