概述:
1.對于mmap的內(nèi)存映射,是將物理內(nèi)存映射到進程的虛擬地址空間中去,那么進程對文件的訪問就相當于直接對內(nèi)存的訪問,從而加快了讀寫操作的效率。在這里,remap_pfn_range函數(shù)是一次性的建立頁表,而nopage函數(shù)是根據(jù)page fault產(chǎn)生的進程虛擬地址去找到內(nèi)核相對應的邏輯地址,再通過這個邏輯地址去找到page。完成映射過程。remap_pfn_range不能對常規(guī)內(nèi)存映射,只能對保留的內(nèi)存與物理內(nèi)存之外的進行映射。
2.在這里,要分清幾個地址,一個是物理地址,這個很簡單,就是物理內(nèi)存的實際地址。第二個是內(nèi)核虛擬地址,即內(nèi)核可以直接訪問的地址,如kmalloc,vmalloc等內(nèi)核函數(shù)返回的地址,kmalloc返回的地址也稱為內(nèi)核邏輯地址。內(nèi)核虛擬地址與實際的物理地址只有一個偏移量。第三個是進程虛擬地址,這個地址處于用戶空間。而對于mmap函數(shù)映射的是物理地址到進程虛擬地址,而不是把物理地址映射到內(nèi)核虛擬地址。而ioremap函數(shù)是將物理地址映射為內(nèi)核虛擬地址。
3.用戶空間的進程調(diào)用mmap函數(shù),首先進行必要的處理,生成vma結構體,然后調(diào)用remap_pfn_range函數(shù)建立頁表。而用戶空間的mmap函數(shù)返回的是映射到進程地址空間的首地址。所以mmap函數(shù)與remap_pfn_range函數(shù)是不同的,前者只是生成mmap,而建立頁表通過remap_pfn_range函數(shù)來完成。
在應用層:
#include <sys/mman.h>
prototype : void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
parameter :
start : 映射區(qū)的開始地址。(一般建議為null,讓內(nèi)核幫我們自動尋找一個合適的地址)
length : 映射區(qū)的長度。
prot:期望的內(nèi)存保護標志,不能與文件的打開模式?jīng)_突。是以下的某個值,可以通過or運算合理的組合在一起:
PROT_EXEC //頁內(nèi)容可以被執(zhí)行
PROT_READ //頁內(nèi)容可以被讀取
PROT_WRITE //頁可以被寫入
PROT_NONE //頁不可訪問
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。它的值可以是一個或者多
個以下位的組合體。
MAP_SHARED //與其它所有映射這個對象的進程共享映射空間。對共享區(qū)的寫
入,相當于輸出到文件。直到msync()或者munmap()被調(diào)
用,文件實際上不會被更新。
MAP_PRIVATE //建立一個寫入時拷貝的私有映射。內(nèi)存區(qū)域的寫入不會影響到原
文件。這個標志和以上標志是互斥的,只能使用其中一個。
(還有更多可選參數(shù),具體網(wǎng)上易得)
fd:有效的文件描述詞。
offset:被映射對象內(nèi)容的起點。
return : 返回所映射的虛擬內(nèi)存首地址。
成功執(zhí)行時,mmap()返回被映射區(qū)的指針,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設為以下的某個值
EACCES:訪問出錯
EAGAIN:文件已被鎖定,或者太多的內(nèi)存已被鎖定
EBADF:fd不是有效的文件描述詞
EINVAL:一個或者多個參數(shù)無效
ENFILE:已達到系統(tǒng)對打開文件的限制
ENODEV:指定文件所在的文件系統(tǒng)不支持內(nèi)存映射
ENOMEM:內(nèi)存不足,或者進程已超出最大內(nèi)存映射數(shù)量
EPERM:權能不足,操作不允許
ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標志
SIGSEGV:試著向只讀區(qū)寫入
SIGBUS:試著訪問不屬于進程的內(nèi)存區(qū)
- #include <stdio.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
- #include<unistd.h>
- #include<sys/mman.h>
-
- int main()
- {
- int fd;
- char *start;
- char buf[100];
-
- /* open the device */
- fd = open("testfile",O_RDWR);
-
- start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
-
- /* read the data */
- strcpy(buf,start);
- printf("buf = %s/n",buf);
-
- /* write data */
- strcpy(start,"Buf Is Not Null!");
-
- munmap(start,100); /* unmap */
- close(fd);
-
- return 0;
- }
在我測試時,strcpy總是會跑飛,后在網(wǎng)上找到程序:- //read.c 讀取共享內(nèi)存中的內(nèi)容,讀取了,內(nèi)容也存在,跟普通文件一樣。
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <string.h>
- #include <errno.h>
-
- #define MAPPED_FILENAME "/tmp/test.mmap.1"
- #define BUFFER_SIZE 1024
-
- int main(int argc, char **argv)
- {
- int fd;
-
- if (argc < 2)
- {
- fprintf(stdout, "Usage:%s <filename>\n", argv[0]);
- exit(-1);
- }
-
- //step 1, open a file and get a fd
- //int open(const char *pathname, int flags, mode_t mode);
- if ((fd = open(argv[1], O_RDWR | O_CREAT, 0644)) <0 )
- {
- if (errno == EEXIST)
- {
- fprintf(stderr, "Fatal error: The target mapped file existed, exit.\n");
- }
- else
- {
- fprintf(stderr, "Error: open file failed: (errno = %d) %s\n", errno, strerror(errno));
- }
- exit(-2);
- }
- off_t offset;
- offset = 1024;
- //step2, create a hole file
- //off_t lseek(int fildes, off_t offset, int whence);
- if (lseek(fd, offset, SEEK_SET) == (off_t) - 1)
- {
- fprintf(stderr, "lseek() failed:%s\n", strerror(errno));
- //FIXME:unlink the file
- close(fd);
- exit(-3);
- }
-
- size_t n;
-
- //size_t write(int fd, const void *buf, size_t count);
- if ((n = write(fd, "", 1)) < 0)
- {
- fprintf(stderr, "write() failed:%s\n", strerror(errno));
- exit(-4);
- }
-
- void *p;
- //step 3, mmap(), get a pointer
- //void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- if ((p = mmap(NULL, 1024, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)
- {
- fprintf(stderr, "mmap() failed:%s\n", strerror(errno));
- close(fd);
- exit(-4);
- }
-
- close (fd);
- fprintf(stdout, "mapped file to memory, size=%d\n", 1024);
- char buffer[BUFFER_SIZE];
-
- //step 4, read/write on shared memory
- //void *memcpy(void *dest, const void *src, size_t n);
- memcpy(buffer, p+256, 32); //與read.c不同的地方,反向拷貝
- fprintf(stdout, "%s\n", buffer);
-
- //step 5, munmap();
- //int munmap(void *stat, size_t length);
- munmap(p, 1024);
-
-
- //close(fd);
-
- return 0;
- }
-
-
-
- //write.c 創(chuàng)建共享內(nèi)存,并寫入數(shù)據(jù)。然后退出程序。
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <string.h>
- #include <errno.h>
-
- #define MAPPED_FILENAME "/tmp/test.mmap.1"
-
- int main(int argc, char **argv)
- {
- int fd;
-
- if (argc < 2)
- {
- fprintf(stdout, "Usage:%s <filename>\n", argv[0]);
- exit(-1);
- }
-
- //step 1, open a file and get a fd
- //int open(const char *pathname, int flags, mode_t mode);
- if ((fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0644)) <0 )
- {
- if (errno == EEXIST)
- {
- fprintf(stderr, "Fatal error: The target mapped file existed, exit.\n");
- }
- else
- {
- fprintf(stderr, "Error: open file failed: (errno = %d) %s\n", errno, strerror(errno));
- }
- exit(-2);
- }
- off_t offset;
- offset = 1024;
- //step2, create a hole file
- //off_t lseek(int fildes, off_t offset, int whence);
- if (lseek(fd, offset, SEEK_SET) == (off_t) - 1)
- {
- fprintf(stderr, "lseek() failed:%s\n", strerror(errno));
- //FIXME:unlink the file
- close(fd);
- exit(-3);
- }
-
- size_t n;
-
- //size_t write(int fd, const void *buf, size_t count);
- if ((n = write(fd, "", 1)) < 0)
- {
- fprintf(stderr, "write() failed:%s\n", strerror(errno));
- exit(-4);
- }
-
- void *p;
- //step 3, mmap(), get a pointer
- //void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- if ((p = mmap(NULL, 1024, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)
- {
- fprintf(stderr, "mmap() failed:%s\n", strerror(errno));
- close(fd);
- exit(-4);
- }
-
- close (fd);
- fprintf(stdout, "mapped file to memory, size=%d\n", 1024);
-
-
- //step 4, read/write on shared memory
- char *banner = "hello world.";
-
- //void *memcpy(void *dest, const void *src, size_t n);
- memset(p, '\0', 1024);
- memcpy(p+256, banner, strlen(banner));
-
- //step 5, munmap();
- //int munmap(void *stat, size_t length);
-
- return 0;
- }
雖然取出數(shù)據(jù),但全是零。
表頭文件: #include <string.h>
定義函數(shù): void *memcpy(void *dest, const void *src, size_t n)
函數(shù)說明: memcpy()用來拷貝src所指的內(nèi)存內(nèi)容前n個字節(jié)到dest所指的內(nèi)存地址上。與strcpy()不同的是,memcpy()會完整的復制n個字節(jié),不會因為遇到字符串結束'\0'而結束
返回值: 返回指向dest的指針
附加說明: 指針src和dest所指的內(nèi)存區(qū)域不可重疊。
在內(nèi)核中:
mmap設備方法是file_operations結構的成員,在Mmap系統(tǒng)調(diào)用發(fā)出時被調(diào)用。在此之前,內(nèi)核已經(jīng)完成了很多工作。mmap設備方法所需要做的就是建立虛擬地址到物理地址的頁表。
prototype : int (*mmap)(struct file *, struct vm_area_struct *);
parameter: struct file * : 需要操作的文件
struct vm_area_struct * : 內(nèi)核自動幫我們找到的一個虛擬內(nèi)存區(qū)域。
return : 虛擬內(nèi)存區(qū)域起始地址
Linux內(nèi)核使用結構vm_area_struct 來描述虛擬虛擬內(nèi)存區(qū)域,其中幾個主要成員如下:
unsigned long vm_start : 虛擬內(nèi)存區(qū)域起始地址
unsinged long vm_end : 虛擬內(nèi)存區(qū)域結束地址
unsigned long vm_flags : 該區(qū)域的標記(能否直接把信息通過虛擬地址存入物理地址等)
通過上面的介紹,其實mmap是如何完成頁表的建立呢?
方法有兩種:
1.使用remap_pfn_range一次建立所有頁表
2.使用nopage VMA方法每次建立一個頁表
我們這里詳細介紹方法一。
prototype : int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
parameter: vma : 虛擬內(nèi)存區(qū)域指針
addr : 虛擬地址的起始值
pfn : 要映射的物理地址的頁幀號,即將物理地址右移PAGE_SHIFT(12位),至于為什
么是12位,敬請查看《深入理解LINUX內(nèi)核》內(nèi)存管理部分
size : 要映射的區(qū)域的大小。
prot : VMA的保護屬性。
return : 返回虛擬內(nèi)存起始地址。
- int memdev_mmap(struct file * filp, struct vm_area_struct * vma)
- {
- vma -> vm_flags |= VM_IO;
- vma -> vm_flags |= VM_RESERVED; //設置保護屬性
-
- if (remap_pfn_range(vma, vma -> vm_start, virt_to_phys(dev -> data) >> PAGE_SHIFT, size, vma -> vm_page_prot))
- {
- return -EAGAIN;
- }
-
- return 0;
- }
注意這里的remap_pfn_range函數(shù)里的virt_to_phys(dev -> data),因為例子采用的“設備”其實就是內(nèi)存,所以需要先轉(zhuǎn)化成物理地址再移位。如果以后我們操作實際的硬件,這里就不用轉(zhuǎn)化了,直接填入物理地址即可。
mmap執(zhí)行的順序
a.在用戶進程創(chuàng)建一個vma區(qū)域
b.驅(qū)動程序獲得頁
c.將獲得的頁分配給vma區(qū)域
內(nèi)存映射的步驟:
* 用open系統(tǒng)調(diào)用打開文件, 并返回描述符fd.
* 用mmap建立內(nèi)存映射, 并返回映射首地址指針start.
* 對映射(文件)進行各種操作, 顯示(printf), 修改(sprintf).
* 用munmap(void *start, size_t lenght)關閉內(nèi)存映射.
* 用close系統(tǒng)調(diào)用關閉文件fd.
|