對於提供了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大小的地區用於專用頁面映射),如:
kmalloc和get_free_page申請的記憶體位於實體記憶體映射地區,而且在物理上也是連續的,它們與真實的物理地址只有一個固定的位移,因此存在較簡單的轉換關係,virt_to_phys()可以實現核心虛擬位址轉化為物理地址:
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
extern inline unsigned long virt_to_phys(volatile void * address)
{
return __pa(address);
}
上面轉換過程是將虛擬位址減去3G(PAGE_OFFSET=0XC000000)。
與之對應的函數為phys_to_virt(),將核心物理地址轉化為虛擬位址:
#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申請的記憶體則位於vmalloc_start~vmalloc_end之間,與物理地址沒有簡單的轉換關係,雖然在邏輯上它們也是連續的,但是在物理上它們不要求連續。
我們用下面的程式來示範kmalloc、get_free_page和vmalloc的區別:
#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)
{
//最好每次記憶體申請都檢查申請是否成功
//下面這段僅僅作為示範的代碼沒有檢查
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);
我們的系統上有160MB的記憶體空間,運行一次上述程式,發現pagemem的地址在0xc7997000(約3G+121M)、kmallocmem 地址在0xc9bc1380(約3G+155M)、vmallocmem的地址在0xcabeb000(約3G+171M)處,符合前文所述的記憶體布局。