上圖說了,mmap是操做這些設備的一種方法,所謂操做設備,好比IO端口(點亮一個LED)、LCD控制器、磁盤控制器,實際上就是往設備的物理地址讀寫數據。node
可是,因爲應用程序不能直接操做設備硬件地址,因此操做系統提供了這樣的一種機制——內存映射,把設備地址映射到進程虛擬地址,mmap就是實現內存映射的接口。linux
操做設備還有不少方法,如ioctl、ioremap數組
mmap的好處是,mmap把設備內存映射到虛擬內存,則用戶操做虛擬內存至關於直接操做設備了,省去了用戶空間到內核空間的複製過程,相對IO操做來講,增長了數據的吞吐量。緩存
既然mmap是實現內存映射的接口,那麼內存映射是什麼呢?看下圖網絡
每一個進程都有獨立的進程地址空間,經過頁表和MMU,可將虛擬地址轉換爲物理地址,每一個進程都有獨立的頁表數據,這可解釋爲何兩個不一樣進程相同的虛擬地址,卻對應不一樣的物理地址。數據結構
每一個進程都有4G的虛擬地址空間,其中3G用戶空間,1G內核空間(linux),每一個進程共享內核空間,獨立的用戶空間,下圖形象地表達了這點框架
驅動程序運行在內核空間,因此驅動程序是面向全部進程的。函數
用戶空間切換到內核空間有兩種方法:測試
(1)系統調用,即軟中斷ui
(2)硬件中斷
瞭解了什麼是虛擬地址空間,那麼虛擬地址空間裏面裝的是什麼?看下圖
虛擬空間裝的大概是上面那些數據了,內存映射大概就是把設備地址映射到上圖的紅色段了,暫且稱其爲「內存映射段」,至於映射到哪一個地址,是由操做系統分配的,操做系統會把進程空間劃分爲三個部分:
(1)未分配的,即進程還未使用的地址
(2)緩存的,緩存在ram中的頁
(3)未緩存的,沒有緩存在ram中
操做系統會在未分配的地址空間分配一段虛擬地址,用來和設備地址創建映射,至於怎麼創建映射,後面再揭曉。
如今大概明白了「內存映射」是什麼了,那麼內核是怎麼管理這些地址空間的呢?任何複雜的理論最終也是經過各類數據結構體現出來的,而這裏這個數據結構就是進程描述符。從內核看,進程是分配系統資源(CPU、內存)的載體,爲了管理進程,內核必須對每一個進程所作的事情進行清楚的描述,這就是進程描述符,內核用task_struct結構體來表示進程,而且維護一個該結構體鏈表來管理全部進程。該結構體包含一些進程狀態、調度信息等上千個成員,咱們這裏主要關注進程描述符裏面的內存描述符(struct mm_struct mm)
具體的結構,請參考下圖
如今已經知道了內存映射是把設備地址映射到進程空間地址(注意:並非全部內存映射都是映射到進程地址空間的,ioremap是映射到內核虛擬空間的,mmap是映射到進程虛擬地址的),實質上是分配了一個vm_area_struct結構體加入到進程的地址空間,也就是說,把設備地址映射到這個結構體,映射過程就是驅動程序要作的事了。
以字符設備驅動爲例,通常對字符設備的操做都以下框圖
而內存映射的主要任務就是實現內核空間中的mmap()函數,先來了解一下字符設備驅動程序的框架
如下是mmap_driver.c的源代碼
下面是測試代碼test_mmap.c
下面是makefile文件
下面命令演示一下驅動程序的編譯、安裝、測試過程(注:其餘用戶在mknod以後還須要chmod改變權限)
# make //編譯驅動
# insmod mmap_driver.ko //安裝驅動
# mknod /dev/mmap_driver c 999 0 //建立設備文件
# gcc test_mmap.c -o test.o //編譯應用程序
# ./test.o //運行應用程序來測試驅動程序
拓展:
關於這個過程,涉及一些術語
(1)設備文件:linux中對硬件虛擬成設備文件,對普通文件的各類操做均適用於設備文件
(2)索引節點:linux使用索引節點來記錄文件信息(如文件長度、建立修改時間),它存儲在磁盤中,讀入內存後就是一個inode結構體,文件系統維護了一個索引節點的數組,每一個元素都和文件或者目錄一一對應。
(3)主設備號:如上面的999,表示設備的類型,好比該設備是lcd仍是usb等
(4)次設備號:如上面的0,表示該類設備上的不一樣設備
(5)文件(普通文件或設備文件)的三個結構
①文件操做:struct file_operations
②文件對象:struct file
③文件索引節點:struct inode
關於驅動程序中內存映射的實現,先了解一下open和close的流程
(1)設備驅動open流程
①應用程序調用open("/dev/mmap_driver", O_RDWR);
②Open就會經過VFS找到該設備的索引節點(inode),mknod的時候會根據設備號把驅動程序的file_operations結構填充到索引節點中(關於mknod /dev/mmap_driver c 999 0,這條指令建立了設備文件,在安裝驅動(insmod)的時候,會運行驅動程序的初始化程序(module_init),在初始化程序中,會註冊它的主設備號到系統中(cdev_add),若是mknod時的主設備號999在系統中不存在,即和註冊的主設備號不一樣,則上面的指令會執行失敗,就建立不了設備文件)
③而後根據設備文件的索引節點中的file_operations中的open指針,就調用驅動的open方法了。
④生成一個文件對象files_struct結構,系統維護一個files_struct的鏈表,表示系統中全部打開的文件
⑤返回文件描述符fd,把fd加入到進程的文件描述符表中
(2)設備驅動close流程
應用程序調用close(fd),最終可調用驅動的close,爲何根據一個簡單的int型fd就能夠找到驅動的close函數?這就和上面說的三個結構(struct file_operations、struct file、struct inode)息息相關了,假如fd = 3
(3)設備驅動mmap流程
由open和close得知,同理,應用程序調用mmap最終也會調用到驅動程序中mmap方法
①應用程序test.mmap.c中mmap函數
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射後虛擬地址的起始地址,一般爲NULL,內核自動分配
length:映射區的大小
prot:頁面訪問權限(PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE)
flags:參考網絡資料
fd:文件描述符
offset:文件映射開始偏移量
②驅動程序的mmap_driver.c中mmap函數
上面說了,mmap的主要工做是把設備地址映射到進程虛擬地址,也便是一個vm_area_struct的結構體,這裏說的映射,是一個很懸的東西,那它在程序中的表現是什麼呢?——頁表,沒錯,就是頁表,映射就是要創建頁表。進程地址空間就能夠經過頁表(軟件)和MMU(硬件)映射到設備地址上了
virt_to_phys(buf),buf是在open時申請的地址,這裏使用virt_to_phys把buf轉換成物理地址,是模擬了一個硬件設備,即把虛擬設備映射到虛擬地址,在實際中能夠直接使用物理地址。
總結
①從以上看到,內核各個模塊錯綜複雜、相互交叉
②單純一個小小驅動模塊,就涉及了進程管理(進程地址空間)、內存管理(頁表與頁幀映射)、虛擬文件系統(structfile、structinode)
③並非全部設備驅動均可以使用mmap來映射,好比像串口和其餘面向流的設備,而且必須按照頁大小進行映射。