內存映射



源地址已無法知道,故不能給出,以下爲原文:


linux中的物理地址和虛擬地址   

在支持MMU32位處理器平臺上,Linux系統中的物理存儲空間和虛擬存儲空間的地址範圍分別都是從0x000000000xFFFFFFFF,共4GB,但物理存儲空間與虛擬存儲空間佈局完全不同。Linux運行在虛擬存儲空間,並負責把系統中實際存在的遠小於4GB的物理內存根據不同需求映射到整個4GB的虛擬存儲空間中。

n物理存儲空間佈局

Linux的物理存儲空間佈局與處理器相關,詳細情況可以從處理器用戶手冊的存儲空間分佈表(memory map)相關章節中查到,我們這裏只列出嵌入式處理器平臺Linux物理內存空間的一般佈局,如圖18-4所示。

18-4 Linux物理內存空間一般佈局示意圖

說明:

1)最大noden不能大於MAX_NUMNODES-1

2MAX_NUMNODES表示系統支持的最多node數。在ARM系統中,Sharp芯片最多支持16nodes,其他芯片最多支持4nodes

3numnodes是當前系統中實際的內存node數。

4)在不支持CONFIG_DISCONTIGMEM選項的系統中,只有一個內存node

5)最大bankm不能大於NR_BANKS-1

6NR_BANKS表示系統中支持的最大內存bank數,一般等於處理器的RAM片選數。在ARM系統中,Sharp芯片最多支持16banks,其他芯片最多支持8banks

7mem_init()函數會將所有節點的頁幀位碼錶所佔空間、孔洞頁描述符空間及空閒內存頁都釋放掉

n虛擬存儲空間佈局

在支持MMU的系統中,當系統做完硬件初始化後就使能MMU功能,這樣整個系統就運行在虛擬存儲空間中,實現虛擬存儲空間到物理存儲空間映射功能的是處理器的MMU,而虛擬存儲空間與5路存儲空間的映射關係則是由Linux內核來管理的。32位系統中物理存儲空間佔4GB空間,虛擬存儲空間同樣佔4GB空間,Linux把物理空間中實際存在的遠遠小於4GB的內存空間映射到整個4GB虛擬存儲空間中除映射I/O空間之外的全部空間,所以虛擬內存空間遠遠大於物理內存空間,這就說同一塊物理內存可能映射到多處虛擬內存地址空間上,這正是Linux內存管理職責所在。圖18-5列出了Linux內核中虛擬內存空間的一般佈局(其實I/O空間也在其中,通常佔用高端內存空間,在此未標出)。

18-5 Linux系統虛擬內存空間一般佈局示意圖

說明:

1)線性地址空間:是指Linux系統中從0x000000000xFFFFFFFF整個4GB虛擬存儲空間。

2)內核空間:內核空間表示運行在處理器最高級別的超級用戶模式(supervisor mode)下的代碼或數據,內核空間佔用從0xC00000000xFFFFFFFF1GB線性地址空間,內核線性地址空間由所有進程共享,但只有運行在內核態的進程才能訪問,用戶進程可以通過系統調用切換到內核態訪問內核空間,進程運行在內核態時所產生的地址都屬於內核空間。

3)用戶空間:用戶空間佔用從0x000000000xBFFFFFFF3GB的線性地址空間,每個進程都有一個獨立的3GB用戶空間,所以用戶空間由每個進程獨有,但是內核線程沒有用戶空間,因爲它不產生用戶空間地址。另外子進程共享(繼承)父進程的用戶空間只是使用與父進程相同的用戶線性地址到物理內存地址的映射關係,而不是共享父進程用戶空間。運行在用戶態和內核態的進程都可以訪問用戶空間。

4)內核邏輯地址空間:是指從PAGE_OFFSEThigh_memory之間的線性地址空間,是系統物理內存映射區,它映射了全部或部分(如果系統包含高端內存)物理內存。內核邏輯地址空間與圖18-4中的系統RAM內存物理地址空間是一一對應的(包括內存孔洞也是一一對應的),內核邏輯地址空間中的地址與RAM內存物理地址空間中對應的地址只差一個固定偏移量,如果RAM內存物理地址空間從0x00000000地址編址,那麼這個偏移量就是PAGE_OFFSET

5)低端內存:內核邏輯地址空間所映射物理內存就是低端內存,低端內存在Linux線性地址空間中始終有永久的一一對應的內核邏輯地址,系統初始化過程中將低端內存永久映射到了內核邏輯地址空間,爲低端內存建立了虛擬映射頁表。低端內存內物理內存的物理地址與線性地址之間的轉換可以通過__pa(x)__va(x)兩個宏來進行,__pa(x)將內核邏輯地址空間的地址x轉換成對應的物理地址,相當於__virt_to_phys((unsigned long)(x))__va(x)則相反,把低端物理內存空間的地址轉換成對應的內核邏輯地址,相當於((void *)__phys_to_virt((unsigned long)(x)))

6)高端內存:低端內存地址之上的物理內存是高端內存,高端內存在Linux線性地址空間中沒有沒有固定的一一對應的內核邏輯地址,系統初始化過程中不會爲這些內存建立映射頁表將其固定映射到Linux線性地址空間,而是需要使用高端內存的時候才爲分配的高端物理內存建立映射頁表,使其能夠被內核使用,否則不能被使用。高端內存的物理地址於現行地址之間的轉換不能使用上面的__pa(x)__va(x)宏。

