zoukankan      html  css  js  c++  java
  • Linux IPC实践(8) --共享内存/内存映射

    概述

        共享内存区是最快的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;

  • 相关阅读:
    5道趣味Python热身题【新手必学】
    操作系统特征
    二叉树的中序遍历
    英语一图画作文模板
    函数
    双阶乘与华里士公式
    因式分解
    【】连通图——详细解释
    【】this指针——c++中的特殊指针
    咱们程序员好用的云笔记
  • 原文地址:https://www.cnblogs.com/itrena/p/5926959.html
Copyright © 2011-2022 走看看