mmap内存映射操作
概述:
1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规内存映射,只能对保留的内存与物理内存之外的进行映射。
2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函数是将物理地址映射为内核虚拟地址。
3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通过remap_pfn_range函数来完成。
在应用层:
#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 : 映射区的开始地址。(一般建议为null,让内核帮我们自动寻找一个合适的地址)
length : 映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理的组合在一起:
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多
个以下位的组合体。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写
入,相当于输出到文件。直到msync()或者munmap()被调
用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原
文件。这个标志和以上标志是互斥的,只能使用其中一个。
(还有更多可选参数,具体网上易得)
fd:有效的文件描述词。
offset:被映射对象内容的起点。
return : 返回所映射的虚拟内存首地址。
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区
- #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;
- }
- //read.c 读取共享内存中的内容,读取了,内容也存在,跟普通文件一样。
- #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 创建共享内存,并写入数据。然后退出程序。
- #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;
- }
虽然取出数据,但全是零。
表头文件: #include <string.h>
定义函数: void *memcpy(void *dest, const void *src, size_t n)
函数说明: memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束'\0'而结束
返回值: 返回指向dest的指针
附加说明: 指针src和dest所指的内存区域不可重叠。
在内核中:
mmap设备方法是file_operations结构的成员,在Mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表。
prototype : int (*mmap)(struct file *, struct vm_area_struct *);
parameter: struct file * : 需要操作的文件
struct vm_area_struct * : 内核自动帮我们找到的一个虚拟内存区域。
return : 虚拟内存区域起始地址
Linux内核使用结构vm_area_struct 来描述虚拟虚拟内存区域,其中几个主要成员如下:
unsigned long vm_start : 虚拟内存区域起始地址
unsinged long vm_end : 虚拟内存区域结束地址
unsigned long vm_flags : 该区域的标记(能否直接把信息通过虚拟地址存入物理地址等)
通过上面的介绍,其实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 : 虚拟内存区域指针
addr : 虚拟地址的起始值
pfn : 要映射的物理地址的页帧号,即将物理地址右移PAGE_SHIFT(12位),至于为什
么是12位,敬请查看《深入理解LINUX内核》内存管理部分
size : 要映射的区域的大小。
prot : VMA的保护属性。
return : 返回虚拟内存起始地址。
- 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;
- }
mmap执行的顺序
a.在用户进程创建一个vma区域
b.驱动程序获得页
c.将获得的页分配给vma区域
内存映射的步骤:
* 用open系统调用打开文件, 并返回描述符fd.
* 用mmap建立内存映射, 并返回映射首地址指针start.
* 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
* 用munmap(void *start, size_t lenght)关闭内存映射.
* 用close系统调用关闭文件fd.