概述

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(如图)。

共享内存 VS. 其他IPC形式

用管道/消息队列传递数据

用共享内存传递数据

共享内存生成之后,传递数据并不需要再走Linux内核,共享内存允许两个或多个进程共享一个给定的存储区域,数据并不需要在多个进程之间进行复制,因此,共享内存的传输速度更快!

mmap内存映射

将文件/设备空间映射到共享内存区

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

参数:

addr:  要映射的起始地址, 通常指定为NULL, 让内核自动选择;

length: 映射到进程地址空间的字节数;

prot: 映射区保护方式(见下);

flags: 标志(见下);

fd: 文件描述符;

offset: 从文件头开始的偏移量;

prot

说明

PROT_READ

页面可读

PROT_WRITE

页面可写

PROC_EXEC

页面可执行

PROC_NONE

页面不可访问

flags

说明

MAP_SHARED

变动是共享的

MAP_PRIVATE

变动是私有的

MAP_FIXED

准确解释addr参数, 如果不指定该参数, 则会以4K大小的内存进行对齐

MAP_ANONYMOUS

建立匿名映射区, 不涉及文件

mmap返回值:

成功: 返回映射到的内存区的起始地址;

失败: 返回MAP_FAILED;

内存映射示意图:

(注意: 内存映射时, 是以页面(4K)作为单位)

/** 示例1: 写文件映射
将文件以可读,可写的方式映射到进程的地址空间, 然后向其中写入内容
**/
struct Student
{
    char name[4];
    int age;
};
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
    if (fd == -1)
        err_exit("file open error");

