對於提供了mmu(儲存管理器,輔助作業系統進行記憶體管理,提供虛實位址轉換等硬體支援)的處理器而言,linux提供了複雜的儲存管理系統,使得程序所能訪問的記憶體達到4gb。
程序的4gb記憶體空間被人為的分為兩個部分--使用者空間與核心空間。使用者空間位址分布從0到3gb(page_offset,在0x86中它等於0xc0000000),3gb到4gb為核心空間,如下圖:
核心空間中,從3g到vmalloc_start這段位址是物理記憶體對映區域(該區域中包含了核心映象、物理頁框表mem_map等等),比如我們使用的vmware虛擬系統記憶體是160m,那麼3g~3g+160m這片記憶體就應該對映物理記憶體。在物理記憶體對映區之後,就是vmalloc區域。對於160m的系統而言,vmalloc_start位置應在3g+160m附近(在物理記憶體對映區與vmalloc_start期間還存在乙個8m的gap來防止躍界),vmalloc_end的位置接近4g(最後位置系統會保留一片128k大小的區域用於專用頁面對映),如下圖:
#define __pa(x) ((unsigned long)(x)-page_offset)
extern inline unsigned long virt_to_phys(volatile void * address)
上面轉換過程是將虛擬位址減去3g(page_offset=0xc000000)。
#define __va(x) ((void *)((unsigned long)(x)+page_offset))
extern inline void * phys_to_virt(unsigned long address)
virt_to_phys()和phys_to_virt()都定義在include/a**-i386/io.h中。
而vmalloc申請的記憶體則位於vmalloc_start~vmalloc_end之間,與實體地址沒有簡單的轉換關係,雖然在邏輯上它們也是連續的,但是在物理上它們不要求連續。
我們用下面的程式來演示kmalloc、get_free_page和vmalloc的區別:
#include
#include
#include
module_license("gpl");
unsigned char *pagemem;
unsigned char *kmallocmem;
unsigned char *vmallocmem;
int __init mem_module_init(void)
void __exit mem_module_exit(void)
module_init(mem_module_init);
module_exit(mem_module_exit);
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對映乙個裝置,意味著使使用者空間的一段位址關聯到裝置記憶體上,這使得只要程式在分配的位址範圍內進行讀取或者寫入,實際上就是對裝置的訪問。
筆者在linux源**中進行包含"ioremap"文字的搜尋,發現真正出現的ioremap的地方相當少。所以筆者追根索源地尋找i/o操作的實體地址轉換到虛擬位址的真實所在,發現linux有替代ioremap的語句,但是這個轉換過程卻是不可或缺的。
譬如我們再次摘取s3c2410這個arm晶元rtc(實時鐘)驅動中的一小段:
static void get_rtc_time(int alm, struct rtc_time *rtc_tm)
else
}spin_unlock_irq(&rtc_lock);
bcd_to_bin(rtc_tm->tm_year);
bcd_to_bin(rtc_tm->tm_mon);
bcd_to_bin(rtc_tm->tm_mday);
bcd_to_bin(rtc_tm->tm_hour);
bcd_to_bin(rtc_tm->tm_min);
bcd_to_bin(rtc_tm->tm_sec);
/* the epoch of tm_year is 1900 */
rtc_tm->tm_year += rtc_leap_year - 1900;
/* tm_mon starts at 0, but rtc month starts at 1 */
rtc_tm->tm_mon--;}
i/o操作似乎就是對almyear、almmon、almday定義的暫存器進行操作,那這些巨集究竟定義為什麼呢?
#define almday brtc(0x60)
#define almmon brtc(0x64)
#define almyear brtc(0x68)
其中借助了巨集brtc,這個巨集定義為:
#define brtc(nb) __reg(0x57000000 + (nb))
其中又借助了巨集__reg,而__reg又定義為:
# define __reg(x) io_p2v(x)
#define io_p2v(x) ((x) | 0xa0000000)
與__reg對應的有個__preg:
# define __preg(x) io_v2p(x)
與io_p2v對應的有個io_v2p:
#define io_v2p(x) ((x) & ~0xa0000000)
可見有沒有出現ioremap是次要的,關鍵問題是有無虛擬位址和實體地址的轉換!
下面的程式在啟動的時候保留一段記憶體,然後使用ioremap將它對映到核心虛擬空間,同時又用remap_page_range對映到使用者虛擬空間,這樣一來,核心和使用者都能訪問。如果在核心虛擬位址將這段記憶體初始化串"abcd",那麼在使用者虛擬位址能夠讀出來:
int remap_page_range(vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot);
使用mmap最典型的例子是顯示卡的驅動,將視訊記憶體空間直接從核心對映到使用者空間將可提供視訊記憶體的讀寫效率。
Linux裝置驅動程式設計之記憶體與I O操作
risc指令系統的cpu 如arm powerpc等 通常只實現乙個實體地址空間,外設i o埠成為記憶體的一部分。此時,cpu可以象訪問乙個記憶體單元那樣訪問外設i o埠,而不需要設立專門的外設i o指令。但是,這兩者在硬體實現上的差異對於軟體來說是完全透明的,驅動程式開發人員可以將記憶體對映方式的...
Linux裝置驅動之記憶體對映
linux裝置驅動之記憶體對映 日期 2011 04 11 1.記憶體對映 所謂的記憶體對映就是把物理記憶體對映到程序的位址空間之內,這些應用程式就可以直接使用輸入輸出的位址空間,從而提高讀寫的效率。linux提供了mmap 函式,用來對映物理記憶體。在驅動程式中,應用程式以裝置檔案為物件,呼叫mm...
Linux塊裝置驅動之記憶體模擬塊裝置
用記憶體代替塊裝置的總結,相對來簡單得多,對記憶體操作想必大家都很熟悉,直接分配一塊記憶體就可以直接讀寫操作了 參考 drivers block xd.c drivers block z2ram.c define ramblock size 1024 1024 乙個扇區是512位元組 static ...