一.概述                                                  

内存映射是在调用进程的虚拟地址空间创建一个新的内存映射。

内存映射分为2种:

1.文件映射:将一个普通文件的全部或者一部分映射到进程的虚拟内存中。映射后,进程就可以直接在对应的内存区域操作文件内容!

2.匿名映射:匿名映射没有对应的文件或者对应的文件是虚拟文件(如:/dev/zero),映射后会把内存分页全部初始化为0。

当多个进程映射了同一个内存区域时,它们会共享物理内存的相同分页。通过fork()创建的子进程也会继承父进程的映射副本!!!

如果多个进程都会同一个内存区域操作时,会根据映射的特性,会有不同的行为。映射特征可分为私有映射和共享映射:

1.私有映射:映射的内容对其他进程不可见。对于文件映射来说,某一个进程在映射内存中改变文件的内容不会反映到被映射的底层文件中。内核会使用copy-on-write(写时复制)技术来解决这个问题:只要有一个进程修改了分页中的内容,内核会为该进程重新创建一个新的分页,并将需要修改的内容复制到新分页中。

2.共享映射:某一个进程对共享的内存区域操作都对其他进程可见!!!对于文件映射,操作的内容会反映到底层文件中。

注意:进程执行exec()调用后,先前的内存映射会丢失,而fork()创建的子进程会继承父进程的,映射的特征(私有和共享)也会被继承。

异常信号:

1.当映射内存的属性设置只读时,如果进行写操作会产生SIGSEGV信号。

2.当映射内存的字节数大于被映射文件的大小,且大于该文件当前的内存分页大小时。如果访问的区域超过了该文件分页大小,会产生SIGBUS信号。

有点绕口,举个简单的例子:假设内核维护的内存分页是4k(一般都是4k,4096字节),一个普通文件a.txt的大小是10字节。如果创建一个映射内存为4097字节,并映射该文件。此时,因为a.txt的大小用一个分页就可以完全映射,10字节远小于一个分页的4096字节,所以内核只会给它一个分页。内存地址是从0开始,0-9区间的内容对应a.txt文件的数据,我们也是可以访问10-4095的区间。但如果访问4096区间时,已经超过一个分页的大小了,此时会产生SIGBUS信号!!!

等会我们用个简单的例子演示下这2个异常。

二.函数接口                                            

1.创建映射

 #include <sys/mman.h>

 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:映射后要存放的虚拟内存地址。如果是NULL,内核会自动帮你选择。

length:映射内存的字节数。

prot:权限保护:PROT_NONE(无法访问),PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行)。

flags:映射特征:MAP_PRIVATE(私有),MAP_SHARED(共享),MAP_ANONYMOUS。还有一些其他的可查询man手册。

fd:要映射的文件描述符。

offset:文件的偏移量,如果为0,且length为文件长度,代表映射整个文件。

2.解除映射

 #include <sys/mman.h>

 int munmap(void *addr, size_t length);

addr:要解除内存的起始地址。如果addr不在刚刚映射区域的开始位置,解除一部分后内存区域可能会分成两半!!!

length:要解除的字节数。

3.同步映射区

 #include <sys/mman.h>

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

addr:要同步的内存起始地址。

length:要同步的字节长度。

flags:MS_SYNC(执行同步文件写入),此操作内核会把内容直接写到磁盘。MS_ASYNC(执行异步文件写入),此操作内核会先把内容写到内核的缓冲区,某个合适的时候再写到磁盘。

三.文件映射实例                                           

 /**
  * @file mmap_file.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/mman.h>

 #define MMAP_FILE_NAME "a.txt"
 #define MMAP_FILE_SIZE 10

 void err_exit(const char *err_msg)
 {
     printf("error:%s\n", err_msg);
     exit();
 }

 /* 信号处理器 */
 void signal_handler(int signum)
 {
     if (signum == SIGSEGV)
         printf("\nSIGSEGV handler!!!\n");
     else if (signum == SIGBUS)
         printf("\nSIGBUS handler!!!\n");
     exit();
 }

 int main(int argc, const char *argv[])
 {
     )
     {
         printf(]);
         exit();
     }

     char *addr;
     int file_fd, text_len;
     long int sys_pagesize;

     /* 设置信号处理器 */
     if (signal(SIGSEGV, signal_handler) == SIG_ERR)
         err_exit("signal()");
     if (signal(SIGBUS, signal_handler) == SIG_ERR)
         err_exit("signal()");

     )
         err_exit("open()");

     /* 系统分页大小 */
     sys_pagesize = sysconf(_SC_PAGESIZE);
     printf("sys_pagesize:%ld\n", sys_pagesize);

     /* 内存只读 */
     //addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ, MAP_SHARED, file_fd, 0);

     /* 映射大于文件长度,且大于该文件分页大小 */
     //addr = (char *)mmap(NULL, sys_pagesize + 1, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);

     /* 正常分配 */
     addr = ();
     if (addr == MAP_FAILED)
         err_exit("mmap()");

     /* 原始数据 */
     printf("old text:%s\n", addr);

     /* 越界访问 */
     //addr += sys_pagesize + 1;
     //printf("out of range:%s\n", addr);

     /* 拷贝新数据 */
     text_len = strlen(argv[]);
     memcpy(addr, argv[], text_len);

     /* 同步映射区数据 */
     //if (msync(addr, text_len, MS_SYNC) == -1)
     //    err_exit("msync()");

     /* 打印新数据 */
     printf("new text:%s\n", addr);

     /* 解除映射区域 */
     )
         err_exit("munmap()");

     ;
 }

