美文网首页
Advanced File I/O Mapping Files

Advanced File I/O Mapping Files

作者: 无无吴 | 来源:发表于2019-08-14 14:58 被阅读0次

    Mapping Files into Memory

    作为标准文件I/O的一种替代,内核提供了一个接口,允许应用程序将文件映射到内存中,这意味着内存与文件内容之间存在一对一的对应关系。然后,程序员可以通过内存直接访问文件,与任何其他内存块相同。甚至允许写入内存,可以透明地映射回磁盘上的文件。

    mmap()

    #include <sys/mman.h>
    void * mmap(void *addr, size_t len, int prot, int flags, 
          int fd, off_t offset);
    

    对mmap()的调用要求内核映射文件描述符fd表示的对象的len字节。
    从offset字节处开始进入文件,映射进内存。
    如果包含addr,则表示在内存中使用地址作为推荐起始。
    addr参数向内核提供了映射文件的最佳位置的建议。这只是一个提示;大多数用户传递0。
    调用返回映射开始的内存中的实际地址。
    访问权限由prot控制。可以或在一起:

    • PROT_READ
      这几页可以阅读。
    • PROT_WRITE
      这几页可以写入。
    • PROT_EXEC
      这几页可以执行。
      注意不要和文件的打开方式冲突
      其他行为可以由flag提供:
    • MAP_FIX
      指示mmap()将addr视为需求,而不是提示。如果内核无法将映射放置在给定地址,则调用失败。如果地址和长度参数重叠 一个现有的映射,重叠的页面被丢弃并被新的映射所取代。由于此选项需要对进程地址空间有深入的了解,所以它是不可移植的,不建议使用这个。
    • MAP_PRIVATE
      映射的状态时不被共享的。文件被映射为写复制,并且此进程在内存中所做的任何更改都不会反映在实际文件中,也不会反映在其他进程。
    • MAP_SHARED
      与映射该文件的所有其他进程共享映射。写入映射相当于写入文件。从映射中读取将反映其他过程对它的写。

    映射文件描述符时,文件的引用计数将增加。因此,您可以在映射文件之后关闭文件描述符,您的进程仍然可以访问它。取消映射文件或进程终止时,将导致文件引用计数减少。

    //example
    void *p;
    p = mmap(0, len, PORT_READ, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED)
          perror("mmap");
    

    The page size

    page是内存管理单元(MMU)的粒度单元。
    准确地说,它是最小的内存单位,可以有不同的权限和行为。
    获取page大小的标准POSIX方法是使用sysconf(),它可以检索各种特定于系统的信息:

    #include <unistd.h>
    long sysconf(int name);
    

    对sysconf()的调用返回配置项name的值,如果名称无效,则返回−1。

    long page_size = sysconf(_SC_PAGESIZE);
    

    Linux还提供了getpagesize()函数:

    #include <unstd.h>
    int getpagesize(void);
    

    对getpagesize()的调用同样将返回页的大小(以字节为单位)。使用甚至比sysconf()更简单:

    int page_size = getpagesize();
    

    page大小也静态地存储在宏PAGE_SIZE中,该宏在<sys/user.h>中定义。因此,检索页面大小的第三种方法是:

    int page_size = PAGE_SIZE;
    

    sysconf()方法是可移植性和未来兼容性的最佳选择。

    Return values and error codes

    成功后,对mmap()的调用返回映射的位置。在失败时,调用返回MAP_FAILED并适当地设置errno。对mmap()的调用永远不会返回0。

    Assocaited signals

    两个信号与映射区域相关联:

    • SIGBUS
      这个信号是当进程试图访问一个不再有效的映射区域时产生的,例如,因为文件被截断,在它被映射之后。
    • SIGSEGV
      此信号是在进程试图写入映射为只读的区域时生成的。

    munmap()

    Linux提供了munmap()系统调用,用于删除用mmap()创建的映射:

    #include <sys/mman.h>
    int munmap(void *addr, size_t len);
    

    通常,munmap()用之前的mmap()调用中传递的返回值和len参数。
    成功时,munmap()返回0;如果失败,则返回−1,并适当设置errno。

    if(munmap(addr, len) == -1)
          perror("munmap");
    

    Mapping Example

    int mappingExample(int argc, char*argv[]) {
        struct stat sb;
        off_t len;
        char *p;
        int fd;
        if(argc < 2){
            fprintf(stderr, "usage: %s <file>\n", argv[0]);
        }
    
        fd = open(argv[1], O_RDONLY);
        if(fd == -1){
            perror("open");
            return 1;
        }
        if(fstat(fd, &sb) == -1){
            perror("fstat");
            return 1;
        }
        if(!S_ISREG(sb.st_mode)){
            fprintf(stderr, "%s is not a file\n", argv[1]);
            return 1;
        }
        p = (char*)mmap(0, sb.st_size,
                PROT_READ, MAP_SHARED, fd, 0);
        if(p == MAP_FAILED){
            perror("mmap");
            return 1;
        }
        if(close(fd) == -1){
            perror("close");
            return 1;
        }
    
        for(len = 0; len < sb.st_size; ++len)
            putchar(p[len]);
        if(munmap(p, sb.st_size) == -1){
            perror("munmap");
            return 1;
        }
        return 0;
    
    }
    
    int main(int argc, char*argv[])
    {
        mappingExample(argc, argv);
        return 0;
    }
    
    测试结果

    Advantages of mmap()

    1. 读取和写入内存映射文件避免了在使用read()或write()系统调用时发生的无关副本,在这种情况下,数据必须复制到和从用户空间缓冲区复制。
    2. 除了任何潜在的页面错误之外,读取和写入内存文件不会引起任何系统调用或上下文切换开销。它就像访问内存一样简单。
    3. 当多个进程将同一个对象映射到内存中,数据在所有进程之间共享。只读映射和共享可写映射是完全共享的;私有可写映射有copy-on-write。
    4. 在映射周围进行搜索涉及到琐碎的指针操作。不需要lseek()系统调用。

    Disadvantages of mmap()

    1. 内存映射始终是大小为整数的页数。因此,备份文件的大小与整数页数之间的差异被“浪费”为空闲空间。对于小文件, 很大一部分映射可能会被浪费掉。例如,对于4KB页,7字节映射浪费了4089字节。
    2. 内存映射必须适合进程的地址空间。具有32位地址空间,
      大量不同大小的映射会导致地址空间的分割,使得很难找到大的自由毗连区域。当然,这个问题在64位地址空间中就不那么明显了。
    3. 在内核内创建和维护内存映射和关联数据结构时存在开销。这种开销通常通过消除双重副本来消除,特别是大型和频繁访问的文件。

    由于这些原因,mmap()的好处在映射大文件时(因此,任何浪费的空间占映射总数的很小百分比),或者当mmap()的总大小时,最大程度地实现了mmap()的好处。 映射的文件可以被页面大小平分(因此不会浪费空间)。

    Resizing a Mapping

    Linux提供mremap()系统调用,用于扩展或缩小给定映射的大小。这个函数是linux特定的:

    #define _GNU_SOURCE
    #include <sys/mman.h>
    void *mremap(void *addr, size_t old_size, 
                size_t new_size, unsigned long flags);
    

    flag参数可以是0,也可以是MREMAP_MAYMOVE,它指定内核可以自由地移动映射(如果需要的话)以执行请求的调整大小。 如果内核可以移动映射,较大的调整大小更有可能导致失败。

    Return values and error codes

    成功后,mremap()返回指向新调整大小的内存映射的指针。失败时,它返回map_false并设置errno。

    Changing the Protection of a Mapping

    POSIX定义了mprotect()接口,以允许程序更改现有内存区域的权限:

    #include <sys/mman.h>
    int mprotect(const void *addr, size_t len, int port);
    

    成功后,mprotect()返回0。如果失败,它返回−1,并将设置errno。

    Synchronizing a File with a Mapping

    POSIX提供了与fsync()系统调用对应的内存映射。

    #include <sys/mman.h>
    int msync(void *arrd, size_t len, int flags);
    

    对msync()的调用会将对通过mmap()映射的文件所做的任何更改刷新回磁盘,从而使映射的文件与映射同步。
    如果不调用MSync(),就无法保证在文件unmapped之前将脏映射写回磁盘。
    flags参数控制同步操作的行为。它是按位或以下值:

    • MS_SYNC
      指定同步应该同步进行。在将所有页面写回磁盘之前,msync()调用不会返回。
    • MS_ASYNC指定同步应该异步发生。更新是按计划的,但是msync()调用会立即返回,而不会等待写操作的发生。
    • MS_INVALIDATE
      指定映射的所有其他缓存副本都为无效的。对此文件的任何映射的任何未来访问都将反映新同步的磁盘内容。也就是说其他进程对次文件的映射都要重新进行。
      成功后,msync()返回0。如果失败,调用将返回−1并设置errno。

    Giving Advice on a Mapping

    Linux提供了一个名为madvise()的系统调用,让进程向内核提供建议和提示,说明它们打算如何使用映射。

    #include <sys/mman.h>
    int madvise(void *addr, size_t len, int advice);
    

    如果len为0,内核将于从addr开始的整个映射采用建议。
    参数advice描述了建议,它可以是以下内容之一:

    • MADV_NORMAL
      应用程序没有关于这个内存范围的具体建议。它应该被视为正常。
      内核像往常一样运行,执行适量的readahead操作。
    • MADV_RANDOM
      应用程序打算在指定的范围内随机(非顺序)访问。
      内核禁用readahead,只读取每个物理读取操作的最小数据量。
    • MADV_SEQUENTIAL
      应用程序打算从较低到更高的地址依次访问指定范围内的页面。
      内核执行咄咄逼人的readahead。
    • MADV_WILLNEED
      应用程序打算。 在不久的将来访问指定范围内的页。
      内核启动readahead,将给定的页面读入内存。
    • MADV_DONTNEED
      应用程序不打算在不久的将来访问指定范围内的页面
      内核释放与给定页面相关的任何资源,并丢弃任何脏的和尚未同步的页面。对映射数据的后续访问将导致数据从备份文件或(用于匿名映射)零填充所请求的页面。
    • MADV_DONTFORK
      不会将这些页面复制到子进程中。此标志仅在Linux内核2.6.16及更高版本中可用,在管理DMA页面时是必需的,其他情况很少。
    • MADV_DOFORK
      撤销MADV_DONTFORK的行为
    //typical usage
    int ret;
    ret = madvise(addr, len, MADV_SEQUENTIAL);
    if(ret < 0)
          perror("madvise");
    

    成功后,madvise()返回0。如果失败,则返回−1,并适当设置errno。

    相关文章

      网友评论

          本文标题:Advanced File I/O Mapping Files

          本文链接:https://www.haomeiwen.com/subject/pxrgjctx.html