43-初窥内存映射mmap

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35733751/article/details/82936965

1. 文件通信

如果你已经完成了进程间通信的学习,相信你对进程间通信有了一个初步的解了。由于进程之间是相互独立的,不能相互访问,进程之间想要通信必须借助内核空间,因为内核空间对于进程之间是共享的。

像这样的进程间通信的方式有很多种,例如管道、信号、共享内存、消息队列、套接字、命名管道等,在linux早期是使用文件来完成进程间通信的。

对于文件通信,本质上也是通过内核空间来完成的。首先系统内核会创建一个内核缓冲区,通过把文件映射到内核缓冲区里,然后进程将数据写入到缓冲区,另一进程从缓冲区将数据读走,完成进程间通信。

接下来要讲的内存映射和文件通信有很多相似之处。

 

2. mmap函数创建内存映射

内存映射是让一个磁盘文件与进程空间中的一块虚拟内存区域映射,完成映射后,进程就可以通过在内存映射区中读写操作来访问文件中的数据,换句话说,你可以理解为对这块内存的读写数据等同于读写文件。

 

mmap函数就是用于完成内存映射的

<sys/mman.h>
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 

返回值说明:成功返回创建的映射区void *类型的首地址;失败返回MAP_FAILED宏(这个宏其实就是个void *类型的-1,即(void *)-1 )。

 

参数说明:

addr : 建立内存映射区的首地址,如果指定addr为NULL,那么将由Linux内核自动指定一个合适的首地址。

length: 创建映射区的大小,以字节为单位(建议length最好是系统内存页的整数倍)。

prot: 内存映射区的保护权限,可以多个选项组合使用,例如PROT_READ | PROT_WRITE表示可读写。

                     值                                  描述
              PROT_EXEC                内存映射区域可以被执行
              PROT_READ                内存映射区域可以被读取
             PROT_WRITE                 内存映射区域可以被写入
             PROT_NONE                  内存映射区域不可访问

 

flags:用于控制内存映射区操作的选项。

如果flags = MAP_PRIVATE,创建一个私有的映射区,在映射区所做的修改操作不会反映到物理磁盘的文件上,对使用同一映射区的其他进程不可见。

如果flags = MAP_SHARED,创建一个共享的映射区,在映射区所做的修改操作会直接反映到物理磁盘的文件上,那么这一修改操作对其他进程都可见。

fd:用来创建内存映射区的文件描述符

offset:从文件的哪个位置开始映射,如果offset为0表示从文件头开始映射,注意offset必须是系统分页4k的整数倍,数据类型为off_t,在32位系统下为long int类型,64为linux系统为long long int类型。

 

3. mmap函数映射过程

                                                   图1-内存映射文件过程(图片来自Linux/UNIX系统编程手册)

length是映射到进程地址空间的字节数,offset表示文件的映射位置从文件头到第offset个字节开始,通常offset设置为0表示从文件头开始映射,当映射成功,mmap将会返回映射区域的首地址。 

 

4. munmap函数

mmap函数是用于解除内存映射,即从进程的虚拟地址空间删除映射。

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

返回值说明:成功返回0,失败返回-1

参数addr:由mmap返回的内存映射区首地址

参数length:映射区大小

 

5. 内存映射示例程序

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>

int main(void){

        char *addr;
        int len = 0;
        int ret;

        int fd = open("test.txt", O_RDWR|O_CREAT, 0644);
        if (fd < 0){
                perror("open error");
        }

        //拓展文件大小4096
        lseek(fd , 4096, SEEK_SET);
        write(fd , "0" , 1);

        //指向文件开头
        lseek(fd , 0 , SEEK_SET);

        //创建共享映射区,可读写
        addr = (char *)mmap(NULL , 4096 , PROT_WRITE|PROT_READ , MAP_SHARED , fd , 0);

        //创建私有映射区,可读写
        //addr = (char *)mmap(NULL, 4096, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);

        if (addr == MAP_FAILED){
                perror("mmap err: ");
        }

        //关闭文件与文件描述符间的关联
        close(fd);

        //实际操作是通过mmap返回的文件指针去操作文件读写,不需要用到文件描述符
        strcpy(addr, "AAAAABBBBB");
        printf("%s\n", addr);

        //解除映射
        ret = munmap(addr , 4096);
        if(ret < 0){
                perror("munmap error: ");
        }

        return 0;
}

在test文件写入hello world:

[root@localhost memory]# cat test.txt 
hello world
[root@localhost memory]# 

指定映射区为MAP_PRIVATE,程序执行结果:

指定MAP_PRIVATE创建私有映射区,对映射区所做的修改操作不会反映到物理磁盘上的文件。

 

指定映射区为MAP_SHARED:

指定MAP_SHARED创建共享映射区,对映射区所做的修改操作会反映到物理磁盘上的文件。

6. munmap函数使用细节

1. 如果参数addr和length指定的区域不存在映射关系,那么调用munmap函数将不会发生任何事情并返回0(表示成功)。

2. 当一个进程终止或调用了exec系列函数后,进程中所有的映射关系将自动解除。

3. 为确保映射区的数据写入物理磁盘上的文件中,在调用munmap解除映射前需要调用msync函数。

4. munmap函数可以解除映射区中的部分区域的映射,一旦这样做的话可能会使原来的映射区变小或分成两个映射区,强烈建议不要这么做。

猜你喜欢

转载自blog.csdn.net/qq_35733751/article/details/82936965