Linux虛擬地址和物理地址的映射

背景node

    通常狀況下,Linux系統中,進程的4GB內存空間被劃分紅爲兩個部分------用戶空間和內核空間,大小分別爲0~3G,3~4G。用戶進程一般狀況下,只能訪問用戶空間的虛擬地址,不能訪問到內核空間。每一個進程的用戶空間都是徹底獨立、互不相干的,用戶進程各自有不一樣的頁表。而內核空間是由內核負責映射,它並不會跟着進程改變,是固定的。內核空間地址有本身對應的頁表,內核的虛擬空間獨立於其餘程序。3~4G之間的內核空間中,從低地址到高地址依次爲:系統物理內存映射區—隔離帶—vmalloc虛擬內存分配區—隔離帶—高端內存映射區—專用頁面映射區—保留區。linux

 

 

內核空間內存動態申請編程

主要包括三個函數:kmalloc(), __get_free_pages, vmalloc。app

kmalloc(), __get_free_pages申請的內存位於物理地址映射區,並且在物理上也是連續的,返回的虛擬地址真實的物理地址(物理地址是連續的,虛擬地址也是連續的)只有一個固定的偏移,所以存在較簡單的轉換關係。函數

而vmalloc申請的內存位於vmalloc虛擬內存分配區(這些區都是以線性地址爲度量),它在虛擬內存空間給出一塊連續的內存區,實質上,這片連續的虛擬內存在物理內存中並不必定連續,而vmalloc申請的虛擬內存和物理內存之間沒有簡單的換算關係由於vmalloc申請的在虛擬內存空間連續的內存區在物理內存中並不必定連續,能夠想象爲了完成vmalloc,新的頁表須要被創建,所以,調用vmalloc來分配少許內存是不妥的。通常來說,kmalloc用來分配小於128K的內存,而更大的內存塊須要用vmalloc來實現佈局

 

虛擬地址與物理地址關係atom

    對於內核物理內存映射區的虛擬內存(用kmalloc(), __get_free_pages申請的),使用virt_to_phys()和phys_to_virt()來實現物理地址和內核虛擬地址之間的互相轉換。它實際上,僅僅作了3G的地址移位。上述方法適用於常規內存(內核物理內存映射區,高端內存的虛擬地址與物理地址之間不存在如此簡單的換算關係。由於它涉及到了分離物理頁的頁表控制機制。  spa

 

ioremap線程

    在ARM中,設備的寄存器或者存儲塊的這部分空間屬於內存空間的一部分,咱們稱之爲IO內存。在內核中訪問IO內存以前,咱們只有IO內存的物理地址,這樣是沒法經過軟件直接訪問的,須要首先用ioremap()函數將設備所處的物理地址映射到內核虛擬地址空間(3GB~4GB)。而後,才能根據映射所獲得的內核虛擬地址範圍,經過訪問指令訪問這些IO內存資源。在將I/O內存資源的物理地址映射成核心虛地址後,理論上講咱們就能夠象讀寫RAM那樣直接讀寫I/O內存資源了。爲了保證驅動程序的跨平臺的可移植性,咱們應該使用Linux中特定的函數來訪問I/O內存資源,而不該該經過指向核心虛地址的指針來訪問。設計

 

mmap

    用mmap映射一個設備,意味着使用戶空間的一段地址關聯到設備內存上,這使得只要程序在分配的地址範圍內進行讀取或者寫入,實際上就是對設備的訪問。這種數據傳輸是直接的,不須要用到內核空間做爲數據轉移的中間站。remap_page_range()函數的功能是構造用於映射一段物理地址的新頁表,實現了內核空間與用戶空間的映射在內核驅動程序的初始化階段,經過ioremap()將物理地址映射到內核虛擬空間;在驅動程序的mmap系統調用中,使用remap_page_range()將該塊ROM映射到用戶虛擬空間。這樣內核空間和用戶空間都能訪問這段被映射後的虛擬地址。

進程空間/內核空間/IO內存

    其中,後面兩個指的是同一段物理內存區域,只是一個爲虛擬地址(內核空間),一個爲物理地址(IO內存)。進程空間和內核空間對應着不一樣的物理地址,它們之間的數據傳遞,是實際的數據的拷貝。

 

進程空間/IO內存

其中,進程空間mmap獲得的那段虛擬地址跟IO內存對應着同一段物理地址。這個過程沒有額外的數據中轉,讀寫都直接針對硬件的物理地址進行。

 通常來說,小數據量的傳輸用ioremap()就足夠了,

 

IO內存的通常訪問方法

首先是調用request_mem_region()申請資源,即告訴內核,本驅動正在使用這段物理內存,其餘驅動不得訪問它們。在設備驅動模塊加載或open()函數中進行。

接着講寄存器地址經過ioremap()映射到內核空間虛擬地址,以後就能夠經過Linux設備訪問編程接口訪問這些設備的寄存器了。在設備驅動初始化、write(),read(),ioctl()函數中進行。

 訪問完成以後,應對ioremap()申請的虛擬地址進行釋放,並釋放release_mem_region()申請的IO內存資源。在設備驅動模塊卸載或release()函數中進行。

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

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

物理存儲空間佈局

    Linux的物理存儲空間佈局與處理器相關,詳細狀況能夠從處理器用戶手冊的存儲空間分佈表(memory map)相關章節中查到,咱們這裏只列出嵌入式處理器平臺Linux物理內存空間的通常佈局

說明:

最大noden不能大於MAX_NUMNODES-1

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

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

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

最大bankm不能大於NR_BANKS-1

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

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

虛擬存儲空間佈局

    在支持MMU的系統中,當系統作完硬件初始化後就使能MMU功能,這樣整個系統就運行在虛擬存儲空間中,實現虛擬存儲空間到物理存儲空間映射功能的是處理器的MMU,而虛擬存儲空間與5路存儲空間的映射關係則是由Linux內核來管理的。32位系統中物理存儲空間佔4GB空間,虛擬存儲空間一樣佔4GB空間,Linux把物理空間中實際存在的遠遠小於4GB的內存空間映射到整個4GB虛擬存儲空間中除映射I/O空間以外的所有空間,因此虛擬內存空間遠遠大於物理內存空間,這就說同一塊物理內存可能映射到多處虛擬內存地址空間上,這正是Linux內存管理職責所在

說明:

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

 

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

 

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

 

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

 

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

 

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

 

高端內存概念的由來:如上所述,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)概念說明。

 

高端線性地址空間:從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地址空間。

 

 

 

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

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

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

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

內存映射方式(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映射一個設備,意味着使用戶空間的一段地址關聯到設備內存上,這使得只要程序在分配的地址範圍內進行讀取或者寫入,實際上就是對設備的訪問。

相關文章
相關標籤/搜索