一、共享内存简介

共享内存区是最快的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. C++设计模式实现--模板(Template)模式

    一. 问题 在面向对象系统的分析与设计过程中常常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,可是逻辑(算法)的框架(或通用的应用算法)是同样的.Template提 ...

  2. RS错误RSV-VAL-0032之项目未在布局中引用的3种解决办法

    如下图所示,我用RS新建了一个空白页面,拖入了一个列表,给该列表新建了一个条件样式 条件样式如下所示,表达式来自查询1 运行,报错如下图所示 原因就是条件样式使用到了查询1中的数据项1但是数据项1在报 ...

  3. (数据挖掘-入门-3)基于用户的协同过滤之k近邻

    主要内容: 1.k近邻 2.python实现 1.什么是k近邻(KNN) 在入门-1中,简单地实现了基于用户协同过滤的最近邻算法,所谓最近邻,就是找到距离最近或最相似的用户,将他的物品推荐出来. 而这 ...

  4. 【C#】SQL数据库助手类1.0(自用)

    using System; using System.Collections.Generic; using System.Text; using System.Configuration; using ...

  5. 如何使用屏幕取色工具ColorPixl

    ColorPix可以屏幕取色,假如现在想要取色桌面徽标键的颜色,按任意键可以锁定这个区域(press any key to lock)这样我们就可以在放大的区域更清楚的取色,加号按钮可以设置该软件是否 ...

  6. Android 的一些提示框

    1.在测试时,如何实现一个提示 可以使用 Toast.makeText(this, "这是一个提示", Toast.LENGTH_SHORT).show(); //从资源文件str ...

  7. 简单概率dp-hdu-4487-Maximum Random Walk

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4487 题目大意: 开始位置在0,每一步可以向右向左或者不动,问走了n步后,路径中能到达最右的期望. ...

  8. MAC系统XAMPP 中 MySQL命令行client配置使用

    在PHP的学习过程中.MySQL预计是必定会接触的. MySQL的管理相信大家也会使用phpmyadmin: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv ...

  9. C#.Net中操作XML方法一

    我们知道XML是一种可标记性的语言,用来标记数据.定义数据类型,是一种执行用户对自己的标记语言进行定义的源语言.由于结构好.而且easy理解,就好比一棵树,层次关系分明,因此也经常把一些数据存储到XM ...

  10. Druid register mbean error

    key: [com.alibaba.druid.stat.DruidDataSourceStatManager.addDataSource(DruidDataSourceStatManager.jav ...