【四】内存映射mmap


(1)概述
    ①作用:在调用进程的虚拟地址空间中创建一个新的内存映射。

    ②"文件映射"和"匿名映射"
        根据内存背后有无实体文件与之关联,映射可以分成以下两种:
        · 文件映射:内存映射区域有实体文件与之关联。 
            1.mmap 系统调用将普通文件的一部分内容直接映射到调用进程的虚拟地址空间。
            2.一旦完成映射,就可以通过在相应的内存区域中操作字节来访问文件内容。这种映射也被称为基于文件的映射。
        · 匿名映射:匿名映射没有对应的文件。这种映射的内存区域会被初始化成 0 。
            3.匿名映射没有对应的文件。这种映射的内存区域会被初始化成 0 。

    ③一个进程映射的内存可以与其他进程中映射的内存共享物理内存。
        所谓共享是指各个进程的页表条目指向 RAM 中的相同分页。    
        
         
        这种内存映射的共享,会在以下两种情况下发生:
            · 通过 fork ,子进程继承了父进程通过 mmap 映射的副本。
            · 多个进程通过 mmap 映射了同一个文件的同一个区域。    
            注意:
             1.虽然子进程拷贝了父进程的内存,但是父子进程的页表并不是始终都指向同一物理内存的,一旦父子进程中有一个尝试
               修改内存的内容时,内核就不得不发起写时复制,分配新的物理内存。从此父子进程分道扬镳,彼此再也看不到对方对内存的改动。
             2.对于进程 malloc 出来的内存,fork 之后父子进程并不是共享同一块映射。

    ④"私有映射"和"共享映射":
        私有映射( MAP_PRIVATE):
            1.在映射内容上发生的变更对其他进程不可见。
            2.对于文件映射而言,变更不会同步到底层文件中。
            3.对映射内容所做的变更是进程私有的。
            4.事实上,内核使用了写时复制技术来完成这个任务。未对映射内容进行修改操作时,页面仍然是共享的。
              一旦有进程试图修改其中一个分页的内容时,内核首先会为该进程创建一个新的分页,并将需要修改的分页中的内容拷贝到新分页中。
        共享映射( MAP_SHARED ):
            1.在映射内容上发生的所有变更,对所有共享同一个映射的其他进程都可见。
            2.对于文件映射而言,变更会同步到底层的文件中。
            3.很明显,共享映射是用于进程间通信的。

    ⑤内存映射的分类及用途
        内存映射根据有无文件关联,分成文件与匿名;根据映射是否在进程间共享,分成私有和共享。这两个维度两两组合,内存映射共分成 4 种类型,    



(2)内存映射的相关接口
    ①void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
        1.内存映射源: fd    offset length
            将 fd 对应的文件,从 offset 位置起,将长度为 length 的内容映射到进程的地址空间。
        2.对于文件映射: 调用 mmap 之前需要调用 open 取到对应文件的文件描述符。
        3.addr: 指定将文件对应的内容映射到进程地址空间的起始地址。一般来讲为了可移植性,该参数总是指定为 NULL ,
                表示交给    内核去选择合适的位置。
        4.prot: 设置对内存映射区域的保护

                   


        5.flags: 用于指定内存映射是共享映射还是私有映射,也用于指定内存映射是文件映射还是匿名映射。
                  
        6.注意:
          1.mmap 系统调用的操作单元是页。参数 addr 和 offset 都必须按页对齐,即必须是页面大小的整数倍。
          2.在 Linux 下,页面大小是 4096字节,该值可以通过 getconf 命令来获取到。
          3.获取页大小: 
            getconf PAGESIZE        // shell
            long sysconf(int name); // 程序
        7.返回值
            当 mmap 调用成功时,则返回映射区域的起始地址,如果失败,则返回 MAP_FAILED ,并置 errno 。
            
        8.误解:调用 mmap 时,真的已经把文件对应区域的内容读取到了内存的对应位置。事实上并非如此, 
                mmap 仅仅是建立了两者之间的关联。当第一次读取映射区的内容或修改映射区的内容时,会
                引发缺页中断( page fault ),这时候才会真正地将文件的内容加载到内存的对应位置。


    ②int munmap(void *addr, size_t length);    // 解除内存映射        
         addr: 是 mmap 返回的内存映射的起始地址    
         length: 是内存映射区域的大小。    
        注意:
          1.执行过 munmap 后,如果继续访问内存映射范围内的地址,那么进程会收到 SIGSEGV 信号,引发段错误。
          2.关闭对应文件的文件描述符并不会引发munmap 。    
          3.如果创建内存映射时 flags 中带上了 MAP_PRIVATE 标志位,那么解除该内存映射时,调用进程对内存映射的所有改动都会被丢弃。  

 
