内存映射文件完全攻略原理和性能

假设采用标准系统调用 open()、read() 和 write() 来顺序读取磁盘文件,每个文件访问都需要系统调用和磁盘访问。又或者采用虚拟内存技术,以将文件 I/O 作为常规内存访问,这种方法称为内存映射文件,允许一部分虚拟内存与文件进行逻辑关联,这会导致显著的性能提高。

基本机制

实现文件的内存映射是将每个磁盘块映射到一个或多个内存页面。

最初,文件访问按普通请求调页来进行,从而产生缺页错误。这样,文件的页面大小部分从文件系统读取到物理页面(有些系统可以选择一次读取多个页面大小的数据块)。以后,文件的读写就按常规内存访问来处理。通过内存的文件操作,没有采用系统调用 read() 和 write() 的开销,而且简化了文件的访问和使用。

请注意,内存映射文件的写入不一定是对磁盘文件的即时(同步)写入。有的操作系统定期检查文件的内存映射页面是否已被修改,以便选择是否更新到物理文件。当关闭文件时,所有内存映射的数据会写到磁盘,并从进程虚拟内存中删除。

有些操作系统仅通过特定的系统调用来提供内存映射,而通过标准的系统调用来处理所有其他文件 I/O。然而,有的系统不管文件是否指定为内存映射,都选择对文件进行内存映射。

以 Solaris 为例,如果一个文件指定为内存映射(采用系统调用 mmaP()),那么 Solaris 会将该文件映射到进程地址空间。如果一个文件通过普通系统调用,如 open()、read() 和 write() 来打开和访问,那么 Solaris 仍然采用内存映射文件。然而,这个文件是映射到内核地址空间,无论文件如何打开,Solaris 都将所有文件 I/O 视为内存映射的,以允许文件访问在高效的内存子系统中进行。

多个进程可以允许并发地内存映射同一文件,以便允许数据共享。任何一个进程的写入会修改虚拟内存的数据,并且其他映射同一文件部分的进程都可看到。

根据虚拟内存的相关知识,可以清楚地看到内存映射部分的共享是如何实现的:每个共享进程的虚拟内存映射指向物理内存的同一页面,而该页面有磁盘块的复制,这种内存共享如图 1 所示。

内存映射文件
图 1 内存映射文件

内存映射系统调用还可以支持写时复制功能,允许进程既可以按只读模式来共享文件,也可以拥有自己修改的任何数据的副本。为了协调对共享数据的访问,有关进程可以使用实现互斥的机制。

很多时候,共享内存实际上是通过内存映射来实现的。在这种情况下,进程可以通过共享内存来通信,而共享内存是通过映射同样文件到通信进程的虚拟地址空间来实现的。内存映射文件充当通信进程之间的共享内存区域(图 2)。

采用内存映射I/O的共享内存
图 2 采用内存映射 I/O 的共享内存

首先创建 POSIX 共享内存对象,然后每个通信进程内存对象映射到其地址空间。在接下来的部分,将说明 Windows API 如何支持通过内存映射文件的内存共享。

共享内存 Windows API

通过内存映射文件的 Windows API 以创建共享内存区域的大致过程是这样的,首先为要映射的文件创建文件映射,接着在进程虚拟地址空间中建立映射文件的视图。另一个进程可以打开映射的文件,并且在虚拟地址空间中创建它的视图。映射文件表示共享内存对象,以便进程能够通信。

接下来更详细地说明这些步骤。首先,生产者进程使用 Windows API 中的内存映射功能来创建共享内存对象。接着,生产者将消息写入共享内存。然后,生产者进程打开对共享内存对象的映射,并读取生产者写入的消息。

为了建立内存映射文件,进程首先通过函数 CreateFile() 打开需要映射的文件,并得到打开文件的 HAIJDLE(句柄)。接着,进程通过函数 CreateFileMapping() 创建这个文件的映射。

一旦建立了文件映射,进程然后通过函数 MapViewOfFile(),在虚拟地址空间中建立映射文件的视图。映射文件的视图表示位于进程虚拟地址空间中的映射文件的部分,可以是整个文件或者是映射文件的一部分。如下所示程序说明了这个顺序。(为使代码简洁,这里省略了大量的错误检查。)
#include <windows.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
    HANDLE hFile,hMapFile;
    LPVOID lpMapAddress;
    hFile = CreateFile("temp.txt",/* file name */
    GENERIC_READ I GENERIC_WRITE,/* read/write access */
    0,/* no sharing of the file */
    NULL,/* default security */
    0PEN_ALWAYS,/* open new or existing file */
    FILE_ATTRIBUTE_NORMAL,/* routine file attributes */
    NULL); /* no file template */
    hMapFile = CreateFileMapping(hFile,/* file handle */
    NULL,/* default security */
    PAGE_READWRITE,/* read/write access to mapped pages */
    0, /* map entire file */
    0,TEXT("SharedObject")); /* named shared memory object */
    lpMapAddress = MapViewOfFile(hMapFile,/* mapped object handle */
    FILE_MAP_ALL_ACCESS,/* mapped view of entire file */
    0,0);
    /* write to shared memory */
    sprintf(lpMapAddress,"Shared memory message");
    UnmapViewOfFile(lpMapAddress);
    CloseHandle(hFile);
    CloseHandle(hMapFile);
}
调用 CreateFileMapping() 创建一个名为 SharedObject 的命名共享内存对象。消费者进程创建这个命名对象的映射,从而利用这个共享内存段进行通信。

