一、共享内存简介

共享内存区是最快的IPC形式,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

即每个进程地址空间都有一个共享存储器的映射区,当这块区域都映射到相同的真正的物理地址空间时,可以通过这块区域进行数据交换,例如共享库就是这么实现的,很多进程都会使用同一个函数如printf,也许在真正的物理地址空间中只存在一份printf.o
,然后所有进程都映射到这一份printf.o 就实现了共享。

用管道或者消息队列传递数据:

用共享内存传递数据:

即使用共享内存传递数据比用消息队列和管道来说,减少了进入内核的次数,提高了效率。

二、mmap 函数

#include <sys/mman.h>

功能:将文件或者设备空间映射到共享内存区。
原型 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
参数
addr: 要映射的起始地址,通常指定为NULL,让内核自动选择
len:映射到进程地址空间的字节数
prot:映射区保护方式
flags:标志
fd:文件描述符
offset:从文件头开始的偏移量,必须是页大小的整数倍(在32位体系统结构上通常是4K)
返回值:成功返回映射到的内存区的起始地址;失败返回-1

prot 参数取值:

PROT_EXEC 表示映射的这一段可执行,例如映射共享库

PROT_READ 表示映射的这一段可读

PROT_WRITE 表示映射的这一段可写

PROT_NONE 表示映射的这一段不可访问

flag参数有很多种取值,这里只讲两种,其它取值可查看mmap(2)

MAP_SHARED 多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。

MAP_PRIVATE 多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

内存映射文件示意图:

如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射:

功能:取消mmap函数建立的映射
原型 int munmap(void *addr, size_t len);
参数
addr: 映射的内存起始地址
len:映射到进程地址空间的字节数
返回值:成功返回0;失败返回-1

下面写两个程序测试一下:

mmap_write.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
 
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

typedef struct stu
{
    char name[4];
    int age;
} STU;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

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

lseek(fd, sizeof(STU) * 5 - 1, SEEK_SET);
    write(fd, "", 1);

STU *p;
    p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);

if (p == -1)
        ERR_EXIT("mmap");

char ch = 'a';
    int i;
    for (i = 0; i < 5; i++)
    {
        memcpy((p + i)->name, &ch, 1);
        (p + i)->age = 20 + i;
        ch++;
    }

printf("initialize over\n");

munmap(p, sizeof(STU) * 5);
    printf("exit...\n");
    return 0;
}

mmap_read.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

typedef struct stu
{
    char name[4];
    int age;
} STU;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

int fd;
    fd = open(argv[1], O_RDWR);
    if (fd == -1)
        ERR_EXIT("open");

STU *p;
    p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);

if (p == -1)
        ERR_EXIT("mmap");

int i;
    for (i = 0; i < 5; i++)
    {
        printf("name = %s age = %d\n", (p + i)->name, (p + i)->age);
    }
    munmap(p, sizeof(STU) * 5);
    printf("exit...\n");
    return 0;
}

先运行mmap_write ,然后用od -c 查看文件内容:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_write test 
initialize over
exit...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ od -c test 
0000000   a  \0  \0  \0 024  \0  \0  \0   b  \0  \0  \0 025  \0  \0  \0
0000020   c  \0  \0  \0 026  \0  \0  \0   d  \0  \0  \0 027  \0  \0  \0
0000040   e  \0  \0  \0 030  \0  \0  \0
0000050
注意od -c 输出的是八进制,024即20,即对内存的操作写入了文件。
再尝试运行mmap_read,输出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test 
name = a age = 20
name = b age = 21
name = c age = 22
name = d age = 23
name = e age = 24
exit...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$

再次将文件test 映射到内存,然后从内存读取到了文件的内容。

mmap 编程注意点:

1、映射不能改变文件的大小;
2、可用于进程间通信的有效地址空间不完全受限于被映射文件的大小;
3、文件一旦被映射后,所有对映射区域的访问实际上是对内存区域的访问。映射区域内容写回文件时,所写内容不能超过文件的大小;

对于1,3点,将mmap_write.c 中40行以后的代码中的5改成10,即映射的内存大于文件的大小,这样写入是不会出错的,因为是向内存写入,但用od 查看时发现文件还是40 个字节,即只有前5个STU才被真正写入到了文件。

对于第2点,将mmap_write.c 和 mmap_read.c 都按上面说的更改成10,然后在mmap_write.c 中munmap 函数之前sleep(10); 先运行mmap_write,再在另一终端运行mmap_read,观察结果:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test 
name = a age = 20
name = b age = 21
name = c age = 22
name = d age = 23
name = e age = 24
name = f age = 25
name = g age = 26
name = h age = 27
name = i age = 28
name = j age = 29
exit...

即在mmap_write
对映射内存区域写入之后尚未取消映射时,mmap_read 也映射了test
文件,两个虚拟进程地址空间的映射区域都指向了同一块物理内存,所以也能读到write 进程对内存的修改,但进程结束后查看test
文件,还是40个字节而已。内存的映射是以页面为单位的,一般为4k,所以才有第2条的说法,其实这才是真正体现共享内存可以进程间通信的所在。