(3)共享文件映射
    ①共享文件的建立和使用
        1)打开文件,获取文件描述符 fd ,这一步是通过 open 来完成的。
            1.打开文件时设置的权限必须要和 mmap 系统调用需要的权限相匹配。
        2)将文件描述符作为 fd 参数,传给 mmap 函数。            
        注意:
            1.没有映射某区域却强行访问,会引发段错误,产生 SIGSEGV 信号。
            2.访问的映射地址虽然在 mmap 映射的内存区域之内,但并不在文件长度的范围以内,会导致 SIGBUS 信号的产生。
                比如: 如果内存映射在使用过程中,调用 truncate 或 ftruncate 将文件截断,那么访问文件真实长度之外的区域,就会触发 SIGBUS 信号
                
    ②共享文件映射的用途            
        操作文件:
            1.共享文件映射区域的内存,就等同于对文件的读写。
            2.对映射内容所做的修改,都会自动反应到文件上,内核会负责将修改最终同步到底层的块设备。    
            3.对于 read 和 write 接口而言,页高速缓存和用户空间缓冲区之间的数据传输是不可避免的。
               但是如果使用 mmap 来操作文件,则不需要这次复制。 mmap 对共享文件映射的操作,直接作用在页高速缓存上,节省了一次数据传输。
        进程间通信:
            1.进程的地址空间是彼此隔离的,一个进程一般不能直接访问另一个进程的地址空间。
            2.通过共享文件映射,两个进程的映射区域指向了同一个物理内存(页高速缓存),这就给进程间通信提供了可能,    
            3.如果两个进程的共享文件映射都源自同一个文件的同一个区域,那么一个进程对映射区域的修改,
               对于另外那个进程是立刻可见的,同时内核会负责在合适的时机将修改同步到底层文件。
               这是因为两个映射区域的对应分页都指向了同一个页高速缓存( Page Cache ),
            4.fcntl 函数提供了文件区域记录锁的功能,可以和mmap配合使用


(4)私有文件映射
    1.当调用 mmap 时,如果将 flags 设置成 MAP_PRIVATE 标志位,那么映射就是私有文件映射。
    2.最常见的情况就是前面提到的加载动态共享库,多个进程共享相同的文本段。
    3.文本段通常被保护成 PROT_READ|PROT_EXEC 。
    4.映射共享库时一般设置为: PROT_READ|PROT_EXEC, 为了防止被恶意程序篡改


(5)共享匿名映射
    ①创建方法    
        方法一: 调用 mmap 时,在参数 flags 中指定 MAP_ANONYMOUS 标志位,并且将参数 fd 指定为 -1 。
        方法二: 打开 /dev/zero 设备文件,并将得到的文件描述符 fd 传递给 mmap 。    
        // 不论采用哪种方式,得到的内存映射中的字节都会被初始化成 0 。

    ②作用:
        让相关进程共享一块内存区域。
        比如父进程创建一个共享匿名映射,然后 fork 创建子进程,这种情况下,父子进程就可以通过这块内存区域来通信。


(6)私有匿名映射
    ①创建方法
        创建匿名映射时,将flags 中设置为 MAP_PRIVATE 标志位,

    ②用途
        1.典型的用途是分配进程所需的内存。映射出来的内存并没有文件与之关联,对内存的操作也是私有的,不会影响到其他进程。
        2.比较典型的例子就是 glibc 中的 malloc 实现。当要分配的内存大于 MMAP_THREASHOLD 字节时, glibc 的 malloc 是使用 mmap 来实现的。
            //strace工具跟踪到 mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2a29f0c000

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值