“小王,不瞞你說,我現在是悲喜交加啊,悲的是:這最後一章,我講的是膽顫心驚(以前自己都沒學好,現在也算還賬了),喜的是每講一張,我知道離結束就近了一點,趕快把這個東西過掉,進入下一環節,那又是我牛皮吹破天的時代了”看著小王期盼和懷疑的眼神,我,昔日的風采也不見了。
“沒事的,小濤哥,其實說真的,不是我安慰你哈,從開始我什麼都不懂,到現在我也算個入門級的高手了,都是你一手帶過來的,我已經對你推崇備至了,你就放心吧,會的你盡情教,不會的,你慢慢說,我都聽你的”小王善解人意的說。
“嗯,你真好,看來我沒看錯你,那好,繼續課程”。。。
一般情況下,使用者空間是不可能也不應該直接存取裝置的,但是裝置驅動程式可實現mmap()函數,這個函數可使得使用者空間能直接存取裝置的物理地址。實際上,mmap()S實現了這樣的一個映射過程,它將使用者空間的一段記憶體與裝置記憶體關聯,當使用者訪問使用者空間的這段位址範圍時,實際上會轉化為對裝置的訪問。
mmp()必須以PAGE_SIZE為單位進行映射,實際上,記憶體只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的位址範圍,要先進行頁對齊,強行以PAGE
_SIZE的倍數大小進行映射。驅動中mmp()函數原型如下:int (*mmp)(struct file *, struct vm_area_struct *);它實現的機制是建立頁表,並填充VMA結構體中
vm_operations_struct指標,vm_area_struct用於描述一個虛擬記憶體地區。結構體如下:
struct vm_area_struct { struct mm_struct * vm_mm; //所處的地址空間 unsigned long vm_start;//開始虛擬位址 unsigned long vm_end;//結束虛擬位址 struct vm_area_struct *vm_next; pgprot_t vm_page_prot; //存取權限 unsigned long vm_flags; //標誌 ... struct vm_operations_struct * vm_ops; //操作VMA的函數集指標 unsigned long vm_pgoff; //位移(頁幀號) struct file * vm_file; void * vm_private_data; ...};
其中vm_ops成員指向這個VMA的操作集,結構體定義如下:
struct vm_operations_struct { void (*open)(struct vm_area_struct * area); void (*close)(struct vm_area_struct * area); struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type); int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock); ...};在核心產生一個VMA後,它就會調用該VMA的open()函數。
而驅動中的mmap()函數將在使用者進行mmap()系統調用時最終被調用,當使用者調用mmap()時候核心會進行如下處理:
1)在進程的虛擬空間尋找一塊VMA.
2)將這塊VMA進行映射。
3)如果裝置驅動程式或檔案系統的file_operations定義了mmap()操作,則調用它。
4)將這個VMA插入到進程的VMA鏈表中。
file_operations中mmap()函數的第一個參數就是步驟1中找的VMA.由mmap()系統調用映射的記憶體可由munmap()解除映射。這個函數原型如下:
int munmap(caddr_t addr, size_t len);
但是,需要注意的是:當使用者進行mmap()系統調用後,儘管VMA在裝置驅動檔案操作結構體的mmap()被調用前就已經產生,核心卻不會調用VMA的open
函數,通常需要在驅動的mmap()函數中先上調用vma->vm_ops->open().為了說明問題,給出一個vm_operations_struct操作範例:
static int xxx_mmp(struct file *filp, struct vm_area_struct *vma){ if(remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff, vma->vm_end - vma->start, vma->vm_page_prot)) //建立頁表 return - EAGAIN; vma->vm_ops = &xxx_remap_vm_ops; xxx_vma_open(vma); return 0;}void xxx_vma_open(struct vm_area_struct *vma) //VMA開啟函數{ ... printk(KERN_NOTICE "xxx VMA open, virt %lx, phys %1x\n",vma->vm_start, vma->vm_pgoff 《PAGE_SHIFT);}void xxx_vma_close(struct vm_area_struct *vma) //VMA關閉函數{ ... printk(KERN_NOTICE "xxx VMA close. \n");}static struct vm_operation_struct xxx_remap_vm_ops = //VM操作結構體{ .open=xxx_vma_open, .close=xxx_vma_close, ...}
在這段代碼中調用的remap_pfn_range()建立頁表。我們前邊說過在核心空間用kmalloc申請記憶體,這部分記憶體如果要映射到使用者空間可以通過mem_map
_reserve()調用設定為保留後進行,具體怎麼操作,咱們下集繼續。