共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件 (特殊情况下还可以采用匿名映射)机制实现,也可以通过systemV共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。 这一篇详解mmap内存文件映射原理及其案例,system V共享内存 以及他们的区别将在后面的随笔中讨论。


非原创,内容源于互联网

mmap内存文件映射

一、传统文件访问

unix访问文件的传统方法使用open打开他们,如果有多个进程访问一个文件,则每一个进程在再记得地址空间都包含有该文件的副本,这不必要地浪费了存储空间。下面说明了两个进程同时读一个文件的同一页的情形,系统要将该页从磁盘读到高速缓冲区中,每个进程再执行一个内存期内的复制操作将数据从高速缓冲区读到自己的地址空间。

二、共享内存映射

现在考虑林一种处理方法:进程A和进程B都将该页映射到自己的地址空间,当进程A第一次访问该页中的数据时,它生成一个缺页终端,内核此时读入这一页到内存并更新页表使之指向它,以后,当进程B访问同一页面而出现缺页中断时,该页已经在内存,内核只需要将进程B的页表登记项指向次页即可。

三、mmap及其相关系统调用

mmap()系统调用使得进城之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write

等操作。

mmap()系统调用形式如下:

#include<sys/mman.h>

void mmap(void *addr, size_t len, int prot,int flags, int fildes, off_t off)
int msync(void *addr, size_t len, int flags);
int munmap(void *addr, size_t len);

mmap的作用是映射文件描述符和指定文件的(off_t off)区域至调用进程的(addr,addr *len)的内存区域,如下图所示:

参数:

  fd:为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间进行通信)。

  len:是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。

  prot:指定空想内存的访问权限。可取如下几个值的或:PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)、PROT_NONE(不可访问)。

  flag:由以下几个常值指定:MAP_SHARED、MAP_PRIVATE、MAP_FIXED,其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。

  offset:一般设为0,表示从文件头开始映射。

  addr:指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。

函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。

四、mmap基础用例

 //测试文件 data.txt 后面的程序也要用到
aaaaaaaaa
bbbbbbbbb
cccccccccccc
ddddddddd

1、通过共享内存映射的方式修改文件

 #include <sys/mman.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<error.h> int main(int argc, char * argv[])
{
int fd, nread;
struct stat sb;
char *mapped; //打开文件
if((fd = open(argv[], O_RDWR)) < ){
perror("open") ;
} //获取文件的属性
if((fstat(fd, &sb)) == - ){
perror("fstat") ;
} //将文件映射至进程的地址空间
if((mapped = mmap(NULL, sb.st_size, PROT_READ|\
PROT_WRITE, MAP_SHARED, fd, o)) ==(void*) -){
perror("mmap") ;
} //修改一个字符,同步到磁盘文件
mapped[] = '';
if((msync((void *)mapped, sb.st_size, MS_SYNC)) == -){
perror("msync") ; //释放存储映射区
if((munmap((void *)mapped,sb.st_size)) == -){
perror("munmap");
} return ;
}

2 私有映射无法修改文件

 //将文件私有映射到进程的地址空间
if((mapped = (char *)mmap(NULL,sb.st_size,PROT_READ|
PROT_WRITE, MAP_PRIVATE, fd, ))==(void *)-){
perror("mmap");

五、使用共享内存映射实现两个进程之间的通信

两个程序映射到同一个文件到自己的地址空间,进程A先运行,每个两秒读取映射区域,看是否发生变化,进程B后运行,它修改映射区域,然后退出,此时进程A能够观察到存储映射区的变化

进程A的代码:

 #include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h> int main(int argc, char **argv)
{
int fd, nread;
struct stat sb;
char *mapped; /* 打开文件 */
if ((fd = open(argv[], O_RDWR)) < ) {
perror("open");
} /* 获取文件的属性 */
if ((fstat(fd, &sb)) == -) {
perror("fstat");
} /* 将文件映射至进程的地址空间 */
if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, )) == (void *)-) {
perror("mmap");
} /* 文件已在内存, 关闭文件也可以操纵内存 */
close(fd); /* 每隔两秒查看存储映射区是否被修改 */
while () {
printf("%s\n", mapped);
sleep();
} return ;
}

进程B的代码

   #include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h> int main(int argc, char **argv)
{
int fd;
struct stat sb;
char *mapped; /* 打开文件 */
if ((fd = open(argv[], O_RDWR)) < ) {
perror("open");
} /* 获取文件的属性 */
if ((fstat(fd, &sb)) == -) {
perror("fstat");
}
/* 私有文件映射将无法修改文件 */
if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ
|PROT_WRITE,MAP_PRIVATE, fd, )) == (void*)-) {
perror("mmap");
} /* 映射完后, 关闭文件也可以操纵内存 */
close(fd); /* 修改一个字符 */
mapped[] = ''; return ;
}

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

 #include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> #define BUF_SIZE 100 int main(int argc, char** argv)
{
char *p_map; /* 匿名映射,创建一块内存供父子进程通信 */
p_map = (char *)mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -, ); if(fork() == ) {
sleep();
printf("child got a message: %s\n", p_map);
sprintf(p_map, "%s", "hi, dad, this is son");
munmap(p_map, BUF_SIZE); //实际上,进程终止时,会自动解除映射。
exit();
} sprintf(p_map, "%s", "hi, this is father");
sleep();
printf("parent got a message: %s\n", p_map); return ;
}

