相關(guān)專題: Linux設(shè)備驅(qū)動程序開發(fā)入門
對于提供了MMU(存儲管理器,輔助操作系統(tǒng)進(jìn)行內(nèi)存管理,提供虛實地址轉(zhuǎn)換等硬件支持)的處理器而言,Linux提供了復(fù)雜的存儲管理系統(tǒng),使得進(jìn)程所能訪問的內(nèi)存達(dá)到4GB。
進(jìn)程的4GB內(nèi)存空間被人為的分為兩個部分--用戶空間與內(nèi)核空間。用戶空間地址分布從0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB為內(nèi)核空間,如下圖:
內(nèi)核空間中,從3G到vmalloc_start這段地址是物理內(nèi)存映射區(qū)域(該區(qū)域中包含了內(nèi)核鏡像、物理頁框表mem_map等等),比如我們使用的VMware虛擬系統(tǒng)內(nèi)存是160M,那么3G~3G+160M這片內(nèi)存就應(yīng)該映射物理內(nèi)存。在物理內(nèi)存映射區(qū)之后,就是vmalloc區(qū)域。對于160M的系統(tǒng)而言,vmalloc_start位置應(yīng)在3G+160M附近(在物理內(nèi)存映射區(qū)與vmalloc_start期間還存在一個8M的gap來防止躍界),vmalloc_end的位置接近4G(最后位置系統(tǒng)會保留一片128k大小的區(qū)域用于專用頁面映射),如下圖:
kmalloc和get_free_page申請的內(nèi)存位于物理內(nèi)存映射區(qū)域,而且在物理上也是連續(xù)的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉(zhuǎn)換關(guān)系,virt_to_phys()可以實現(xiàn)內(nèi)核虛擬地址轉(zhuǎn)化為物理地址:
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) extern inline unsigned long virt_to_phys(volatile void * address) { return __pa(address); } |
上面轉(zhuǎn)換過程是將虛擬地址減去3G(PAGE_OFFSET=0XC000000)。
與之對應(yīng)的函數(shù)為phys_to_virt(),將內(nèi)核物理地址轉(zhuǎn)化為虛擬地址:
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET)) extern inline void * phys_to_virt(unsigned long address) { return __va(address); } |
virt_to_phys()和phys_to_virt()都定義在include\asm-i386\io.h中。
而vmalloc申請的內(nèi)存則位于vmalloc_start~vmalloc_end之間,與物理地址沒有簡單的轉(zhuǎn)換關(guān)系,雖然在邏輯上它們也是連續(xù)的,但是在物理上它們不要求連續(xù)。
我們用下面的程序來演示kmalloc、get_free_page和vmalloc的區(qū)別:
#include <linux/module.h> #include <linux/slab.h> #include <linux/vmalloc.h> MODULE_LICENSE("GPL"); unsigned char *pagemem; unsigned char *kmallocmem; unsigned char *vmallocmem;
int __init mem_module_init(void) { //最好每次內(nèi)存申請都檢查申請是否成功 //下面這段僅僅作為演示的代碼沒有檢查 pagemem = (unsigned char*)get_free_page(0); printk("<1>pagemem addr=%x", pagemem);
kmallocmem = (unsigned char*)kmalloc(100, 0); printk("<1>kmallocmem addr=%x", kmallocmem);
vmallocmem = (unsigned char*)vmalloc(1000000); printk("<1>vmallocmem addr=%x", vmallocmem);
return 0; }
void __exit mem_module_exit(void) { free_page(pagemem); kfree(kmallocmem); vfree(vmallocmem); }
module_init(mem_module_init); module_exit(mem_module_exit); |
我們的系統(tǒng)上有160MB的內(nèi)存空間,運(yùn)行一次上述程序,發(fā)現(xiàn)pagemem的地址在0xc7997000(約3G+121M)、kmallocmem地址在0xc9bc1380(約3G+155M)、vmallocmem的地址在0xcabeb000(約3G+171M)處,符合前文所述的內(nèi)存布局。
接下來,我們討論Linux設(shè)備驅(qū)動究竟怎樣訪問外設(shè)的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è)專門實現(xiàn)了一個單獨的地址空間,稱為"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。