一.概述                                                  

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

内存映射分为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. Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  2. linux基础知识总结

    使用linux将近一年了,一直都没有时间来总结一下,借着最近在整理知识框架的机会总结一下linux的应用知识.   1.linux有两个目录很特殊,一个是~,另一个是/.两个目录的含义不一样,/是系统 ...

  3. 七个结构模式之外观模式(Facade Pattern)

    定义: 为子系统的一组接口提供一个统一的入口,从而降低系统之间的耦合度,提高子系统的可用性.外观模式又称为门面模式,是迪米特法则的一个体现,主要目的就是降低耦合. 结构图 Facade:抽象外观类,子 ...

  4. Hadoop学习笔记1-如何简单布署hadoop

    企业机型配置: 选型标准:普通的,廉价的,标准的(容易替换的),工业化大规模生产的 CPU:支持多核CPU,如2个4核CPU 内存:16G以上,内存越大,常用数据都缓存在内存,提高速度 硬盘:不需RA ...

  5. paramiko 遭遇socket.error: Socket is closed 错误的解决办法

    似乎是connection自己断了解决的办法是在创建conn的时候添加下面这句 conn.keep_this = conn_session 完整代码 def create_a_conn(ip_addr ...

  6. 博客迁移到独立域名owenchen.net,此博客不再更新。

    博客已迁移到阿里云,自己搭的wordpress,可以有更多的灵活性. 写点代码,放点示例,欢迎访问. owenchen.net

  7. SAP 录屏BDC使用—实例

    1)  输入TCode:SHDB进入BDC录制初始界面,该界面可以实现已创建BDC Session信息的查看.删除及锁定等操作 2)  单击工具栏 Newrecording 按钮创建一个新的BDC,系 ...

  8. JavaScript学习13 JavaScript中的继承

    JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript"> //继承第一种方式 ...

  9. sqlite锁的机制

     reserved state 进入reserved state以后,sqlite可以修改数据库中的内容,不过把修改以后的内容写到pager的缓存里,大小由page cache指定. 进入这个状态以 ...

  10. Swfit 字符与字符串

    Swfit 字符与字符串 OC 定义字符 char charValue = 'a'; Swift 定义字符 var charValue:Character = "a" Unicod ...