代碼格式不清漠魏,查看原文章見以下鏈接
https://www.cnblogs.com/big-devil/p/8590228.html
?背景
一般情況下焕襟,Linux系統(tǒng)中碾局,進(jìn)程的4GB內(nèi)存空間被劃分成為兩個部分------用戶空間和內(nèi)核空間桥滨,大小分別為0~3G芽唇,3~4G。用戶進(jìn)程通常情況下脓豪,只能訪問用戶空間的虛擬地址巷帝,不能訪問到內(nèi)核空間。每個進(jìn)程的用戶空間都是完全獨(dú)立扫夜、互不相干的楞泼,用戶進(jìn)程各自有不同的頁表驰徊。而內(nèi)核空間是由內(nèi)核負(fù)責(zé)映射,它并不會跟著進(jìn)程改變堕阔,是固定的棍厂。內(nèi)核空間地址有自己對應(yīng)的頁表,內(nèi)核的虛擬空間獨(dú)立于其他程序超陆。3~4G之間的內(nèi)核空間中牺弹,從低地址到高地址依次為:系統(tǒng)物理內(nèi)存映射區(qū)—隔離帶—vmalloc虛擬內(nèi)存分配區(qū)—隔離帶—高端內(nèi)存映射區(qū)—專用頁面映射區(qū)—保留區(qū)。
?內(nèi)核空間內(nèi)存動態(tài)申請
主要包括三個函數(shù):kmalloc(), __get_free_pages, vmalloc时呀。
?kmalloc(), __get_free_pages申請的內(nèi)存位于物理地址映射區(qū)张漂,而且在物理上也是連續(xù)的,返回的虛擬地址與真實(shí)的物理地址(物理地址是連續(xù)的谨娜,虛擬地址也是連續(xù)的)只有一個固定的偏移航攒,因此存在較簡單的轉(zhuǎn)換關(guān)系。
?而vmalloc申請的內(nèi)存位于vmalloc虛擬內(nèi)存分配區(qū)(這些區(qū)都是以線性地址為度量)瞧预,它在虛擬內(nèi)存空間給出一塊連續(xù)的內(nèi)存區(qū)屎债,實(shí)質(zhì)上,這片連續(xù)的虛擬內(nèi)存在物理內(nèi)存中并不一定連續(xù)垢油,而vmalloc申請的虛擬內(nèi)存和物理內(nèi)存之間也沒有簡單的換算關(guān)系盆驹。因?yàn)関malloc申請的在虛擬內(nèi)存空間連續(xù)的內(nèi)存區(qū)在物理內(nèi)存中并不一定連續(xù),可以想象為了完成vmalloc滩愁,新的頁表需要被建立躯喇,因此,調(diào)用vmalloc來分配少量內(nèi)存是不妥的硝枉。一般來講廉丽,kmalloc用來分配小于128K的內(nèi)存,而更大的內(nèi)存塊需要用vmalloc來實(shí)現(xiàn)妻味。
?虛擬地址與物理地址關(guān)系
對于內(nèi)核物理內(nèi)存映射區(qū)的虛擬內(nèi)存(用kmalloc(), __get_free_pages申請的)正压,使用virt_to_phys()和phys_to_virt()來實(shí)現(xiàn)物理地址和內(nèi)核虛擬地址之間的互相轉(zhuǎn)換。它實(shí)際上责球,僅僅做了3G的地址移位焦履。上述方法適用于常規(guī)內(nèi)存(內(nèi)核物理內(nèi)存映射區(qū)),高端內(nèi)存的虛擬地址與物理地址之間不存在如此簡單的換算關(guān)系雏逾。因?yàn)樗婕暗搅朔蛛x物理頁的頁表控制機(jī)制嘉裤。
?ioremap
在ARM中,設(shè)備的寄存器或者存儲塊的這部分空間屬于內(nèi)存空間的一部分栖博,我們稱之為IO內(nèi)存屑宠。在內(nèi)核中訪問IO內(nèi)存之前,我們只有IO內(nèi)存的物理地址仇让,這樣是無法通過軟件直接訪問的典奉,需要首先用ioremap()函數(shù)將設(shè)備所處的物理地址映射到內(nèi)核虛擬地址空間(3GB~4GB)躺翻。然后,才能根據(jù)映射所得到的內(nèi)核虛擬地址范圍秋柄,通過訪問指令訪問這些IO內(nèi)存資源获枝。在將I/O內(nèi)存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內(nèi)存資源了骇笔。為了保證驅(qū)動程序的跨平臺的可移植性,我們應(yīng)該使用Linux中特定的函數(shù)來訪問I/O內(nèi)存資源嚣崭,而不應(yīng)該通過指向核心虛地址的指針來訪問笨触。
?mmap
用mmap映射一個設(shè)備,意味著使用戶空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上雹舀,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或者寫入芦劣,實(shí)際上就是對設(shè)備的訪問。這種數(shù)據(jù)傳輸是直接的说榆,不需要用到內(nèi)核空間作為數(shù)據(jù)轉(zhuǎn)移的中間站虚吟。remap_page_range()函數(shù)的功能是構(gòu)造用于映射一段物理地址的新頁表,實(shí)現(xiàn)了內(nèi)核空間與用戶空間的映射签财。在內(nèi)核驅(qū)動程序的初始化階段串慰,通過ioremap()將物理地址映射到內(nèi)核虛擬空間;在驅(qū)動程序的mmap系統(tǒng)調(diào)用中唱蒸,使用remap_page_range()將該塊ROM映射到用戶虛擬空間邦鲫。這樣內(nèi)核空間和用戶空間都能訪問這段被映射后的虛擬地址。
?進(jìn)程空間/內(nèi)核空間/IO內(nèi)存
? ??其中神汹,后面兩個指的是同一段物理內(nèi)存區(qū)域庆捺,只是一個為虛擬地址(內(nèi)核空間),一個為物理地址(IO內(nèi)存)屁魏。進(jìn)程空間和內(nèi)核空間對應(yīng)著不同的物理地址滔以,它們之間的數(shù)據(jù)傳遞,是實(shí)際的數(shù)據(jù)的拷貝氓拼。
?進(jìn)程空間/IO內(nèi)存
其中你画,進(jìn)程空間mmap得到的那段虛擬地址跟IO內(nèi)存對應(yīng)著同一段物理地址。這個過程沒有額外的數(shù)據(jù)中轉(zhuǎn)披诗,讀寫都直接針對硬件的物理地址進(jìn)行撬即。
一般來講,小數(shù)據(jù)量的傳輸用ioremap()就足夠了呈队,
?IO內(nèi)存的一般訪問方法
?首先是調(diào)用request_mem_region()申請資源剥槐,即告訴內(nèi)核,本驅(qū)動正在使用這段物理內(nèi)存宪摧,其他驅(qū)動不得訪問它們粒竖。在設(shè)備驅(qū)動模塊加載或open()函數(shù)中進(jìn)行颅崩。
?接著講寄存器地址通過ioremap()映射到內(nèi)核空間虛擬地址,之后就可以通過Linux設(shè)備訪問編程接口訪問這些設(shè)備的寄存器了蕊苗。在設(shè)備驅(qū)動初始化刷后、write(),read(),ioctl()函數(shù)中進(jìn)行。
??訪問完成之后寸莫,應(yīng)對ioremap()申請的虛擬地址進(jìn)行釋放并思,并釋放release_mem_region()申請的IO內(nèi)存資源。在設(shè)備驅(qū)動模塊卸載或release()函數(shù)中進(jìn)行瞧柔。
?linux中的物理地址和虛擬地址?:
? ??在支持MMU的32位處理器平臺上漆弄,Linux系統(tǒng)中的物理存儲空間和虛擬存儲空間的地址范圍分別都是從0x00000000到0xFFFFFFFF,共4GB造锅,但物理存儲空間與虛擬存儲空間布局完全不同撼唾。Linux運(yùn)行在虛擬存儲空間,并負(fù)責(zé)把系統(tǒng)中實(shí)際存在的遠(yuǎn)小于4GB的物理內(nèi)存根據(jù)不同需求映射到整個4GB的虛擬存儲空間中哥蔚。
?物理存儲空間布局
? ??Linux的物理存儲空間布局與處理器相關(guān)倒谷,詳細(xì)情況可以從處理器用戶手冊的存儲空間分布表(memory map)相關(guān)章節(jié)中查到,我們這里只列出嵌入式處理器平臺Linux物理內(nèi)存空間的一般布局糙箍。
說明:
?最大node號n不能大于MAX_NUMNODES-1渤愁。
?MAX_NUMNODES表示系統(tǒng)支持的最多node數(shù)。在ARM系統(tǒng)中倍靡,Sharp芯片最多支持16個nodes猴伶,其他芯片最多支持4個nodes。
?numnodes是當(dāng)前系統(tǒng)中實(shí)際的內(nèi)存node數(shù)塌西。
?在不支持CONFIG_DISCONTIGMEM選項(xiàng)的系統(tǒng)中他挎,只有一個內(nèi)存node。
?最大bank號m不能大于NR_BANKS-1捡需。
?NR_BANKS表示系統(tǒng)中支持的最大內(nèi)存bank數(shù)办桨,一般等于處理器的RAM片選數(shù)。在ARM系統(tǒng)中站辉,Sharp芯片最多支持16個banks呢撞,其他芯片最多支持8個banks。
?mem_init()函數(shù)會將所有節(jié)點(diǎn)的頁幀位碼表所占空間饰剥、孔洞頁描述符空間及空閑內(nèi)存頁都釋放掉殊霞。
?虛擬存儲空間布局
? ??在支持MMU的系統(tǒng)中,當(dāng)系統(tǒng)做完硬件初始化后就使能MMU功能汰蓉,這樣整個系統(tǒng)就運(yùn)行在虛擬存儲空間中绷蹲,實(shí)現(xiàn)虛擬存儲空間到物理存儲空間映射功能的是處理器的MMU,而虛擬存儲空間與5路存儲空間的映射關(guān)系則是由Linux內(nèi)核來管理的。32位系統(tǒng)中物理存儲空間占4GB空間祝钢,虛擬存儲空間同樣占4GB空間比规,Linux把物理空間中實(shí)際存在的遠(yuǎn)遠(yuǎn)小于4GB的內(nèi)存空間映射到整個4GB虛擬存儲空間中除映射I/O空間之外的全部空間,所以虛擬內(nèi)存空間遠(yuǎn)遠(yuǎn)大于物理內(nèi)存空間拦英,這就說同一塊物理內(nèi)存可能映射到多處虛擬內(nèi)存地址空間上蜒什,這正是Linux內(nèi)存管理職責(zé)所在。
說明:
?線性地址空間:是指Linux系統(tǒng)中從0x00000000到0xFFFFFFFF整個4GB虛擬存儲空間疤估。
?內(nèi)核空間:內(nèi)核空間表示運(yùn)行在處理器最高級別的超級用戶模式(supervisor mode)下的代碼或數(shù)據(jù)灾常,內(nèi)核空間占用從0xC000000到0xFFFFFFFF的1GB線性地址空間,內(nèi)核線性地址空間由所有進(jìn)程共享做裙,但只有運(yùn)行在內(nèi)核態(tài)的進(jìn)程才能訪問岗憋,用戶進(jìn)程可以通過系統(tǒng)調(diào)用切換到內(nèi)核態(tài)訪問內(nèi)核空間,進(jìn)程運(yùn)行在內(nèi)核態(tài)時所產(chǎn)生的地址都屬于內(nèi)核空間锚贱。
?用戶空間:用戶空間占用從0x00000000到0xBFFFFFFF共3GB的線性地址空間,每個進(jìn)程都有一個獨(dú)立的3GB用戶空間关串,所以用戶空間由每個進(jìn)程獨(dú)有拧廊,但是內(nèi)核線程沒有用戶空間,因?yàn)樗划a(chǎn)生用戶空間地址晋修。另外子進(jìn)程共享(繼承)父進(jìn)程的用戶空間只是使用與父進(jìn)程相同的用戶線性地址到物理內(nèi)存地址的映射關(guān)系吧碾,而不是共享父進(jìn)程用戶空間。運(yùn)行在用戶態(tài)和內(nèi)核態(tài)的進(jìn)程都可以訪問用戶空間墓卦。
?內(nèi)核邏輯地址空間:是指從PAGE_OFFSET到high_memory之間的線性地址空間倦春,是系統(tǒng)物理內(nèi)存映射區(qū),它映射了全部或部分(如果系統(tǒng)包含高端內(nèi)存)物理內(nèi)存落剪。內(nèi)核邏輯地址空間與圖18-4中的系統(tǒng)RAM內(nèi)存物理地址空間是一一對應(yīng)的(包括內(nèi)存孔洞也是一一對應(yīng)的)睁本,內(nèi)核邏輯地址空間中的地址與RAM內(nèi)存物理地址空間中對應(yīng)的地址只差一個固定偏移量,如果RAM內(nèi)存物理地址空間從0x00000000地址編址忠怖,那么這個偏移量就是PAGE_OFFSET呢堰。
?低端內(nèi)存:內(nèi)核邏輯地址空間所映射物理內(nèi)存就是低端內(nèi)存,低端內(nèi)存在Linux線性地址空間中始終有永久的一一對應(yīng)的內(nèi)核邏輯地址凡泣,系統(tǒng)初始化過程中將低端內(nèi)存永久映射到了內(nèi)核邏輯地址空間枉疼,為低端內(nèi)存建立了虛擬映射頁表。低端內(nèi)存內(nèi)物理內(nèi)存的物理地址與線性地址之間的轉(zhuǎn)換可以通過__pa(x)和__va(x)兩個宏來進(jìn)行鞋拟,__pa(x)將內(nèi)核邏輯地址空間的地址x轉(zhuǎn)換成對應(yīng)的物理地址骂维,相當(dāng)于__virt_to_phys((unsigned long)(x)),__va(x)則相反贺纲,把低端物理內(nèi)存空間的地址轉(zhuǎn)換成對應(yīng)的內(nèi)核邏輯地址航闺,相當(dāng)于((void *)__phys_to_virt((unsigned long)(x)))。
?高端內(nèi)存:低端內(nèi)存地址之上的物理內(nèi)存是高端內(nèi)存哮笆,高端內(nèi)存在Linux線性地址空間中沒有沒有固定的一一對應(yīng)的內(nèi)核邏輯地址来颤,系統(tǒng)初始化過程中不會為這些內(nèi)存建立映射頁表將其固定映射到Linux線性地址空間汰扭,而是需要使用高端內(nèi)存的時候才為分配的高端物理內(nèi)存建立映射頁表,使其能夠被內(nèi)核使用福铅,否則不能被使用萝毛。高端內(nèi)存的物理地址于現(xiàn)行地址之間的轉(zhuǎn)換不能使用上面的__pa(x)和__va(x)宏。
?高端內(nèi)存概念的由來:如上所述滑黔,Linux將4GB的線性地址空間劃分成兩部分笆包,從0x00000000到0xBFFFFFFF共3GB空間作為用戶空間由用戶進(jìn)程獨(dú)占,這部分線性地址空間并沒有固定映射到物理內(nèi)存空間上略荡;從0xC0000000到0xFFFFFFFF的第4GB線性地址空間作為內(nèi)核空間庵佣,在嵌入式系統(tǒng)中,這部分線性地址空間除了映射物理內(nèi)存空間之外還要映射處理器內(nèi)部外設(shè)寄存器空間等I/O空間汛兜。0xC0000000~high_memory之間的內(nèi)核邏輯地址空間專用來固定映射系統(tǒng)中的物理內(nèi)存巴粪,也就是說0xC0000000~high_memory之間空間大小與系統(tǒng)的物理內(nèi)存空間大小是相同的(當(dāng)然在配置了CONFIG_DISCONTIGMEMD選項(xiàng)的非連續(xù)內(nèi)存系統(tǒng)中,內(nèi)核邏輯地址空間和物理內(nèi)存空間一樣可能存在內(nèi)存孔洞)粥谬,如果系統(tǒng)中的物理內(nèi)存容量遠(yuǎn)小于1GB肛根,那么內(nèi)核現(xiàn)行地址空間中內(nèi)核邏輯地址空間之上的high_memory~0xFFFFFFFF之間還有足夠的空間來固定映射一些I/O空間÷┎撸可是派哲,如果系統(tǒng)中的物理內(nèi)存容量(包括內(nèi)存孔洞)小于1GB,那么就沒有足夠的內(nèi)核線性地址空間來固定映射系統(tǒng)全部物理內(nèi)存以及一些I/O空間了掺喻,為了解決這個問題芭届,在x86處理器平臺設(shè)置了一個經(jīng)驗(yàn)值:896MB,就是說感耙,如果系統(tǒng)中的物理內(nèi)存(包括內(nèi)存孔洞)大于896MB褂乍,那么將前896MB物理內(nèi)存固定映射到內(nèi)核邏輯地址空間0xC0000000~0xC0000000+896MB(=high_memory)上,而896MB之后的物理內(nèi)存則不建立到內(nèi)核線性地址空間的固定映射抑月,這部分內(nèi)存就叫高端物理內(nèi)存树叽。此時內(nèi)核線性地址空間high_memory~0xFFFFFFFF之間的128MB空間就稱為高端內(nèi)存線性地址空間,用來映射高端物理內(nèi)存和I/O空間谦絮。896MB是x86處理器平臺的經(jīng)驗(yàn)值题诵,留了128MB線性地址空間來映射高端內(nèi)存以及I/O地址空間,我們在嵌入式系統(tǒng)中可以根據(jù)具體情況修改這個閾值层皱,比如性锭,MIPS中將這個值設(shè)置為0x20000000B(512MB),那么只有當(dāng)系統(tǒng)中的物理內(nèi)存空間容量大于0x20000000B時叫胖,內(nèi)核才需要配置CONFIG_HIGHMEM選項(xiàng)草冈,使能內(nèi)核對高端內(nèi)存的分配和映射功能。什么情況需要劃分出高端物理內(nèi)存以及高端物理內(nèi)存閾值的設(shè)置原則見上面的內(nèi)存頁區(qū)(zone)概念說明。
?高端線性地址空間:從high_memory到0xFFFFFFFF之間的線性地址空間屬于高端線性地址空間怎棱,其中VMALLOC_START~VMALLOC_END之間線性地址被vmalloc()函數(shù)用來分配物理上不連續(xù)但線性地址空間連續(xù)的高端物理內(nèi)存哩俭,或者被vmap()函數(shù)用來映射高端或低端物理內(nèi)存,或者由ioremap()函數(shù)來重新映射I/O物理空間拳恋。PKMAP_BASE開始的LAST_PKMAP(一般等于1024)頁線性地址空間被kmap()函數(shù)用來永久映射高端物理內(nèi)存凡资。FIXADDR_START開始的KM_TYPE_NR*NR_CPUS頁線性地址空間被kmap_atomic()函數(shù)用來臨時映射高端物理內(nèi)存,其他未用高端線性地址空間可以用來在系統(tǒng)初始化期間永久映射I/O地址空間谬运。
?嵌入式系統(tǒng)中如何訪問I/O資源
? ??幾乎每一種外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的隙赁,通常包括控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類梆暖,外設(shè)的寄存器通常被連續(xù)地編址伞访。根據(jù)CPU體系結(jié)構(gòu)的不同,CPU對IO端口的編址方式有兩種:
?I/O映射方式(I/O-mapped)
典型地轰驳,如X86處理器為外設(shè)專門實(shí)現(xiàn)了一個單獨(dú)的地址空間厚掷,稱為"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元级解。
?內(nèi)存映射方式(Memory-mapped)
RISC指令系統(tǒng)的CPU(如ARM蝗肪、PowerPC等)通常只實(shí)現(xiàn)一個物理地址空間,外設(shè)I/O端口成為內(nèi)存的一部分蠕趁。此時,CPU可以象訪問一個內(nèi)存單元那樣訪問外設(shè)I/O端口辛馆,而不需要設(shè)立專門的外設(shè)I/O指令俺陋。
但是,這兩者在硬件實(shí)現(xiàn)上的差異對于軟件來說是完全透明的昙篙,驅(qū)動程序開發(fā)人員可以將內(nèi)存映射方式的I/O端口和外設(shè)內(nèi)存統(tǒng)一看作是"I/O內(nèi)存"資源腊状。
一般來說,在系統(tǒng)運(yùn)行時苔可,外設(shè)的I/O內(nèi)存資源的物理地址是已知的缴挖,由硬件的設(shè)計決定。但是CPU通常并沒有為這些已知的外設(shè)I/O內(nèi)存資源的物理地址預(yù)定義虛擬地址范圍焚辅,驅(qū)動程序并不能直接通過物理地址訪問I/O內(nèi)存資源映屋,而必須將它們映射到核心虛地址空間內(nèi)(通過頁表),然后才能根據(jù)映射所內(nèi)指令訪問這些I/O得到的核心虛地址范圍同蜻,通過訪內(nèi)存資源棚点。Linux在io.h頭文件中聲明了函數(shù)ioremap(),用來將I/O內(nèi)存資源的物理地址映射到核心虛地址空間(3GB-4GB)中湾蔓,原型如下:
void* ioremap(unsignedlongphys_addr, unsignedlongsize, unsignedlongflags);
iounmap函數(shù)用于取消ioremap()所做的映射瘫析,原型如下:
voidiounmap(void* addr);
這兩個函數(shù)都是實(shí)現(xiàn)在mm/ioremap.c文件中。
在將I/O內(nèi)存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內(nèi)存資源了贬循。為了保證驅(qū)動程序的跨平臺的可移植性咸包,我們應(yīng)該使用Linux中特定的函數(shù)來訪問I/O內(nèi)存資源,而不應(yīng)該通過指向核心虛地址的指針來訪問杖虾。如在x86平臺上烂瘫,讀寫I/O的函數(shù)如下所示:
#definereadb(addr) (*(volatile unsigned char *) __io_virt(addr))#definereadw(addr) (*(volatile unsigned short *) __io_virt(addr))#definereadl(addr) (*(volatile unsigned int *) __io_virt(addr))#definewriteb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))#definewritew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))#definewritel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))#definememset_io(a,b,c) memset(__io_virt(a),(b),(c))#definememcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))#definememcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))
最后,我們要特別強(qiáng)調(diào)驅(qū)動程序中mmap函數(shù)的實(shí)現(xiàn)方法亏掀。用mmap映射一個設(shè)備忱反,意味著使用戶空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或者寫入滤愕,實(shí)際上就是對設(shè)備的訪問温算。