//为内存映射争取空间
    if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1)
        err_exit("lseek error");
    write(fd, "", 1);

    Student *p = (Student *)mmap(NULL, sizeof(Student)*5,
                                 PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 此时:操纵文件就可以如同操纵内存一样了
    char ch = 'a';
    for (int i = 0; i < 5; ++i)
    {
        memcpy((p+i)->name, &ch, 1);
        (p+i)->age = 20+i;
        ++ ch;
    }
    cout << "file initialized!" << endl;
    if (munmap(p, sizeof(Student)*5) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;

    return 0;
}
/**示例2: 读文件映射
**/
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    //以只读方式打开
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        err_exit("file open error");

    void *mmap(void *addr, size_t length, int prot, int flags,
               int fd, off_t offset);
    Student *p = (Student *)mmap(NULL, sizeof(Student)*5,
                                 PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 从内存中读取数据(其实是从文件中读取)
    for (int i = 0; i < 5; ++i)
    {
        cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl;
    }
    if (munmap(p, sizeof(Student)*5) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;

    return 0;
}

map注意点:

1. 内存映射不能(也不可能)改变文件的大小;

2. 可用于进程间通信的有效地址空间不完全受限于映射文件的大小, 而应该以内存页面的大小为准(见下面测试);

3. 文件一旦被映射之后, 所有对映射区域的访问实际上是对内存区域的访问; 映射区域内容写会文件时, 所写内容不能超过文件的大小.

/** 测试: 注意点2
文件以40K的内容进行创建, 而以120K的内容进行写回
**/
//程序1: 写文件映射
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
    if (fd == -1)
        err_exit("file open error");

    // 注意: 此处我们的文件其实只有40个字节
    if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1)
        err_exit("lseek error");
    write(fd, "", 1);

    // 但是我们却是以120个字节的方式进行映射
    Student *p = (Student *)mmap(NULL, sizeof(Student)*15,
                                 PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 以120个字节的方式进行写入
    char ch = 'a';
    for (int i = 0; i < 15; ++i)
    {
        memcpy((p+i)->name, &ch, 1);
        (p+i)->age = 20+i;
        ++ ch;
    }
    cout << "file initialized!" << endl;
    // 以120字节的方式卸载该内存区
    if (munmap(p, sizeof(Student)*15) == -1)
        err_exit("munmap error");

    // 注意: 要故意暂停一会儿, 以便让read程序读取该共享内存的内容
    sleep(20);
    cout << "process exit..." << endl;
}
//程序2: 读文件映射
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    //以只读方式打开
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        err_exit("file open error");

    void *mmap(void *addr, size_t length, int prot, int flags,
               int fd, off_t offset);
    // 以120字节的方式映射
    Student *p = (Student *)mmap(NULL, sizeof(Student)*15,
                                 PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 以120字节的方式读取
    for (int i = 0; i < 15; ++i)
    {
        cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl;
    }
    if (munmap(p, sizeof(Student)*15) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;
}

msync函数

int msync(void *addr, size_t length, int flags);

对映射的共享内存执行同步操作

参数:

addr: 内存起始地址;

length: 长度

flags: 选项

flags

说明

MS_ASYNC

执行异步写

MS_SYNC

执行同步写, 直到内核将数据真正写入磁盘之后才返回

MS_INVALIDATE

使高速缓存的数据失效

返回值:

成功: 返回0;

失败: 返回-1;

Linux IPC实践(8) --共享内存/内存映射的更多相关文章

  1. Linux IPC实践(10) --Posix共享内存

    1. 创建/获取一个共享内存 #include <sys/mman.h> #include <sys/stat.h> /* For mode constants */ #inc ...

  2. Linux IPC实践(1) -- 概述

    进程的同步与互斥 进程同步: 多个进程需要相互配合共同完成一项任务. 进程互斥: 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥;系统中某些 ...

  3. Linux IPC实践(9) --System V共享内存

    共享内存API #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int ...

  4. Linux IPC System V 共享内存

    模型 #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> ftok() //获取key值 s ...

  5. Linux IPC实践(13) --System V IPC综合实践

    实践:实现一个先进先出的共享内存shmfifo 使用消息队列即可实现消息的先进先出(FIFO), 但是使用共享内存实现消息的先进先出则更加快速; 我们首先完成C语言版本的shmfifo(基于过程调用) ...

  6. Linux IPC实践(11) --System V信号量(1)

    信号量API #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget ...

  7. Linux IPC实践(7) --Posix消息队列

    1. 创建/获取一个消息队列 #include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For m ...

  8. Linux IPC实践(4) --System V消息队列(1)

    消息队列概述 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法(仅局限于本机); 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值. 消息队列也有管道一样的不足:  ...

  9. Linux IPC实践(2) --匿名PIPE

    管道概念 管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个"管道", 管道的本质是固定大小的内核缓冲区; 如:ps aux | gre ...

随机推荐

  1. C++并发高级接口:std::async和std::future

    std::async和std::future std::async创建一个后台线程执行传递的任务,这个任务只要是callable object均可,然后返回一个std::future.future储存 ...

  2. Canvas实现3D效果-可旋转的立方体

    摘要:Canvas画布是一个二维平面,如何展示出3D效果?通过将三维空间中的Z轴抽取出来,将图像的点投影到与Z轴垂直的平面上,在通过旋转等变换效果,我们就能实现3D效果. 一.建立坐标系 1)立方体坐 ...

  3. Java集合框架总结

    java集合框架主要分为实现了Collection接口的List和Set.映射接口Map. |-- List 有序,元素都有索引,可重复. |-- Set 无序,不可以存储重复的元素. |-- Map ...

  4. dimens.xml详解

    本文联合两篇博文和自身理解写下 其中一篇:http://blog.csdn.net/hnzcdy/article/details/50628993 另一篇:暂不知原作者 Android中官方建议的屏幕 ...

  5. 预处理指令--C语言

    ANSI标准C还定义了如下几个宏: __LINE__ 表示正在编译的文件的行号 __FILE__ 表示正在编译的文件的名字 __DATE__ 表示编译时刻的日期字符串,例如:"25 Dec ...

  6. iOS学习笔记--数据存储

    iOS应用数据存储的常用方式 XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档(NSCoding) SQLite3 Core Data 1. XM ...

  7. Python实现八大排序算法(转载)+ 桶排序(原创)

    插入排序 核心思想 代码实现 希尔排序 核心思想 代码实现 冒泡排序 核心思想 代码实现 快速排序 核心思想 代码实现 直接选择排序 核心思想 代码实现 堆排序 核心思想 代码实现 归并排序 核心思想 ...

  8. FORM开发中Profiles的使用

    用户配置文件相当于系统参数,可以在不同层级(Site层.应用模块层.责任层.用户层)设置不同的值:作用范围小的覆盖范围大的层,系统已经预设了很多user profile; 开发人员也可以定义 在EBS ...

  9. React Native之ListView实现九宫格效果

    概述 在安卓原生开发中,ListView是很常用的一个列表控件,那么React Native(RN)如何实现该功能呢?我们来看一下ListView的源码 ListView是基于ScrollView扩展 ...

  10. JAVA面向对象-----抽象类

    1抽象类 为什么使用抽象类 1:定义Dog类 有颜色属性和叫的方法 2:定义Bird类 有颜色属性和叫的方法 3:定义其父类Animal 1:颜色的属性可以使用默认初始化值. 2:叫的方法在父类中如何 ...