1. 程式人生 > >實體地址和虛擬地址的區別

實體地址和虛擬地址的區別



(一)地址的概念


1)實體地址:CPU地址匯流排傳來的地址,由硬體電路控制其具體含義。實體地址中
很大一部分是留給記憶體條中的記憶體的,但也常被對映到其他儲存器上 (如視訊記憶體、
BIOS等)。在程式指令中的虛擬地址經過段對映和頁面對映後,就生成了實體地址,
這個實體地址被放到CPU的地址線上。
實體地址空間,一部分給物理RAM(記憶體)用,一部分給匯流排用,這是由硬體設計來決定的,
因此在32 bits地址線的x86處理器中,實體地址空間是2的32次方,即4GB,但物理RAM一般
不能上到4GB,因為還有一部分要給匯流排用(總線上還掛著別的 許多裝置)。在PC機中,
一般是把低端實體地址給RAM用,高階實體地址給匯流排用。


2)匯流排地址:匯流排的地址線或在地址週期上產生的訊號。外設使用的是匯流排地址,
CPU使用的是實體地址。
實體地址與匯流排地址之間的關係由系統的設計決定的。在x86平臺上,實體地址就是匯流排地址,
這是因為它們共享相同的地址空間——這句話有點難理解,詳見下 面的“獨立編址”。在其他平臺上,
可能需要轉換/對映。比如:CPU需要訪問實體地址是0xfa000的單元,那麼在x86平臺上,
會產生一個PCI匯流排 上對0xfa000地址的訪問。因為實體地址和匯流排地址相同,所以憑眼睛
看是不能確定這個地址是用在哪兒的,它或者在記憶體中,或者是某個卡上的儲存單元, 
甚至可能這個地址上沒有對應的儲存器。


3)虛擬地址:現代作業系統普遍採用虛擬記憶體管理(Virtual Memory Management)機制,
這需要MMU(Memory Management Unit)的支援。MMU通常是CPU的一部分,如果處理器沒有MMU,
或者有MMU但沒有啟用,CPU執行單元發出的記憶體地址將直接傳到晶片引腳上,被 記憶體晶片
(實體記憶體)接收,這稱為實體地址(Physical Address),如果處理器啟用了MMU,
CPU執行單元發出的記憶體地址將被MMU截獲,從CPU到MMU的地址稱為虛擬地址(Virtual Address),
而MMU將這個地址翻譯成另一個地址發到CPU晶片的外部地址引腳上,也就是將虛擬地址對映
成實體地址。
Linux中,程序的4GB(虛擬)記憶體分為使用者空間、核心空間。使用者空間分佈為0~3GB(
即PAGE_OFFSET,在0X86中它等於 0xC0000000)
,剩下的1G為核心空間。程式設計師只能使用虛擬地址。系統中每個程序有各自的私有用
戶空間(0~3G),這個空間對系統中的其他程序是不可見的。
CPU發出取指令請求時的地址是當前上下文的虛擬地址,MMU再從頁表中找到這個虛擬地址
的實體地址,完成取指。同樣讀取資料的也是虛擬地址,比如mov ax, var. 編譯時var就
是一個虛擬地址,也是通過MMU從也表中來找到實體地址,再產生匯流排時序,完成取資料的。




(二)編址方式
1)外設都是通過讀寫裝置上的暫存器來進行的,外設暫存器也稱為“I/O埠”,而IO埠有
兩種編址方式:獨立編址和統一編制。


統一編址:外設介面中的IO暫存器(即IO埠)與主存單元一樣看待,每個端口占用一個
儲存單元的地址,將主存的一部分劃出來用作IO地址空間,如,在 PDP-11中,把最高的
4K主存作為IO裝置暫存器地址。端口占用了儲存器的地址空間,使儲存量容量減小。
統一編址也稱為“I/O記憶體”方式,外設暫存器位於“記憶體空間”(很多外設有自己的記憶體、
緩衝區,外設的暫存器和記憶體統稱“I/O空間”)。
如,Samsung的S3C2440,是32位ARM處理器,它的4GB地址空間被外設、RAM等瓜分:
0x8000 1000    LED 8*8點陣的地址
0x4800 0000 ~ 0x6000 0000  SFR(特殊暫存器)地址空間
0x3800 1002   鍵盤地址
0x3000 0000 ~ 0x3400 0000  SDRAM空間 
0x2000 0020 ~ 0x2000 002e  IDE
0x1900 0300   CS8900


