IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Linux 下进程间通讯之内存映射详解 -> 正文阅读

[C++知识库]Linux 下进程间通讯之内存映射详解

引用:前面的系列文章介绍管道,本文介绍另外一种比较高效的进程间通讯方式——内存映射。

一、内存映射概述

内存映射(Memory-mapped I/O)使得一个磁盘文件与存储空间中的一个缓冲区相映射,相当于将磁盘文件的数据映射到内存中,用户通过修改内存就能修改磁盘文件

在这里插入图片描述

于是当从缓冲区中取数据,就相当于读文件中的相应字节。以此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不使用 read 和 write 函数的情况下,使用地址(指针)完成 I/O 操作(通过内存操作函数完成I/O操作)。

内存映射也是进程间通讯的一种方式,而且效率比较高,因为它相当于直接对内存进行操作。其原理是把磁盘文件中的数据映射到内存当中,映射之后返回映射地址,在程序中就可以直接操作这块内存,操作过程中会把数据同步到磁盘文件中,这样可以实现进程间通讯。

二、内存映射 API

mmap 函数

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:一个文件或者其它对象映射进内存中
参数:
    addr :  指定映射的起始地址, 通常设为NULL, 由系统指定。
       	【补充】如果 addr 为 NULL,则内核会自行挑选一个页对齐的地址;
        	   如果 addr 不为 NULL ,则内核只是将其作为一个提示。
    length:映射到内存的文件长度,这个值不能为 0(即文件大小 > 0);建议直接使用文件的长度。
        【补充】获取文件长度可通过 stat()、lseek() 等函数
    prot:  映射区的保护方式(【注意】要操作映射内存,必须要有读的权限):
        a) 读:PROT_READ
        b) 写:PROT_WRITE
        c) 读写:PROT_READ | PROT_WRITE
    flags:  映射区的特性, 可以是
        a) MAP_SHARED : 写入映射区的数据会复制回文件, 即映射区的数据会自动和磁盘文件同步;
			且允许其他映射该文件的进程共享,所以进程间通信,必须要设置这个选项。
        b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write),
			对此区域所做的修改不会写回原文件,即映射区的数据会自动和磁盘文件不同步。
    fd:由 open() 返回的文件描述符, 代表要映射的文件。注意点如下:
        a) 文件的大小不能为 0;     
        b) open() 指定的权限不能和 prot 参数冲突(即映射区的权限 <= 文件打开的权限):
                prot: PROT_READ                	open:只读/读写 
                prot: PROT_READ | PROT_WRITE   	open:读写
    offset:以文件开始处的偏移量, 必须是4k的整数倍;
                一般不用,所以通常为0, 表示从文件头开始映射(4k是页大小)
返回值:
    成功:返回创建的映射区首地址
    失败:MAP_FAILED宏

munmap 函数

#include <sys/mman.h>
int munmap(void *addr, size_t length);
功能:释放内存映射区
参数:
    addr:使用 mmap 函数创建的映射区的首地址
    length:映射区的大小,即要释放的内存的大小,要和mmap函数中的length参数的值一样。
返回值:
    成功:0
    失败:-1

API 使用注意事项

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作。

  2. 当 MAP_SHARED 时,要求:映射区的权限 <= 文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为 mmap 中的权限是对内存的限制。

  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。

  4. 特别注意:

    当映射文件大小为0时,不能创建映射区。所以用于映射的文件必须要有实际大小;

    mmap 函数使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

  5. munmap 函数传入的地址一定是 mmap 的返回地址;所以对于mmap 函数的返回值,建议不要对该指针进行 ++ 操作。如果确实需要这样做,需要保存 ++ 前的地址,这样在释放空间的时候,传入 ++ 前的地址才是正确释放空间。

  6. 文件偏移量必须为 4K 的整数倍,如果不是 4k 的整数倍,则函数调用出错,返回MAP_FAILED。

  7. mmap 函数创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

三、内存映射使用场景

内存映射实现进程间通信

(1)有关系的进程间通信

