乱花渐欲迷人眼——内存映射区

内存映射区的作用:将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

创建内存映射区

#include

void *mmap(void *addr,  size_t length,  int prot,  int flags,  int fd,  off_t offset);

 参数

addr : 映射区首地址,一般传NULL

length :映射区的大小,不能为0,一般写文件大小。

prot : 映射区权限,可以在man文档中查看。常用的主要有两个:

            PROT_READ--->必须指定的权限,因为内存映射区必须要有读权限

            PROT_WRITW---> 写权限   

 flags : 常用的主要有两个

            MAP_SHARED--->修改了内存数据同步到磁盘

            MAP_PRIVATE--->修改了内存数据不会同步到磁盘

fd :  要映射的文件的文件描述符

offset : 映射时候文件指针的偏移量,但必须是4k的整数倍,如0,4096,8192....

返回值

创建成功:返回映射区的首地址

创建失败:返回一个MAP_FAILED的宏,相当于 void*(-1), 同时errno被设置为一个合适的值   

 释放内存映射区  

                              int munmap(void* addr, size_t length);

  参数:        

  addr:映射区首地址,mmap()函数的返回值。

length:映射区长度

返回值:成功返回 0 ,失败返回 -1,错误原因存于errno中错误代码 EINVAL

 

注意事项:

  • 以下情况,mmap会调用失败:
  1. 第二个参数  length   为0
  2. 第三个参数未指定 PROT_READ
  3. 使用open()打开文件时指定的权限小于mmap()第三个参数  prot  指定的权限
  4. 最后一个参数  offset  不是4096的整数倍。
  • 在mmap()函数调用之后关闭文件描述符无影响
  • 对ptr (mmap()的返回值 )进行越界操作,可能会出现段错误,最好不要这样做

具体代码

        本例首先创建一个文件,往其中写入一些数据,为防止文件中没有数据导致在程序中计算文件大小时为 0 ,而 0 不能作为 mmap 的第二个参数,这点要注意。

       该代码实现父子进程间通过内存映射区进行通信

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc, char* argv[])
{
    int fd = open("test.txt",O_RDWR);
    int len = lseek(fd, 0, SEEK_END);
    void *ptr = mmap(NULL,len,PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    close(fd);
    
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    if(pid > 0)    //父进程写数据
    {
        strcpy(ptr, "this is a parent\n");
        wait(NULL);
    }
    else if(pid == 0)
    {
        printf("%s\n",(char*)ptr);    //子进程把父进程写入的数据打印出来
    }

    int ret = munmap(ptr, len);
    if(ret == -1 )
    {
        perror("munmap error");
        exit(1);
    }
    exit(0);
}

       从上面的例子可以察觉到,test.txt这个文件并不重要,即我们对用来进行映射的原文件的内容并不关心,所以我们有创建匿名映射区的方式,不需要具体文件来进行映射,即可实现父子进程间的通信。

此时由于我们不需要打开某个文件,所以只需要对原来的代码做以下事情:

  • 自己指定需要创建的映射区的大小
  • 在第四个参数中添加  MAP_ANON  宏
  • 把第五个参数文件描述的位置传入 -1

具体代码如下:

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc, char* argv[])
{
    int len = 4096;    //需要的内存映射区大小
    void *ptr = mmap(NULL,len,PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    if(pid > 0)    //父进程写数据
    {
        strcpy(ptr, "this is a parent\n");
        wait(NULL);
    }
    else if(pid == 0)
    {
        printf("%s\n",(char*)ptr);    //子进程把父进程写入的数据打印出来
    }

    int ret = munmap(ptr, len);
    if(ret == -1 )
    {
        perror("munmap error");
        exit(1);
    }
    exit(0);
}

通过匿名映射区的方式,我们也可以进行父子进程间通信。

那么我们能否通过内存映射区进行两个无关联的进程间通信呢?当然是可以的。

不过有以下需要注意的地方:

  • 不能使用匿名映射的方式,只能借助同一个磁盘文件创建共同的内存映射区
  • 由于内存映射区是非阻塞的,所以要格外注意文件的读写顺序和时间,或者用锁进行同步。

 

 

 

你可能感兴趣的:(Linux必知必会)