獨立編址(單獨編址):IO地址與儲存地址分開獨立編址,I/0埠地址不佔用儲存空間的
地址範圍,這樣,在系統中就存在了另一種與儲存地址無關的IO地 址,CPU也必須具有專
用與輸入輸出操作的IO指令(IN、OUT等)和控制邏輯。獨立編址下,地址總線上過來一
個地址,裝置不知道是給IO埠的、還是 給儲存器的,於是處理器通過MEMR/MEMW和IOR
/IOW兩組控制訊號來實現對I/O埠和儲存器的不同定址。如,intel 80x86就採用單獨編址,
CPU記憶體和I/O是一起編址的,就是說記憶體一部分的地址和I/O地址是重疊的。
獨立編址也稱為“I/O埠”方式,外設暫存器位於“I/O(地址)空間”。
對於x86架構來說,通過IN/OUT指令訪問。PC架構一共有65536個8bit的I/O埠,組成64K
個I/O地址空間,編號從 0~0xFFFF,有16位,80x86用低16位地址線A0-A15來定址。連續兩
個8bit的埠可以組成一個16bit的埠,連續4個組成一個 32bit的埠。I/O地址空間和
CPU的實體地址空間是兩個不同的概念,例如I/O地址空間為64K,一個32bit的CPU實體地址
空間是4G。 如,在Intel 8086+Redhat9.0 下用“more /proc/ioports”可看到:
0000-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
……


不過Intel x86平臺普通使用了名為記憶體對映(MMIO)的技術,該技術是PCI規範的一部分,
IO裝置埠被對映到記憶體空間,對映後,CPU訪問IO埠就如同訪 問記憶體一樣。看Intel 
TA 719文件給出的x86/x64系統典型記憶體地址分配表:
系統資源  佔用
------------------------------------------------------------------------
BIOS  1M
本地APIC  4K
晶片組保留 2M
IO APIC  4K
PCI裝置  256M
PCI Express裝置 256M
PCI裝置(可選) 256M
顯示幀快取 16M
TSEG  1M




對於某一既定的系統,它要麼是獨立編址、要麼是統一編址,具體採用哪一種則取決於
CPU的體系結構。 如,PowerPC、m68k等採用統一編址,而X86等則採用獨立編址,存
在IO空間的概念。目前,大多數嵌入式微控制器如ARM、PowerPC等並 不提供I/O空間,
僅有記憶體空間,可直接用地址、指標訪問。但對於Linux核心而言,它可能用於不同的CPU,
所以它必須都要考慮這兩種方式,於是它採 用一種新的方法,將基於I/O對映方式的或內
存對映方式的I/O埠通稱為“I/O區域”(I/O region),不論你採用哪種方式,都要先
申請IO區域:request_resource(),結束時釋放 它:release_resource()。




2)對外設的訪問


1、訪問I/O記憶體的流程是:request_mem_region() -> ioremap() -> ioread8()/iowrite8()
 -> iounmap() -> release_mem_region() 。
前面說過,IO記憶體是統一編址下的概念,對於統一編址,IO地址空間是物理主存的一部分,
對於程式設計而言,我們只能操作虛擬記憶體,所以,訪問的第一步就是要把裝置所處的實體地址
對映到虛擬地址,Linux2.6下用ioremap():
void *ioremap(unsigned long offset, unsigned long size);
然後,我們可以直接通過指標來訪問這些地址,但是也可以用Linux核心的一組函式來讀寫:
ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()......


2、訪問I/O埠
訪問IO埠有2種途徑:I/O對映方式(I/O-mapped)、記憶體對映方式(Memory-mapped)。
前一種途徑不對映到記憶體空間,直接使用 intb()/outb()之類的函式來讀寫IO埠;後一種
MMIO是先把IO埠對映到IO記憶體(“記憶體空間”),再使用訪問IO記憶體的函式來訪問 IO埠。
void ioport_map(unsigned long port, unsigned int count);
通過這個函式,可以把port開始的count個連續的IO埠對映為一段“記憶體空間”,
然後就可以在其返回的地址是像訪問IO記憶體一樣訪問這些IO埠。