内存映射实现父子进程间通信

  1. 准备一个大小不是 0 的磁盘文件
  2. 还没有子进程的时候,通过唯一的父进程,先创建内存映射区
  3. 有了内存映射区以后,创建子进程
  4. 父子进程共享创建的内存映射区
  5. 【注意】内存映射区通信,是非阻塞。

参考示例:创建一个 test.txt 文件,并保证该文件大小大于 0。

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
int main() 
{
    // 1.打开一个文件
	int fd = open("test.txt", O_RDWR);// 打开一个文件
    int len = lseek(fd, 0, SEEK_END);//获取文件大小

    // 2.创建内存映射区
    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 == 0) //子进程
    {
        sleep(1); //保证父进程先执行

        // 读数据
        printf("%s\n", (char*)ptr);
    }
    else if (pid > 0) //父进程
    {
        // 写数据
        strcpy((char*)ptr, "i am u father!!");
        // 回收子进程资源
        wait(NULL);
    }

    // 释放内存映射区
    int ret = munmap(ptr, len);
    if (ret == -1)
    {
        perror("munmap error");
        exit(-1);
    }
    
    // 关闭文件
    close(fd);
    
    return 0;
}

在这里插入图片描述

运行结果:

yxm@192:~$ gcc test.c -o test
yxm@192:~$ ./test
i am u father!!
yxm@192:~$ 

(2)没有关系的进程间通信

内存映射实现不同进程间通讯

  1. 准备一个大小不是 0 的磁盘文件
  2. 进程 1 通过磁盘文件创建内存映射区,得到一个操作这块内存的指针
  3. 进程 2 通过磁盘文件创建内存映射区,得到一个操作这块内存的指针。【注意】进程 1 与进程 2 是通过同一磁盘文件创建内存映射区的。
  4. 使用内存映射区通信
  5. 【注意】内存映射区通信,是非阻塞。

参考示例:创建一个 test.txt 文件,并保证该文件大小大于 0。

// write.c
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>

int main(void)
{
    int fd = -1;
    int ret = -1;
    pid_t pid = -1;
    void *addr = NULL;
    
    // 1 以读写的方式打开一个文件
    fd = open("test.txt", O_RDWR);
    if(-1 == fd)
    {
        perror("open");
        return 1;
    }
    int len = lseek(fd, 0, SEEK_END);//获取文件大小
    
    // 2 将文件映射到内存
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr  == MAP_FAILED)
    {
        perror("mmap");
        return 1;
    }
    printf("文件存储映射ok.....\n");
    
    // 3 关闭文件
    close(fd);
    
	// 4 写入到存储映射区
    memcpy(addr, "1234567890", 10);  
    
    // 5断开存储映射
    munmap(addr, 1024);
    
    return 0;
}
// read.c
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>

int main(void)
{
    int fd = -1;
    int ret = -1;
    pid_t pid = -1;
    void *addr = NULL;
    
    // 1 以读写的方式打开一个文件
    fd = open("test.txt", O_RDWR);
    if(-1 == fd)
    {
        perror("open");
        return 1;
    }
    int len = lseek(fd, 0, SEEK_END);//获取文件大小
    
    // 2 将文件映射到内存
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr  == MAP_FAILED)
    {
        perror("mmap");
        return 1;
    }
    printf("文件存储映射ok.....\n");
    
    // 3 关闭文件
    close(fd);
    
	// 4 读存储映射区数据
   printf("addr:%s\n", (char*)addr);
    
    // 5断开存储映射
    munmap(addr, 1024);
    
    return 0;
}

运行结果:

yxm@192:~$ gcc write.c -o write
yxm@192:~$ gcc read.c -o read
yxm@192:~$ ./write 
文件存储映射ok.....
yxm@192:~$ ./read
文件存储映射ok.....
addr:1234567890
yxm@192:~$ 

匿名映射实现父子进程通信

通过使用我们发现,使用内存映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个大小不为 0 的文件才能实现。

通常为了建立映射区要 open 一个 temp 文件,创建好了再 unlink、close 掉,比较麻烦。其实 Linux 系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区,这样可以直接使用匿名映射来代替前面提到的内存映射,【注意】匿名映射只能用于具有血缘关系的进程间通讯 。