七、对mmap()返回地址的访问

linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:

总结一下就是,文件大小,mmap()的参数len都不能决定进程能访问的大小,而是容纳文件被映射部分的最小页面数决定进程能访问的大小,下面看一个实例:

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h> int main(int argc, char** argv)
{
int fd,i;
int pagesize,offset;
char *p_map;
struct stat sb; /* 取得page size */
pagesize = sysconf(_SC_PAGESIZE);
printf("pagesize is %d\n",pagesize); /* 打开文件 */
fd = open(argv[], O_RDWR, );
fstat(fd, &sb);
printf("file size is %zd\n", (size_t)sb.st_size); offset = ;
p_map = (char *)mmap(NULL, pagesize * , PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
close(fd); p_map[sb.st_size] = ''; /* 导致总线错误 */
p_map[pagesize] = ''; /* 导致段错误 */ munmap(p_map, pagesize * ); return ;
}

共享内存之——mmap内存映射的更多相关文章

  1. Python之mmap内存映射模块(大文本处理)说明

    背景: 通常在UNIX下面处理文本文件的方法是sed.awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力.关于sed的说明可以看了解sed的工作原理,本文将介绍通过 ...

  2. linux mmap 内存映射【转】

    转自:http://blog.csdn.net/xyyangkun/article/details/7830313 [-] mmap vs readwritelseek mmap vs malloc ...

  3. 【转】Python之mmap内存映射模块(大文本处理)说明

    [转]Python之mmap内存映射模块(大文本处理)说明 背景: 通常在UNIX下面处理文本文件的方法是sed.awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力 ...

  4. [转载]Linux驱动mmap内存映射

    原文地址:https://www.cnblogs.com/wanghuaijun/p/7624564.html mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法 ...

  5. Linux驱动mmap内存映射

    mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED).LCD控制器.磁盘控制器,实际上就是往设备的物理地址读写数据. ...

  6. mmap内存映射

    http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...

  7. Android mmap 文件映射到内存介绍

    本文链接: Android mmap 文件映射到内存介绍 Android开发中,我们可能需要记录一些文件.例如记录log文件.如果使用流来写文件,频繁操作文件io可能会引起性能问题. 为了降低写文件的 ...

  8. linux mmap 内存映射

    mmap() vs read()/write()/lseek() 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进程空间), ...

  9. sendfile“零拷贝”和mmap内存映射

    在学习sendfille之前,我们先来了解一下浏览器访问页面时,后台服务器的大致工作流程. 下图是从用户访问某个页面到页面的显示这几秒钟的时间当中,在后台的整个工作过程. 如上图,黑色箭头所示的过程, ...

随机推荐

  1. cocos2dx for lua A*寻路算法实现2

    关于A*算法的实现过程,简单来说就是一个计算权限的过程. 首先,创建一个地图节点类,"MapNode.lua" local MapNode = class("MapNod ...

  2. LLDB详解

    LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功倍 L ...

  3. C#:CodeSmith根据数据库中的表创建C#数据模型Model + 因为没有钱买正版,所以附加自己写的小代码

    对于C#面向对象的思想,我们习惯于将数据库中的表创建对应的数据模型: 但假如数据表很多时,我们手动增加模型类会显得很浪费时间: 这个时候有些人会用微软提供的EntityFrameWork,这个框架很强 ...

  4. Codeforces Round #513 (rated, Div. 1 + Div. 2)

    前记 眼看他起高楼:眼看他宴宾客:眼看他楼坍了. 比赛历程 开考前一分钟还在慌里慌张地订正上午考试题目. “诶这个数位dp哪里见了鬼了???”瞥了眼时间,无奈而迅速地关去所有其他窗口,临时打了一个缺省 ...

  5. matplotlib绘图股票走势图实践

    导入模块 import pandas as pdimport numpy as npfrom pandas import Series,DataFrameimport matplotlib.pyplo ...

  6. MySQL的GTID复制与传统复制的相互切换

    MySQL的GTID复制与传统复制的相互转换 1. GTID复制转换成传统复制 1.1 环境准备 1.2 停止slave 1.3 查看当前主从状态 1.4 change master 1.5 启动主从 ...

  7. 详解wordpress如何把文件保存到阿里云OSS上!

    自己搞了一个Wordpress的博客,装完之后一直晾着没管,最近闲来开荒.为了减小服务器的带宽.存储.CUP的压力,决定把博客中的所有文件都保存到阿里云OSS上面. 关于这个问题,自己去调用OSS的S ...

  8. 将php数组转js数组,js如何接收PHP数组,json的用法

    首先下载下面这个文件(这是一段是别人写出来专门解析json的代码),然后引入这个文件! http://pan.baidu.com/s/1dD8qVr7 现在当我们需要用ajax与后台进行交互时,怎样将 ...

  9. python爬虫基础15-python图像处理,PIL库

    Python图像处理-Pillow 简介 Python传统的图像处理库PIL(Python Imaging Library ),可以说基本上是Python处理图像的标准库,功能强大,使用简单. 但是由 ...

  10. LeetCode(123) Best Time to Buy and Sell Stock III

    题目 Say you have an array for which the ith element is the price of a given stock on day i. Design an ...