Linux下的IO埠和IO記憶體


CPU對外設埠實體地址的編址方式有兩種:一種是IO對映方式,另一種是記憶體對映方式。 
 Linux將基於IO對映方式的和記憶體對映方式的IO埠統稱為IO區域(IO region)。
  IO region仍然是一種IO資源,因此它仍然可以用resource結構型別來描述。


  Linux管理IO region:


  1) request_region()


  把一個給定區間的IO埠分配給一個IO裝置。


  2) check_region()


  檢查一個給定區間的IO埠是否空閒,或者其中一些是否已經分配給某個IO裝置。


  3) release_region()


  釋放以前分配給一個IO裝置的給定區間的IO埠。


  Linux中可以通過以下輔助函式來訪問IO埠:


  inb(),inw(),inl(),outb(),outw(),outl()


  “b”“w”“l”分別代表8位,16位,32位。


 對IO記憶體資源的訪問


  1) request_mem_region()


  請求分配指定的IO記憶體資源。


  2) check_mem_region()


  檢查指定的IO記憶體資源是否已被佔用。


  3) release_mem_region()


  釋放指定的IO記憶體資源。


  其中傳給函式的start address引數是記憶體區的實體地址(以上函式引數表已省略)。


  驅動開發人員可以將記憶體對映方式的IO埠和外設記憶體統一看作是IO記憶體資源。


  ioremap()用來將IO資源的實體地址對映到核心虛地址空間(3GB - 4GB)中,
引數addr是指向核心虛地址的指標。


  Linux中可以通過以下輔助函式來訪問IO記憶體資源:


  readb(),readw(),readl(),writeb(),writew(),writel()。


  Linux在kernel/resource.c檔案中定義了全域性變數ioport_resource和iomem_resource,
來分別描述基於IO對映方式的整個IO埠空間和基於記憶體對映方式的IO記憶體資源空間(
包括IO埠和外設記憶體)。


1)關於IO與記憶體空間:
在X86處理器中存在著I/O空間的概念,I/O空間是相對於記憶體空間而言的,它通過特定的指令in、
out來訪問。埠號標識了外設的暫存器地址。Intel語法的in、out指令格式為:
IN 累加器, {埠號│DX}
OUT {埠號│DX},累加器
目前,大多數嵌入式微控制器如ARM、PowerPC等中並不提供I/O空間,而僅存在記憶體空間。
記憶體空間可以直接通過地址、指標來訪問,程式和程式執行中使用的變數和其他資料都存
在於記憶體空間中。 
即便是在X86處理器中,雖然提供了I/O空間,如果由我們自己設計電路板,外設仍然可以
只掛接在記憶體空間。此時,CPU可以像訪問一個記憶體單元那樣訪問外設I/O埠,而不需要設
立專門的I/O指令。因此,記憶體空間是必須的,而I/O空間是可選的。


(2)inb和outb:


在Linux裝置驅動中,宜使用Linux核心提供的函式來訪問定位於I/O空間的埠,這些函式包括:
· 讀寫位元組埠(8位寬)
unsigned inb(unsigned port); 
void outb(unsigned char byte, unsigned port); 
· 讀寫字埠(16位寬)
unsigned inw(unsigned port); 
void outw(unsigned short word, unsigned port); 
· 讀寫長字埠(32位寬)
unsigned inl(unsigned port); 
void outl(unsigned longword, unsigned port); 
· 讀寫一串位元組
void insb(unsigned port, void *addr, unsigned long count); 
void outsb(unsigned port, void *addr, unsigned long count);
· insb()從埠port開始讀count個位元組埠,並將讀取結果寫入addr指向的記憶體;
outsb()將addr指向的記憶體的count個位元組連續地寫入port開始的埠。
· 讀寫一串字
void insw(unsigned port, void *addr, unsigned long count); 
void outsw(unsigned port, void *addr, unsigned long count); 
· 讀寫一串長字
void insl(unsigned port, void *addr, unsigned long count); 
void outsl(unsigned port, void *addr, unsigned long count); 
上述各函式中I/O埠號port的型別高度依賴於具體的硬體平臺,因此,
只是寫出了unsigned。