1.首先创建一个10字节的文件:

 $: count=

2.把程序编译运行后,依次执行2次写入:

可以看到本机的分页大小是4096字节。第一次写入9个字节,原来用dd命令创建的文件为空,old text为空。第二次写入4个字节,只覆盖了最前面的1234。

3.验证可访问现有分页的内存。写入超过10字节的数据:

上面我们写入了17个字节,虽然64行的mmap()映射了MMAP_FILE_SIZE=10字节。但从输入new text可以看出,我们依然可以访问10字节后面的内存,因为该数据都在一个分页(4096)里面。cat查看a.txt后,只有前10个字节写入了a.txt。

4.验证SIGSEGV信号。把64行注释调,58行打开,设置映射属性为只读,编译后访问:

设置只读属性后,第77行有写操作。我们自定义的信号处理器就捕捉到了该信号。如果没有自定义信号处理器,终端就会输出Segmentation fault

5.验证SIGBUS信号。用61行的方法来映射内存。映射了一个分页大小再加1字节的内存,并放开72,73行的代码,让指针指向一个分页后的区域。编译后运行:

SIGBUS信号被自定义处理器捕捉到了。如果没有自定义信号处理器,终端就会输出Bus error

四.匿名映射                                            

匿名映射有2种方式:

1.指定mmap()的flags参数为MAP_ANONYMOUS,在linux上当指定这个值后会忽略fd参数的值。不过在有的UNIX上还需要把fd指定为-1。

2.把/dev/zero当做文件描述符打开,从/dev/zero读取数据时它会给你提供无穷无尽的0,向它写数据,它会丢弃。丢弃这点跟/dev/null一样,只是/dev/null不跟你提供数据。

3.匿名映射的使用跟上面的文件映射差不多。这里不再给例子。

linux编程之内存映射的更多相关文章

  1. Linux驱动之内存映射

    本文参考了http://www.cnblogs.com/geneil/archive/2011/12/08/2281222.html.本文作为学习总结,将主要过程简要描述. 很多驱动实现某些功能都要通 ...

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

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

  3. Linux驱动mmap内存映射

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

  4. Linux高端内存映射(上)【转】

    转自:http://blog.csdn.net/vanbreaker/article/details/7579941 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 高端内 ...

  5. Linux编程之内存池的设计与实现(C++98)

    假设服务器的硬件资源"充裕",那么提高服务器性能的一个很直接的方法就是空间换时间,即"浪费"服务器的硬件资源,以换取其运行效率.提升服务器性能的一个重要方法就是 ...

  6. 【Linux编程】存储映射I/O

    存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而能够不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完毕,函数原型 ...

  7. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  8. linux mmap 内存映射【转】

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

  9. linux中的 IO端口映射和IO内存映射

    参考自:http://blog.csdn.net/zyhorse2010/article/details/6590488 CPU地址空间 (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬 ...

随机推荐

  1. Jquery实际应用,判断radio,selelct,checkbox是否选中及选中的值

    jquery取radio单选按钮的值 $("input[name='items']:checked").val();  另:判断radio是否选中并取得选中的值 如下所示: fun ...

  2. Python WMI获取Windows系统信息 监控系统

    #!/usr/bin/env python # -*- coding: utf-8 -*- #http://www.cnblogs.com/liu-ke/ import wmi import os i ...

  3. spring MVC @Resource不支持Lazy加载

    今天迁一系统时发现有个bean使用@Resource注入了另外一个bean,这个被注入的bean是将被deprecated的类,而且只有一两个功能使用到,为了先调整进行测试,增加了@Lazy注解,启动 ...

  4. java内存模型-volatile

    volatile 的特性 当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别.理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一 ...

  5. 我们的动机(Our motivation)

    我们的动机(Our motivation) There are many PHP frameworks nowadays, but none of them is like Phalcon (Real ...

  6. 升级tomcat7的运行日志框架到log4j2,可以打进kafka

    为了让web application能随意使用logging组件而不受web容器自身的影响,从tomcat 6.0开始,tomact默认使用的是java.util.logging framework来 ...

  7. 基于XMPP的IOS聊天客户端程序

    简介:XMPP协议是一种基于Socket长连接.以XML格式进行基本信息交换.C/S S/S多种架构的聊天协议 XMPPServer 基于XMPP协议的服务端(例如eJabber.OpenFire) ...

  8. Win7重装系统遇到的问题以及MysQL的问题解决

    连续三天因为系统的错误,android方面的软件一直不能正确运行.而且每次开机的时候QQ 微信等聊天工具也出现损坏.虽然重新下载一个可以保证在电脑不管的情况下正常的运行.可是作为玩电脑时间不长的我来说 ...

  9. xcode 7及以上版本网络请求不成功的原因

    在Xcode 7以前的版本使用的是http协议,从Xcode 7开始,默认使用的是https协议,这优化了加密性能. 要想在Xcode 7及以上版本能够进行网络请求,我们需要在info.plist文件 ...

  10. 浅谈Java五大设计原则之责任链模式

    首先我们得先定义一个责任链模式: 责任链模式是一种线性执行流程,多个对象都有机会去执行同一个任务,只是在执行过程中, 由于执行的权利和范围不一样,那么当自己不能处理此任务时,就必须将这个任务抛给下一个 ...