匿名映射同样需要借助标志位参数 flags 来指定,使用 MAP_ANONYMOUS 或 MAP_ANON(MAP_ANON 已经被废弃) 特性即可实现。

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
  • 4 是随意举例,该位置表示映射区大小,可依实际需要填写。
  • MAP_ANONYMOUS 和 MAP_ANON 这两个宏是Linux操作系统特有的宏。

程序示例:

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

int main() 
{	
	// 创建匿名内存映射区
    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 > 0) //父进程
    {
        // 写数据
        strcpy((char*)ptr, "hello mike!!");
        // 回收
        wait(NULL);
    }
    else if (pid == 0)//子进程
    {
        sleep(1);	//保证父进程先执行
        // 读数据
        printf("%s\n", (char*)ptr);
    }

    // 释放内存映射区
    int ret = munmap(ptr, len);
    if (ret == -1)
    {
        perror("munmap error");
        exit(-1);
    }
    
   	return 0;
}

运行结果:

yxm@192:~$ gcc test.c -o test
yxm@192:~$ ./test
hello, world

内存映射的方式操作文件

共享内存除了可以实现进程间通讯外,还可以实现文件操作。不过,很少有人使用内存映射的方式操作文件,此处只简单举例说明:

// 使用内存映射实现文件拷贝的功能
/*
    思路:
        1.对原始的文件进行内存映射
        2.创建一个新文件(拓展该文件)
        3.把新文件的数据映射到内存中
        4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
        5.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() 
{

    // 1.对原始的文件进行内存映射
    int fd = open("english.txt", O_RDWR);
    if(fd == -1) 
    {
        perror("open");
        exit(0);
    }

    // 获取原始文件的大小
    int len = lseek(fd, 0, SEEK_END);

    // 2.创建一个新文件(拓展该文件)
    int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) 
    {
        perror("open");
        exit(0);
    }
    
    // 对新创建的文件进行拓展
    truncate("cpy.txt", len);
    write(fd1, " ", 1);

    // 3.分别做内存映射
    void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);

    if(ptr == MAP_FAILED) 
    {
        perror("mmap");
        exit(0);
    }

    if(ptr1 == MAP_FAILED) 
    {
        perror("mmap");
        exit(0);
    }

    // 内存拷贝
    memcpy(ptr1, ptr, len);
    
    // 释放资源
    munmap(ptr1, len);
    munmap(ptr, len);

    close(fd1);
    close(fd);

    return 0;
}
yxm@192:~$ gcc test.c -o test
yxm@192:~$ ./test 
yxm@192:~$ ls -l
total 332
-rw-rw-r--  1 yxm yxm 129772 Sep  6 02:45 cpy.txt
-rw-rw-r--  1 yxm yxm 129772 Sep  6 02:44 english.txt
.....
-rwxrwxr-x  1 yxm yxm   9016 Sep  6 02:45 test
-rw-rw-r--  1 yxm yxm   1546 Sep  6 02:44 test.c

四、内存映射注意事项

  • 如果 open 时模式为 O_RDONLY,mmap 时 prot 参数指定 PROT_READ | PROT_WRITE 会怎样?

    此时调用 mmap() 函数会出错:返回MAP_FAILED。

    如果想要函数调用正常,open() 函数中的权限需要和 prot 参数的权限保持一致,权限不能和 prot 参数冲突。

  • mmap 什么情况下会调用失败?

    • 第二个参数:length = 0
    • 第三个参数:prot 参数只指定了写权限
    • 第五个参数:fd 参数,open 指定的权限和 prot 参数有冲突。
  • 可以open 的时候 O_CREAT 一个新文件来创建映射区吗?

    可以的,但是创建的文件的大小如果为0的话,肯定不行,此时可以使用 lseek() truncate()函数对新的文件进行扩展。

  • mmap 后关闭文件描述符,对 mmap 映射有没有影响?
    int fd = open(“XXX”);
    mmap(,fd,0);
    close(fd);

    映射区还存在,创建映射区的fd被关闭,没有任何影响。

  • 如果文件偏移量为 1000 会怎样?
    偏移量必须是 4K 的整数倍,如果不是,则会报错并返回 MAP_FAILED

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:47:57  更:2022-09-15 01:50:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:57:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码