接着,生产者在它的虚拟地址空间中创建内存映射文件的视图。通过将0传递给最后三个参数,表明映射的视图为整个文件。通过传递指定的偏移和大小,这样创建的视图只包含文件的一部分。

注意,在建立映射后,整个映射可能不会加载到内存中。映射文件可能是请求调页的,因此只有在被访问时才将所需页面加载到内存。

函数 MapViewOfFile() 返回共享内存对象的指针,因此,对这个内存位置的任何访问就是对共享内存文件的访问。在这个例子中,生产者进程将消息"Shared memory message"写到共享内存。

如下所示的程序说明了消费者进程如何建立命名共享内存对象的视图。这个程序比之前的程序要简单,因为这个进程所需做的就是创建一个到现有的命名共享内存对象的映射。消费者进程也必须创建映射文件的视图,这与之前程序的生产者进程一样。然后,消费者就从共享内存中读取由生产者进程写入的消息"Shared memory message"。
#include <windows.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
    HANDLE hMapFile;
    LPVOID lpMapAddress;
    hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,/* R/W access */
    FALSE,/* no inheritance 氺/
    TEXT("SharedObj ect")); /* name of mapped file object */
    lpMapAddress = MapViewOfFile(hMapFile,0);
    /* read from shared memory */
    printf ("Read message %s",lpMapAddress);
    UnmapViewOfFile(lpMapAddress);
    CloseHandle(hMapFile);
}
最后,两个进程调用 UnmapViewOfFile() 来删除映射文件的视图。

内存映射 I/O

在 I/O 的情况下,每个 I/O 控制器包括保存命令和传输数据的寄存器。通常,专用 I/O 指令允许在这些寄存器和系统内存之间进行数据传输。为了更方便地访问 I/O 设备,许多计算机体系结构提供了内存映射 I/O。

在这种情况下,一组内存地址专门映射到设备寄存器。对这些内存地址的读取和写入,导致数据传到或取自设备寄存器。这种方法适用于具有快速响应时间的设备,例如视频控制器。对于 IBMPC,屏幕上的每个位置都映射到一个内存位置。在屏幕上显示文本几乎和将文本写入适当内存映射位置一样简单。

内存映射 I/O 也适用于其他设备,如用于联结 modem 和打印机的计算机串口和并口,通过读取和写入这些设备寄存器(称为 I/O 端口),CPU 可以对这些设备传输数据。

为了通过内存映射串行端口发送一长串字节,CPU 将一个数据字节写到数据寄存器,并将控制寄存器的一个位置位以表示有字节可用。设备读取数据字节,并清零控制寄存器的指示位,以表示已准备好接收下一个字节。接着,CPU 可以传输下一个字节。如果 CPU 采用轮询监视控制位,不断循环查看设备是否就绪,这种操作称为程序 I/O。如果 CPU 不是轮询控制位,而是在设备准备接收一个字节时收到中断,则数据传输称为中断驱动

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