7)高端內存概念的由來:如上所述,Linux4GB的線性地址空間劃分成兩部分,從0x000000000xBFFFFFFF3GB空間作爲用戶空間由用戶進程獨佔,這部分線性地址空間並沒有固定映射到物理內存空間上;從0xC00000000xFFFFFFFF的第4GB線性地址空間作爲內核空間,在嵌入式系統中,這部分線性地址空間除了映射物理內存空間之外還要映射處理器內部外設寄存器空間等I/O空間。0xC0000000~high_memory之間的內核邏輯地址空間專用來固定映射系統中的物理內存,也就是說0xC0000000~high_memory之間空間大小與系統的物理內存空間大小是相同的(當然在配置了CONFIG_DISCONTIGMEMD選項的非連續內存系統中,內核邏輯地址空間和物理內存空間一樣可能存在內存孔洞),如果系統中的物理內存容量遠小於1GB,那麼內核現行地址空間中內核邏輯地址空間之上的high_memory~0xFFFFFFFF之間還有足夠的空間來固定映射一些I/O空間。可是,如果系統中的物理內存容量(包括內存孔洞)大於1GB,那麼就沒有足夠的內核線性地址空間來固定映射系統全部物理內存以及一些I/O空間了,爲了解決這個問題,在x86處理器平臺設置了一個經驗值:896MB,就是說,如果系統中的物理內存(包括內存孔洞)大於896MB,那麼將前896MB物理內存固定映射到內核邏輯地址空間0xC0000000~0xC0000000+896MB=high_memory)上,而896MB之後的物理內存則不建立到內核線性地址空間的固定映射,這部分內存就叫高端物理內存。此時內核線性地址空間high_memory~0xFFFFFFFF之間的128MB空間就稱爲高端內存線性地址空間,用來映射高端物理內存和I/O空間。896MBx86處理器平臺的經驗值,留了128MB線性地址空間來映射高端內存以及I/O地址空間,我們在嵌入式系統中可以根據具體情況修改這個閾值,比如,MIPS中將這個值設置爲0x20000000B512MB),那麼只有當系統中的物理內存空間容量大於0x20000000B時,內核才需要配置CONFIG_HIGHMEM選項,使能內核對高端內存的分配和映射功能。什麼情況需要劃分出高端物理內存以及高端物理內存閾值的設置原則見上面的內存頁區(zone)概念說明。

8)高端線性地址空間:從high_memory0xFFFFFFFF之間的線性地址空間屬於高端線性地址空間,其中VMALLOC_START~VMALLOC_END之間線性地址被vmalloc()函數用來分配物理上不連續但線性地址空間連續的高端物理內存,或者被vmap()函數用來映射高端或低端物理內存,或者由ioremap()函數來重新映射I/O物理空間。PKMAP_BASE開始的LAST_PKMAP(一般等於1024)頁線性地址空間被kmap()函數用來永久映射高端物理內存。FIXADDR_START開始的KM_TYPE_NR*NR_CPUS頁線性地址空間被kmap_atomic()函數用來臨時映射高端物理內存,其他未用高端線性地址空間可以用來在系統初始化期間永久映射I/O地址空間。

Linux 2.6.10內核中的ARM處理器平臺部分沒有對高端內存的支持,圖18-6和圖18-7分別列出了SA1100IXP4XX處理器平臺的Linux線性地址空間佈局。

在嵌入式系統中如何訪問I/O資源呢?

幾乎每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態寄存器和數據寄存器三大類,外設的寄存器通常被連續地編址。根據CPU體系結構的不同,CPU對IO端口的編址方式有兩種:

  (1)I/O映射方式(I/O-mapped)

  典型地,如X86處理器爲外設專門實現了一個單獨的地址空間,稱爲"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。

  (2)內存映射方式(Memory-mapped)

  RISC指令系統的CPU(如ARM、PowerPC等)通常只實現一個物理地址空間,外設I/O端口成爲內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。

  但是,這兩者在硬件實現上的差異對於軟件來說是完全透明的,驅動程序開發人員可以將內存映射方式的I/O端口和外設內存統一看作是"I/O內存"資源。

  一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常並沒有爲這些已知的外設I/O內存資源的物理地址預定義虛擬地址範圍,驅動程序並不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然後才能根據映射所得到的核心虛地址範圍,通過訪內指令訪問這些I/O內存資源。Linux在io.h頭文件中聲明瞭函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間(3GB-4GB)中,原型如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

  iounmap函數用於取消ioremap()所做的映射,原型如下:

void iounmap(void * addr);

  這兩個函數都是實現在mm/ioremap.c文件中。

  在將I/O內存資源的物理地址映射成核心虛地址後,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。爲了保證驅動程序的跨平臺的可移植性,我們應該使用Linux中特定的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問。如在x86平臺上,讀寫I/O的函數如下所示:

#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

#define memset_io(a,b,c) memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))

  最後,我們要特別強調驅動程序中mmap函數的實現方法。用mmap映射一個設備,意味着使用戶空間的一段地址關聯到設備內存上,這使得只要程序在分配的地址範圍內進行讀取或者寫入,實際上就是對設備的訪問。

發佈了13 篇原創文章 · 獲贊 23 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章