`
yidongkaifa
  • 浏览: 4065690 次
文章分类
社区版块
存档分类
最新评论

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;    
}
在我测试时,strcpy总是会跑飞,后在网上找到程序:
//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;
}
注意这里的remap_pfn_range函数里的virt_to_phys(dev -> data),因为例子采用的“设备”其实就是内存,所以需要先转化成物理地址再移位。如果以后我们操作实际的硬件,这里就不用转化了,直接填入物理地址即可。


mmap执行的顺序
a.在用户进程创建一个vma区域
b.驱动程序获得页
c.将获得的页分配给vma区域

内存映射的步骤:
* 用open系统调用打开文件, 并返回描述符fd.
* 用mmap建立内存映射, 并返回映射首地址指针start.
* 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
* 用munmap(void *start, size_t lenght)关闭内存映射.
* 用close系统调用关闭文件fd.







分享到:
评论

相关推荐

    linux mmap文件内存映射机制

    mmap系统调用并不是完全为了... mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    Python3 mmap内存映射文件示例解析

    1. mmap内存映射文件 建立一个文件的内存映射将使用操作系统虚拟内存来直接访问文件系统上的数据,而不是使用常规的I/O函数访问数据。内存映射通常可以提供I/O性能,因为使用内存映射是,不需要对每个访问都建立一个...

    QTableWidget大数据文件 文件映射mmap

    QTableWidget大数据文件显示 ...测试:QTableWidge分页显示1千万行数据,建立mmap文件映射,只读取需要显示的部分文件内容,此种方式速度快,占用内存小,内存占用149MB左右,每次只读取显示部分的数据,推荐此种方式。

    mmap内核驱动与应用程序

    里面包括 1. 一个用户空间的mmap将文件映射到内存进行操作的例子 2. 一个mmap内核驱动及对应的mmap用户空间的程序。

    ios-MUImageCache - 简单轻量的图片缓存框架.zip

    2.支持mmap内存映射,高效的I/O操作,减少一次文件拷贝操作 3.减少内存占用; 支持Byte Alignment字节对其,渲染过程中,避免执行CA::Render::copy_image内存操作; 4.支持图片绘制圆角,并避免调用( layer....

    mmap测试例程应用实例

    资源是mmap使用的例程,将文件映射到用户空间的虚拟地址中,并通过用户空间的虚拟地址实现对文件的读写操作,其中mmap_test.c为例程的源码,testdata为测试用的文件。

    Mmkv——存取速度比SharedPreferences快数十倍的本地缓存工具

    通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。 数据组织 数据序列化方面我们选用 protobuf 协议,pb 在性能和空间...

    smmap:滑动内存映射管理器

    当以类似于随机访问的方式从许多可能很大的文件中读取数据时,使用内存映射通常是最快,最有效的方法。 尽管内存映射具有许多优点,但是它们代表了非常有限的系统资源,因为每个映射都使用一个文件描述符,每个进程...

    操作系统(内存管理)

    因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,...

    mmap:节点mmap实施

    当您执行此操作时, mmap()会尝试通过将文件创建或增长到n_bytes(如有必要)来执行正确的操作。 抵消 文件偏移量。 必须为零或mmap.PAGESIZE的倍数。 安装了终结器以自动取消映射缓冲区时,您可以使用以下命令...

    深入了解MongoDB是如何存储数据的

    内存映射文件是OS通过mmap在内存中创建一个数据文件,这样就把文件映射到一个虚拟内存的区域。 虚拟内存对于进程来说,是一个物理内存的抽象,寻址空间大小为2^64 操作系统通过mmap来把进程所需的所有数据映射到...

    glibc内存管理ptmalloc源代码分析

    2.2.2 Mmap映射区域操作相关函数 3. 概述 3.1 内存管理一般性描述 3.1.1 内存管理的方法 3.1.2 内存管理器的设计目标 3.1.3 常见C内存管理程序 3.2 Ptmalloc内存管理概述 3.2.1 简介 3.2.2 内存管理的设计...

    内存管理内存管理内存管理

    因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时...

    包含LINUX内核同步、信号、内存、调度、文件系统.rar

    虚拟内存管理 包括 : 反向映射 , KSM , MMAP 映射 , 缺页中断 , 共享内存 , 进程虚拟地址空间管理 , 页面回收 ; 物理内存管理 包括 : 页面分配器 等 ; 内存管理 位于 下图 Linux 内核整体架构图 中的 内核空间 ;

    Go语言共享内存读写实例分析

    我们知道不同进程见的内存是互相独立的,没办法直接互相操作对方内的数据,而共享内存则是靠操作系统提供的内存映射机制,让不同进程的一块地址空间映射到同一个虚拟内存区域上,使不同的进程可以操作到一块共用的...

    Memeory-MappedFiles

    内存映射文件是OS通过mmap在内存中创建一个数据文件,这样就把文件映射到一个虚拟内存的区域。虚拟内存对于进程来说,是一个物理内存的抽象,寻址空间大小为2^64操作系统通过mmap来把进程所需的所有数据映射到这个...

    使用mmap实现多进程对大文件拷贝

    为降低实现复杂度,可选用mmap来实现源、目标文件的映射,通过指针操作内存地址,设置每个进程拷贝的起始、结束位置。借助MAP_SHARED选项将内存中所做的修改反映到物理磁盘上。 思路: //1. 指定创建子进程的个数 /...

    BufferFromFile:Nodejs的本机模块提供了将文件用作标准ArrayBuffer的便捷方式,从而使mmap成为幕后

    BufferFromFile使用mmap将文件从硬盘驱动器映射到内存,并返回ArrayBuffer或TypedBuffer,它们可以像普通缓冲区一样进行操作。 mmap()在调用过程的虚拟地址空间中创建一个新的映射。 在mmap操作期间,所有性能...

    V4l2视屏采集资料

    V4L2支持两种方式来采集图像:内存映射(mmap)和直接读取方式(read)。V4L2在include/linux/video.h文件下定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4...

Global site tag (gtag.js) - Google Analytics