最后一点,与write 类似,将文件映射到内存后对内存进行写入,不一定会马上写回文件,有可能内核也会产生一个缓冲区,找个适当的时间内核再写回设备文件,write 之后可以调用fsync 进行同步,同样地,mmap 可以调用msync 进行同步。

参考:

《linux c 编程一站式学习》

《UNP》

共享内存简介和mmap 函数的更多相关文章

  1. Linux环境编程之共享内存区(一):共享内存区简单介绍

    共享内存区是可用IPC形式中最快的.一旦内存区映射到共享它的进程的地址空间,进程间数据的传递就不再涉及内核.然而往该共享内存区存放信息或从中取走信息的进程间通常须要某种形式的同步.不再涉及内核是指:进 ...

  2. Linux下多任务间通信和同步-mmap共享内存

    Linux下多任务间通信和同步-mmap共享内存 嵌入式开发交流群280352802,欢迎加入! 1.简介 共享内存可以说是最有用的进程间通信方式.两个不用的进程共享内存的意思是:同一块物理内存被映射 ...

  3. mmap和shm共享内存的区别和联系

    共享内存的创建 根据理论: 1. 共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿 ...

  4. linux下共享内存mmap和DMA(直接访问内存)的使用 【转】

    转自:http://blog.chinaunix.net/uid-7374279-id-4413316.html 介绍Linux内存管理和内存映射的奥秘.同时讲述设备驱动程序是如何使用“直接内存访问” ...

  5. mmap映射区和shm共享内存的区别总结

    [转载]原文链接:https://blog.csdn.net/hj605635529/article/details/73163513 linux中的两种共享内存.一种是我们的IPC通信System ...

  6. mmap映射文件至内存( 实现 共享内存 与 文件的另类访问 )

    Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明: 头文件: < ...

  7. (转)mmap和shm共享内存的区别和联系

    共享内存的创建 根据理论: 1. 共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件 (特殊情况下还可以采用 ...

  8. 共享内存之——mmap内存映射

    共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件 (特殊情况下还可以采用匿名映射)机制实现,也可以通过sy ...

  9. 细说linux IPC(三):mmap系统调用共享内存

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途]         前面讲到socket的进程间通 ...

随机推荐

  1. EasyUI-datagrid-自动合并单元格

    1.目标 1.1表格初始化完成后,已经自动合并好需要合并的行: 1.2当点击字段排序后,重新进行合并: 2.实现 2.1 引入插件 /** * author ____′↘夏悸 * create dat ...

  2. 修改一行SQL代码 性能提升了100倍

    在PostgreSQL中修改了一行不明显的代码,把(ANY(ARRAY[...]) 改成 ANY(VALUES(...))),结果查询时间从20s变为0.2s.最初我们学习使用 EXPLAN ANAL ...

  3. WordPress 如何搜索文章内容而不搜索页面

    如何在WordPress 中只搜索指定的文章类型?在http://www.wpbeginner.com上了解到通过WP提供的钩子"pre_get_posts"方法可能实现 该钩子方 ...

  4. java 读取本地的json文件

    首先,要先去下载相关的jar包,否则你是无法做到的. 在百度或者谷歌里面输入java json  jar包下载就行了(共7个包). xom-1.1.jar ezmorph-1.0.6.jar json ...

  5. 【PHP 】伪静态 - 4. 实际运用

    伪静态的实际运用 1. 在一个项目中有两个文件夹,public和private, public文件夹的图片可以被所有人访问,private只能被自己访问.如何实现? 第一个方法是: 在public和p ...

  6. [Backbone]4. Model & View, toggle between Model and View. -- 1

    如上图所示: Server有Data都交给Models处理, 然后由Models给Views Data,让View去告诉DOM如何显示, 然后DOM显示HTML; View events update ...

  7. 以Settings.APPLICATION_DEVELOPMENT_SETTINGS打开开发人员面板出错总结

    近期遇到了一个问题,感觉须要记录一下. 要打开开发人员面板,之前的代码例如以下: Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVE ...

  8. Android 之数据传递小结

    Android开发中,在不同模块(如Activity)间经常会有各种各样的数据需要相互传递,常用的的有五种传递方式.它们各有利弊,有各自的应用场景.下面分别介绍一下: 1. Intent对象传递简单数 ...

  9. Field.setAccessible()方法

    http://blog.csdn.net/kjfcpua/article/details/8496911 java代码中,常常将一个类的成员变量置为private 在类的外面获取此类的私有成员变量的v ...

  10. 【C++系列小结】面向过程的编程风格

    前言 编程语言有面向过程和面向对象之分,因此编程风格也有所谓的面向过程的编程和面向对象的编程,并且语言的性质不会限制编程的风格. 这里主要说一以下向过程的编程. "面向过程"(Pr ...