随着云计算和网络技术的发展,越来越多的数据需要在服务器之间进行传输和共享。云挂机宝作为一种常见的服务器设备,具有高效、稳定的特性,使得它成为了许多企业和个人用户传输数据的首选。本文将讨论云挂机宝服务器
阿里云服务器Tomcat无法从外部访问一、环境阿里云Ubuntu 12.04.5 LTStomcat和java都是阿里云默认的7的版本,如下图二、问题部署后./startup.sh启动tomcat 之后外部访问http://ip:8080/无法访问,之后去查看防火墙关闭了Ubuntu下面的防火墙123
购买一系列的东西其实就是花钱买块区域,服务器,域名,云解析,花了好多money。。。一、租服务器百度搜索阿里云进入官网首页,点击登录进入登录页面,可以使用邮箱登录或者注册进入注册页面,填写注册信息登录成功,进入主页,点击云服务ECS,点击购买推荐或者选购配置根据实际情况选择产品,可能没有货,那么就需要更改购买条件如果是新用户,购买
前言昨天买了域名,服务器,然后搭建了环境,然后想他通过默认的端口,不用端口就访问。设置WEB项目的欢迎页在WEB-INF文件夹下有个web.xml文件(最近新建的项目不包含此文件,可以手动新建),在welcome-file-list节点中设置,代码如下 <welcome-file-list> <welcome-file>test.html</welcome-file>
前言由于服务器centos6.8安装mysql一直出现不能连接问题,然后看到一个方法,一不小心就把yum给删除了,都还原不了,很是绝望,很难受。然后没有yum这个centos就感觉废了,然后找方法装上去。 python --version查看python 版本 whereis python 查看python文件 python: /usr/bin/python2.6 /usr/bin/python
前言用 SSH客户端登录上以后,自己想要在本地连接服务器上的mysql服务器。解决方法1、首先启动数据库[root@iZm5ec880z2rorZ ~]# service mysqld start可以输入一下加粗命令:[root@iZm5ec880z2rorZ ~]# mysql -u root -p Enter password: (输入你的数据库密码)Welcome to the My
前言乱码问题是很让人抓狂的问题,下面我将记录一下Linux下MySQL乱码问题的解决方法。 mysql在linux下乱码问题一、操作mysql默认字符集是latin1,但是我们大部分程序使用的字符集是utf8,我们就需要修改mysql的字符集了。1)查看默认编码show variables like 'character%';+--------------------------+--------
问题描述今天阿里云服务器安装mysql的收,遇到了一个很熟悉的问题 输入#mysql -u root -p ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) 解决方案一、方案1 1.#ps -A|grep mysql 显示类似:
今天在使用filezilla连接虚拟机中的ubuntu的时候出现上次出错,错误详情为:open for write: permission denied看完错误大概知道和权限有问题,再次查看虚拟机,我们使用以下命令给我们需要放入的目标文件权限:sudo chmod 777 /home再次尝试上传,上传成功! ...
直接上步骤,做一个简单的记录!配置阿里云oss找到对应的oss如果没有bucket,则新建一个新建之后,进入到对应的bucket,然后进行相关设置以上就是oss的基本设置,接下来是域名相关设置,这也是最重要的一些步骤。oss域名解析和cdn设置oss域名解析设置oss的域名设置是在传输管理下进行设置。如果没有域名,需要添加域名,没有注册域名自行注册。添加之后就会在上面列表上显示。以上就是域名的解析配置,期间还需要设置域名解析cname等。oss的cdn设置加速设置cdn
这个需要开放centos的端口和腾讯云服务器的端口。防火墙开放8080端口因为Centos7以上用firewalld代替了iptables,也就是说firewalld开通了8080端口应该就行了1.输入netstat -tunlp 查看8080是否放开。2.如果没有开放,输入firewall-cmd --state #查看防火墙状态。3.在running 状态下,向firewall 添加需要开放的端口firewall-cmd --permanent --zone=public --add-po
前言:博主资历尚浅,很多东西都还在刚起步学习的阶段,这几天开发任务比较轻,就在自己window系统下,模拟部署远程服务器,利用Jenkins + Ant + Tomcat 搭建了一个自动发布部署的环境
为了做集群测试,在每台机器上装了3个tomcat,每次发布项目的时候都要反复敲一些命令,重启 tomcat 之前先检查 tomcat 进程有没有停掉,没有还要手动 kill 该进程。 发布次数多了,操
Jenkins修改管理员密码,我看了网上所有的教程,竟然全都是拿着一串已经加密好的 111111 的密文去替代 config.xml 文件里面的密码,然后大家的密码都是 111111!我觉得这种做法实
一、四层与七层负载均衡在原理上的区别 1.图示 2.概述 四层负载均衡工作在 OSI 模型中的四层,即传输层。四层负载均衡只能根据报文中目标地址和源地址对请求进行转发,而无法修改或判断所请求资源的具体
和 window不同,在Linux压缩文件需要注意的是,压缩后的文件会把源文件给替代,无论是gzip、bzip2、xz 均不支持压缩目录,要达到压缩目录的目的,需要用到tar指令。 gzip 压缩 g
一、前言 随着每天业务的增长,Tomcat 的catalina.out日志 变得越来越大,占用磁盘空间不说。要查看某个时候的日志的时候,庞大的日志让你顿时无从下手,所以日志的切割的变得刻不容缓。而且,
一、安装 和 准备工作 我们选择了用 Tomcat 服务器下 war 包的安装方式。Jenkins 的下载地址:http://mirrors.jenkins-ci.org/,打开链接后,表格有war列
一、gcc gcc是Linux上面最标准的C语言的编译程序,用来源代码的编译链接。 gcc -c hello.c 编译产生目标文件hello.o gcc -O hello.c 编译产生目标文件,并进行
随着智能化互联时代的来临,家中的智能设备越来越多:电视机、平板、游戏主机、电脑、手机等遍及家中各个角落,同时设备之间共享数据的需求变的越来越强烈。比如同步、备份手机上的照片和视频,在电视机上观看电脑中