Linux基礎系列 記憶體與I O操作

2021-05-23 08:25:49 字數 4145 閱讀 7260

該文大部**宋寶華老師的文章,自是將它納入自己整理的乙個小體系中,這個系列中如果沒有很合適的文章,會用原創補上。

對於提供了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)

#define __va(x) ((void *)((unsigned long)(x)+page_offset))

extern inline void * phys_to_virt(unsigned long address)

x86架構的**中,virt_to_phys()和phys_to_virt()都定義在include/asm-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);

我們的系統上有160mb的記憶體空間,執行一次上述程式,發現pagemem的位址在0xc7997000(約3g+121m)、kmallocmem位址在0xc9bc1380(約3g+155m)、vmallocmem的位址在0xcabeb000(約3g+171m)處,符合前文所述的記憶體布局。

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訪問

linux 記憶體分類 由於複雜的記憶體管理功能,記憶體的概念也相對複雜,有常規記憶體,高階記憶體,虛擬位址,邏輯位址,匯流排位址,實體地址,i o記憶體,裝置記憶體,預留記憶體等。高階處理器一般會提供mmu 記憶體管理單元 mmu具有虛擬位址和實體地址轉換,記憶體訪問許可權保護等功能,為了理解mm...

I O埠與I O記憶體

埠的概念 裝置通過系統匯流排上的介面與cpu相連,介面電路中含有多種暫存器,cpu向裝置讀寫資料實際上是向介面上的暫存器讀寫資料,這些暫存器稱為i o埠。乙個介面通常包含控制埠,資料埠,狀態埠。對於x86平台,實體地址就是匯流排位址。linux中,程序中的4gb虛擬記憶體分為使用者空間和核心空間,使...

基礎IO Linux與標準庫下的檔案IO操作

在進行linux的學習過程中,我們會接觸到一系列與io操作相關的函式。一般而言,我們將其分為三類,分別是 檔案io 標準io和庫函式。其中,檔案io顧名思義便是對檔案進行操作的函式,再加上在linux系統中,一切皆檔案,所以當我們在之後的時間內進行更加深入的學習之後,檔案io的操作函式將會使用的越來...