精华内容
下载资源
问答
  • mmap库:Python内存映射文件操作

    千次阅读 2021-05-05 18:54:27
    使用mmap()函数可以创建一个内存映射文件。该函数的第1个参数是一个文件描述符,可以通过file对象的fileno()函数获取;第2个参数是要映射的文件部分大小(单位字节),如果该值为0,映射整个文件,如果该

    前言

    内存映射通常可以提高I/O的性能,因为使用内存映射时,不需要对每个访问都建立一个单独的系统调用,也不需要在缓冲区之间复制数据,内核和用户都能很方便的直接访问内存。

    本篇,将详细介绍Python内存映射库:mmap。

    mmap(读文件)

    使用mmap()函数可以创建一个内存映射文件。该函数的第1个参数是一个文件描述符,可以通过file对象的fileno()函数获取;第2个参数是要映射的文件部分大小(单位字节),如果该值为0,映射整个文件,如果该参数大于文件大小,则扩展该文件。

    示例如下:

    import mmap
    
    with open('英文文档.txt','r') as f:
        with mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as m:
            print(m.read(10))
            print(m.read(10))
            print(m[:-10])
    

    运行之后,效果如下:
    输出
    这里读写会根据文件指针进行移动,比如开始读10个字符,那么第2次读就会接着11个字符开始在读,不会返回起点,而通过切片或者seek()函数可以将指针重置。

    至于access参数,表示以什么方式访问,这里以读的方式。

    需要特别注意的是,windows不支持创建长度为0的映射。

    write(写文件)

    写文件比较简单,这里我们直接看一段代码:

    import mmap
    
    word = b'The'
    
    with open('英文文档.txt', 'r+') as f:
        with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE) as m:
            loc = m.find(word)
            m[loc:loc + len(word)] = b'lyj'
            print(m.read())
    

    运行之后,首字母The,就被我们替换成"lyj"了,效果如下:
    追加

    当然,这里除了切片写入之外,也可以使用write()等文件操作方法进行操作,与文件操作一样。需要注意的是假如access等于ACCESS_COPY,那么不会把修改的内容写入磁盘上的文件。

    展开全文
  • 内存映射文件的应用

    千次阅读 2019-06-08 11:31:35
    内存映射文件的应用一,操作系统加载EXE和DLL二,读写大文件三,进程间通信      内存映射文件是windows开发下常用的一种技术,既可以用来读写磁盘上的大文件,也可以用来实现进程间的...

         内存映射文件是windows开发下常用的一种技术,既可以用来读写磁盘上的大文件,也可以用来实现进程间的通信,本文主要对内存映射的几大用途做一个简述。

    一,操作系统加载EXE和DLL

         当一个应用程序启动时,操作系统首先会调用CreateFile来打开磁盘上的.exe文件。接着系统会调用CreateFileMapping来创建文件映射对象。最后系统会以新创建的进程的名义调用MapViewOfFileEx(),这样就把.exe文件映射到了进程的地址空间中。这里使用MapViewOfFileEx的原因是为了将文件映射到进程的指定基地址处(32位程序默认基地址为0x400000)。然后系统创建进程的主线程,在映射得到的视图中取得可执行代码的第一个字节的地址,把该地址放到线程的指令指针中,最后CPU执行其中的代码。

         这时再启动该应用程序的第二个实例,操作系统会发现该.exe文件已经有一个文件映射对象,那么系统会将该文件映射对象视图映射到新创建的进程的地址空间中。显然,由于物理内存中包含.exe文件可执行代码的那些页面为两个进程(或多个进程)所共享,因此内存的使用率更高。

         但是由于该.exe的所有实例都是共享同一个内存映射,那么如果其中一个实例修改了数据页面中的一些全局变量,其他实例也会被修改,当然,操作系统也考虑到了这一点,通过内存管理系统的写时复制特性来防止这种情况的发生。任何时候当应用程序试图写入内存映射文件的时候,系统首先会截获到这类尝试,接着为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。这样,其他实例不会受到任何影响。
    在这里插入图片描述

    二,读写大文件

         C++中提供了类似fopen,fstream等函数来操作文件,这些函数的原理都是直接对文件进行IO读取和缓存,当处理的文件只有几k,几十k,或者几百k的时候,使用这些函数都没有问题。但是如果处理的文件是几百兆,几G的大小,频繁的IO操作会很耗时,甚至由于需要把这么大的数据都加载到程序缓存中,还会影响到程序的内存,因此,我们采用共享内存的方式来解决这个问题。

         共享内存读取大文件主要有以下几个步骤:
    1, 创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的那个磁盘文件。
    2, 创建一个文件映射内核对象,来告诉操作系统文件的大小以及我们打算如何访问文件。
    3, 告诉操作系统把文件映射对象的部分或全部映射到进程的地址空间中。

    代码示例如下:

    int Func()
    {
    	clock_t cStart, cEnd;
    	cStart = clock();
    
    	//打开一个文件内核对象
    	HANDLE hFile = CreateFile(L"test.txt",	//文件名称
    		GENERIC_WRITE | GENERIC_READ,	//访问文件的方式(可读可写)
    		0,								//此文件共享的方式(0表示其他任何试图打开文件的操作都会失败)
    		NULL,							//指向SECURITY_ATTRIBUTES的指针,用以指示安全信息以及句柄继承等属性
    		OPEN_EXISTING,					//指定打开文件的标志(OPEN_EXISTING表示打开已有的文件,如果不存在则打开失败)
    		FILE_ATTRIBUTE_NORMAL,			//文件属性,默认
    		NULL							//文件句柄传空,如果非空,则忽略上面那个参数,使用当前文件句柄的属性
    	);
    	if (INVALID_HANDLE_VALUE == hFile)
    	{
    		printf("CreateFile Failed!\n");
    		return 0;
    	}
    
    	//获取文件大小
    	DWORD dwFileSize = GetFileSize(hFile, NULL);
    
    	//创建文件映射内核对象
    	HANDLE hFileMap = CreateFileMapping(hFile,	//文件内核对象句柄
    		NULL,									//指向SECURITY_ATTRIBUTES的指针,用以指示安全信息以及句柄继承等属性
    		PAGE_READWRITE,							//文件保护属性(可读可写)
    		0,										//文件大小高位值,如果文件小于4G,则填0
    		dwFileSize + 1,							//文件大小低位值,PAGE_READ属性可以直接填0,PAGE_READWRITE的话要>=实际文件大小
    		L"FileMapTest"							//文件映射对象的名称
    	);
    	if (INVALID_HANDLE_VALUE == hFileMap)
    	{
    		printf("CreateFile Failed!\n");
    		return 0;
    	}
    
    	//将文件的数据映射到进程的地址空间中
    	PVOID pvFile = MapViewOfFile(hFileMap,
    		FILE_MAP_WRITE, 0, 0, 0);
    
    	//操作pvFile来遍历数据
    	char *pData = (char*)pvFile;
    	for (int i = 0; i < dwFileSize; i++)
    	{
    		
    	}
    
    	cEnd = clock();
    	printf("MapViewOfFile: %.6f\n", double(cEnd - cStart) / CLOCKS_PER_SEC);
    
    	//释放资源
    	UnmapViewOfFile(pvFile);
    
    	CloseHandle(hFileMap);
    
    	CloseHandle(hFile);
    }
    
    

    三,进程间通信

         Windows提供了多种机制可以使得应用程序之间可以快速地,方便地共享数据和信息,包括剪切板,套接字,windows消息(尤其是WM_COPYDATA),DDE,RPCDEN等。在windows系统中,在同一台机器上共享数据的最底层的机制就是内存映射文件,刚才提到的那些进程间通信方式归根结底都会用到内存映射文件。因此如果要求低开销和高性能,内存映射文件无疑是同一台机器上的多进程通信最好的方法。

         举例说明,有一个管理客户端,有一个专门处理业务的后台进程,后台进程处理业务的数据量(比如行情数据量,代码数据量,查询笔数等信息)需要同步到管理端界面显示,这两个进程之间的通信就是通过内存映射来实现。由于这个例子不涉及到多个进程同时写数据,所以不需要考虑到加锁,如果存在多进程写的情况,需要加内核对象锁。

         管理客户端代码:

    //创建文件映射对象
    	char szMapFileName[MAX_PATH] = { 0 };
    	if (NULL == m_hMapHandle)
    	{
    		_snprintf(szMapFileName, sizeof(szMapFileName) - 1, "statistic_map_%s.dat", g_cHqissueName);
    		m_hMapHandle = CreateFileMapping(
    			(HANDLE)INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(STATISTIC_DATA), szMapFileName);
    		if (m_hMapHandle == NULL)
    		{
    			return -1;
    		}
    	}
    
    	//将文件映射到程序的内存地址
    	if (NULL == m_pStatisticData)
    	{
    		m_pStatisticData = (STATISTIC_DATA *)MapViewOfFile(m_hMapHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    		if (m_pStatisticData == NULL)
    		{
    			CloseHandle(m_hMapHandle);
    			m_hMapHandle = NULL;
    			return -2;
    		}
    
    		memset(m_pStatisticData, 0, sizeof(STATISTIC_DATA));
    		m_pStatisticData->nSize = sizeof(STATISTIC_DATA);
    		m_pStatisticData->dwProcessID = GetCurrentProcessId();
    	}
    
    

         其中STATISTIC_DATA是我们共享的数据结构。m_pStatisticData也就是映射到当前进程中的地址,直接对其进行访问赋值。
         后台进程代码:

    m_hMapHandle = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "statistic_map_hqissue1.dat");
    		if (!m_hMapHandle)
    		{
    			return;
    		}
    
    		m_pStatisticData = (STATISTIC_DATA*)MapViewOfFile(m_hMapHandle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
    		if (!m_pStatisticData)
    		{
    			return;
    		}
    
    

         直接打开命名的内存映射文件,映射到当前进程中,访问m_pStatisticData即可取值。

    展开全文
  • 内存映射文件

    千次阅读 2013-08-11 16:48:36
    内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对...


    内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与 虚拟内存有些类似,通过内存映射文件可以保留一个 地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

    编辑本段基本概述

    文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的。
    内存映射文件是由一个文件到进程地址空间的映射。Win32中,每个进程有自己的地址空间,一个进程不能轻易地访问另一个进程地址空间中的数据,所以不能像16位Windows那样做。Win32系统允许多个进程(运行在同一计算机上)使用内存映射文件来共享数据。实际上,其他共享和传送数据的技术,诸如使用SendMessage或者PostMessage,都在内部使用了内存映射文件。 [1]

    编辑本段数据共享

    文件数据共享

    这种数据共享是让两个或多个进程映射同一文件映射对象的视图,即它们在共享同一物理存储页。这样,当一个进程向内存映射文件的一个视图写入数据时,其他的进程立即在自己的视图中看到变化。 注意,对文件映射对象要使用同一名字。

    访问方法

    这样,文件内的数据就可以用内存读/写指令来访问,而不是用ReadFile和WriteFile这样的I/O系统函数,从而提高了文件存取速度。

    编辑本段范围应用

    适用范围

    这种函数最适用于需要读取文件并且对文件内包含的信息做 语法分析的应用程序,如:对输入文件进行语法分析的彩色语法编辑器,编译器等。
    把文件映射后进行读和分析,能让应用程序使用内存操作来操纵文件,而不必在文件里来回地读、写、移动 文件指针

    应用

    有些操作,如放弃“读”一个字符,在以前是相当复杂的,用户需要处理缓冲区的刷新问题。在引入了映射文件之后,就简单的多了。应用程序要做的只是使指针减少一个值。
    映射文件的另一个重要应用就是用来支持永久命名的共享内存。要在两个应用程序之间共享内存,可以在一个应用程序中创建一个文件并映射之,然后另一个应用程序可以通过打开和映射此文件把它作为共享的内存来使用。VC++使用内存映射文件处理大文件

    编辑本段内存文件

    内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。
    内存映射文件并不是简单的文件I/O操作,实际用到了Windows的核心编程技术-- 内存管理。所以,如果想对内存映射文件有更深刻的认识,必须对Windows操作系统的内存管理机制有清楚的认识,下面给出使用内存映射文件的一般方法:
    首先要通过CreateFile()函数来创建或打开一个文件 内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

    编辑本段相关函数

    在使用内存映射文件时,所使用的API函数主要就是前面提到过的那几个函数,下面分别对其进行介绍:
    HANDLE CreateFile(LPCTSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile);
    函数CreateFile()即使是在普通的文件操作时也经常用来创建、打开文件,在处理内存映射文件时,该函数来创建/打开一个文件内核对象,并将其句柄返回,在调用该函数时需要根据是否需要数据读写和文件的共享方式来设置参数dwDesiredAccess和dwShareMode,错误的参数设置将会导致相应操作时的失败。
    HANDLE CreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCTSTR lpName);
    CreateFileMapping()函数创建一个文件映射内核对象,通过参数hFile指定待映射到进程地址空间的文件句柄(该句柄由CreateFile()函数的返回值获取)。由于内存映射文件的物理存储器实际是存储于磁盘上的一个文件,而不是从系统的页文件中分配的内存,所以系统不会主动为其保留地址空间区域,也不会自动将文件的存储空间映射到该区域,为了让系统能够确定对页面采取何种保护属性,需要通过参数flProtect来设定,保护属性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分别表示文件映射对象被映射后,可以读取、读写文件数据。在使用PAGE_READONLY时,必须确保CreateFile()采用的是GENERIC_READ参数;PAGE_READWRITE则要求CreateFile()采用的是GENERIC_READ|GENERIC_WRITE参数;至于属性PAGE_WRITECOPY则只需要确保CreateFile()采用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的参数dwMaximumSizeHigh和dwMaximumSizeLow也是相当重要的,指定了文件的最大字节数,由于这两个参数共64位,因此所支持的最大文件长度为16EB,几乎可以满足任何大数据量文件处理场合的要求。
    LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,DWORD dwNumberOfBytesToMap);
    MapViewOfFile()函数负责把文件数据映射到进程的地址空间,参数hFileMappingObject为CreateFileMapping()返回的文件映像对象句柄。参数dwDesiredAccess则再次指定了对文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。虽然这里一再对保护属性进行重复设置看似多余,但却可以使应用程序能更多的对数据的保护属性实行有效控制。MapViewOfFile()函数允许全部或部分映射文件,在映射时,需要指定数据文件的 偏移地址以及待映射的长度。其中,文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。当然,也可以通过如下代码来动态获取当前操作系统的分配粒度:
    SYSTEM_INFO sinf;GetSystemInfo(&sinf);DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;
    参数dwNumberOfBytesToMap指定了数据文件的映射长度,这里需要特别指出的是,对于Windows 9x操作系统,如果MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象,将返回空值(NULL);但是在Windows 2000下,MapViewOfFile()只需要为必要的视图找到足够大的一个区域即可,而无须考虑整个文件映射对象的大小。
    在完成对映射到进程地址空间区域的文件处理后,需要通过函数UnmapViewOfFile()完成对文件数据映像的释放,该函数原型声明如下:
    BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
    唯一的参数lpBaseAddress指定了返回区域的基地址,必须将其设定为MapViewOfFile()的返回值。在使用了函数MapViewOfFile()之后,必须要有对应的UnmapViewOfFile()调用,否则在进程终止之前,保留的区域将无法释放。除此之外,前面还曾由CreateFile()和CreateFileMapping()函数创建过文件内核对象和文件映射内核对象,在进程终止之前有必要通过CloseHandle()将其释放,否则将会出现资源泄漏的问题。
    除了前面这些必须的API函数之外,在使用内存映射文件时还要根据情况来选用其他一些辅助函数。例如,在使用内存映射文件时,为了提高速度,系统将文件的数据页面进行高速缓存,而且在处理文件映射视图时不立即更新文件的磁盘映像。为解决这个问题可以考虑使用FlushViewOfFile()函数,该函数强制系统将修改过的数据部分或全部重新写入磁盘映像,从而可以确保所有的数据更新能及时保存到磁盘。

    编辑本段应用示例

    下面结合一个具体的实例来进一步讲述内存映射文件的使用方法。该实例从端口接收数据,并实时将其存放于磁盘,由于数据量大(几十GB),在此选用内存映射文件进行处理。下面给出的是位于工作线程MainProc中的部分主要代码,该线程自程序运行时启动,当端口有数据到达时将会发出事件hEvent[0],WaitForMultipleObjects()函数等待到该事件发生后将接收到的数据保存到磁盘,如果终止接收将发出事件hEvent[1],事件处理过程将负责完成资源的释放和文件的关闭等工作。下面给出此线程处理函数的具体实现过程:
    // 创建文件内核对象,其句柄保存于hFile
    HANDLE hFile = CreateFile("Recv1.zip",GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
    // 创建文件映射内核对象,句柄保存于hFileMapping
    HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0x4000000,NULL);
    // 释放文件内核对象
    CloseHandle(hFile);
    // 设定大小、偏移量等参数
    __int64 qwFileSize = 0x4000000;
    __int64 qwFileOffset = 0;
    __int64 T = 600 * sinf.dwAllocationGranularity;
    DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;
    // 将文件数据映射到进程的地址空间
    PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,FILE_MAP_ALL_ACCESS,(DWORD)(qwFileOffset>>32),(DWORD)(qwFileOffset&0xFFFFFFFF),dwBytesInBlock);
    while(bLoop)
    {
    // 捕获事件hEvent[0]和事件hEvent[1]
    DWORD ret = WaitForMultipleObjects(2,hEvent,FALSE,INFINITE);
    ret -= WAIT_OBJECT_0;
    switch (ret)
    {
    // 接收数据事件触发
    case 0:
    // 从端口接收数据并保存到内存映射文件
    nReadLen=syio_Read(port[1],pbFile + qwFileOffset,QueueLen);
    qwFileOffset += nReadLen;
    // 当数据写满60%时,为防数据溢出,需要在其后开辟一新的映射视图
    if (qwFileOffset > T)
    {
    T = qwFileOffset + 600 * sinf.dwAllocationGranularity;UnmapViewOfFile(pbFile);
    pbFile = (PBYTE)MapViewOfFile(hFileMapping,FILE_MAP_ALL_ACCESS,(DWORD)(qwFileOffset>>32),(DWORD)(qwFileOffset&0xFFFFFFFF),dwBytesInBlock);
    }
    break;
    // 终止事件触发
    case 1:
    bLoop = FALSE;
    // 从进程的地址空间撤消文件数据映像
    UnmapViewOfFile(pbFile);
    // 关闭文件映射对象
    CloseHandle(hFileMapping);
    break;
    }
    }…
    在终止事件触发处理过程中如果只简单的执行UnmapViewOfFile()和CloseHandle()函数将无法正确标识文件的实际大小,即如果开辟的内存映射文件为30GB,而接收的数据只有14GB,那么上述程序执行完后,保存的文件长度仍是30GB。也就是说,在处理完成后还要再次通过内存映射文件的形式将文件恢复到实际大小,下面是实现此要求的主要代码:
    // 创建另外一个文件内核对象
    hFile2 = CreateFile("Recv.zip",GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
    // 以实际数据长度创建另外一个文件映射内核对象
    hFileMapping2 = CreateFileMapping(hFile2,NULL,PAGE_READWRITE,0,(DWORD)(qwFileOffset&0xFFFFFFFF),NULL);
    // 关闭文件内核对象
    CloseHandle(hFile2);
    // 将文件数据映射到进程的地址空间
    pbFile2 = (PBYTE)MapViewOfFile(hFileMapping2,FILE_MAP_ALL_ACCESS,0,0,qwFileOffset);
    // 将数据从原来的内存映射文件复制到此内存映射文件
    memcpy(pbFile2,pbFile,qwFileOffset);
    file:
    //从进程的地址空间撤消文件数据映像
    UnmapViewOfFile(pbFile);
    UnmapViewOfFile(pbFile2);
    // 关闭文件映射对象
    CloseHandle(hFileMapping);
    CloseHandle(hFileMapping2);
    // 删除临时文件
    DeleteFile("Recv1.zip");
    展开全文
  • mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段...


    mmap基础概念

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

              

    由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

    linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:

             

    vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。具体步骤请看下一节。

     

    mmap内存映射原理

    mmap内存映射的实现过程,总的来说可以分为三个阶段:

    (一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

    1、进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

    2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

    3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

    4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

     

    (二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

    5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

    6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

    7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

    8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

     

    (三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

    注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

    9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

    10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

    11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

    12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

    注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

     

    mmap和常规文件操作的区别

    对linux文件系统不了解的朋友,请参阅我之前写的博文《从内核文件系统看文件读写过程》,我们首先简单的回顾一下常规文件系统操作(调用read/fread等类函数)中,函数的调用过程:

    1、进程发起读文件请求。

    2、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。

    3、inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。

    4、如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。

    总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

    而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

    总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

     

    mmap优点总结

    由上文讨论可知,mmap优点共有一下几点:

    1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

    2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

    3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。

         同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。

    4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

     

    mmap相关函数

    函数原型

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

    返回说明

    成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1], error被设为以下的某个值:

      返回错误类型

    参数

    start:映射区的开始地址

    length:映射区的长度

    prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

      prot

    flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

      flag

    fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1

    offset:被映射对象内容的起点

    相关函数

    int munmap( void * addr, size_t len ) 

    成功执行时,munmap()返回0。失败时,munmap返回-1,error返回标志和mmap一致;

    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小;

    当映射关系解除后,对原来映射地址的访问将导致段错误发生。 

     

    int msync( void *addr, size_t len, int flags )

    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。

    可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

     

    mmap使用细节

    1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。

    2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。

    3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

     

    在上面的知识前提下,我们下面看看如果大小不是页的整倍数的具体情况:

    情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。

    分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。映射后的对应关系如下图所示:

                   

    此时:

    (1)读/写前5000个字节(0~4999),会返回操作文件内容。

    (2)读字节5000~8191时,结果全为0。写5000~8191时,进程不会报错,但是所写的内容不会写入原文件中 。

    (3)读/写8192以外的磁盘部分,会返回一个SIGSECV错误。

     

    情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。

    分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:

                     

    此时:

    (1)进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。

    (2)对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。

    (3)对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。

    (4)对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。

     

    情形三:一个文件初始大小为0,使用mmap操作映射了1000*4K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。

    分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。

    但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。

    这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。

    mmap基础概念

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

              

    由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

    linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:

             

    vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。具体步骤请看下一节。

     

    mmap内存映射原理

    mmap内存映射的实现过程,总的来说可以分为三个阶段:

    (一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

    1、进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

    2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

    3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

    4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

     

    (二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

    5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

    6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

    7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

    8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

     

    (三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

    注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

    9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

    10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

    11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

    12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

    注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

     

    mmap和常规文件操作的区别

    对linux文件系统不了解的朋友,请参阅我之前写的博文《从内核文件系统看文件读写过程》,我们首先简单的回顾一下常规文件系统操作(调用read/fread等类函数)中,函数的调用过程:

    1、进程发起读文件请求。

    2、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。

    3、inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。

    4、如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。

    总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

    而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

    总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

     

    mmap优点总结

    由上文讨论可知,mmap优点共有一下几点:

    1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

    2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

    3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。

         同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。

    4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

     

    mmap相关函数

    函数原型

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

    返回说明

    成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1], error被设为以下的某个值:

      返回错误类型

    参数

    start:映射区的开始地址

    length:映射区的长度

    prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

      prot

    flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

      flag

    fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1

    offset:被映射对象内容的起点

    相关函数

    int munmap( void * addr, size_t len ) 

    成功执行时,munmap()返回0。失败时,munmap返回-1,error返回标志和mmap一致;

    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小;

    当映射关系解除后,对原来映射地址的访问将导致段错误发生。 

     

    int msync( void *addr, size_t len, int flags )

    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。

    可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

     

    mmap使用细节

    1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。

    2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。

    3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

     

    在上面的知识前提下,我们下面看看如果大小不是页的整倍数的具体情况:

    情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。

    分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。映射后的对应关系如下图所示:

                   

    此时:

    (1)读/写前5000个字节(0~4999),会返回操作文件内容。

    (2)读字节5000~8191时,结果全为0。写5000~8191时,进程不会报错,但是所写的内容不会写入原文件中 。

    (3)读/写8192以外的磁盘部分,会返回一个SIGSECV错误。

     

    情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。

    分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:

                     

    此时:

    (1)进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。

    (2)对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。

    (3)对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。

    (4)对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。

     

    情形三:一个文件初始大小为0,使用mmap操作映射了1000*4K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。

    分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。

    但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。

    这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。

    展开全文
  • 内存映射文件详解(Windows)

    千次阅读 2017-07-07 13:47:13
    内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对...
  • 1. mmap内存映射文件 建立一个文件的内存映射将使用操作系统虚拟内存来直接访问文件系统上的数据,而不是使用常规的I/O函数访问数据。内存映射通常可以提供I/O性能,因为使用内存映射是,不需要对每个访问都建立一个...
  • 谈谈内存映射文件

    千次阅读 2014-11-24 22:26:29
    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件。将文件映射到内存中后,我们就可以在内存中操作他们了...
  • 使用windows内存-内存映射文件

    千次阅读 2016-01-03 19:19:56
    内存映射文件 和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。将文件映射成内存,我们可以像使用内存一样使用文件....
  • 内存映射 在 Linux操作系统中非常重要,因为其涉及到高效的跨进程通信 &amp; 文件操作 今天,我将详细讲解操作系统中核心概念:内存映射 目录 1. 定义 关联 进程中的1个虚拟内存区域 &amp; 1个磁盘上的...
  • C#内存映射文件学习总结

    千次阅读 2017-05-05 22:43:54
    C#内存映射文件学习 http://www.cnblogs.com/flyant/p/4443187.html 内存映射文件是由一个文件到进程地址空间的映射。  C#提供了允许应用程序把文件映射到一个进程的函(MemoryMappedFile.CreateOrOpen)...
  • 内存映射文件原理探索

    万次阅读 多人点赞 2016-11-27 17:10:09
    虚拟内存与内存映射文件的区别与联系 二者的联系虚拟内存和内存映射文件都是将一部分内容加载到,另一部分放在磁盘上的一种机制,二者都是应用程序动态性的基础,由于二者的虚拟性,对于用户都是透明的. 虚拟内存其实...
  • Linux操作系统中的内存映射

    千次阅读 2018-05-11 17:29:44
    内存映射 在 Linux操作系统中与高效的跨进程通信 &amp; 文件操作息息相关。 定义 关联 进程中的1个虚拟内存区域 &amp; 1个磁盘上的对象,使得二者存在映射关系 被映射的对象称为:共享对象(普通文件 /...
  • 虚拟内存与内存映射文件操作系统内存管理的重要部分,二者有相似也有不同的地方,本文是作者学习与应用中得到的一些体会,有错误的地方,请提点。 二者的联系:虚拟内存与内存映射文件都是将一部分内容加载的...
  • IPC之Posix内存映射文件详解

    千次阅读 2016-01-10 20:44:25
    1.什么是内存映射文件    内存映射文件,就是把磁盘上的物理文件映射至进程地址空间中,使用内存映射文件的特性是,所有的I/O都是在内核掩盖下完成,我们只需编写存取内存映射区中各个值的代码,也就是不需要...
  • 前面的《Windows内存体系(3) – 内存映射文件》文章,对内存映射文件技术的原理进行了介绍,本篇文章着重介绍该技术的使用场景。 一、内存映射文件技术介绍 常用的有Win32 API的CreateFile()、WriteFile()、...
  • c#实现内存映射文件共享内存

    万次阅读 2016-04-10 14:17:54
    内存映射文件是利用虚拟内存把文件映射到进程的地址空间中去,在此之后进程操作文件,就像操作进程空间里的地址一样了,比如使用c语言的 memcpy等内存操作的函数。这种方法能够很好的应用在需要频繁处理一个文件或者...
  • 内存映射文件例子

    千次阅读 2013-10-10 15:51:42
    内存映射文件  http://kymcuc.blog.163.com/blog/static/201942114201211542541308/ 1、利用段在同一程序的多个实例之间共享数据 #includeusing namespace std; #pragma data_seg("shared")//自定义端,...
  • 内存映射文件的物理存储器来自磁盘已有的文件,而不是来自系统的页交换文件。一旦把文件映射到地址空间,就可以对它进行访问,就好像整个文件都已经被载入内存一样。不必再对文件执行I/O操作。 使用内存映射文件...
  •  内存映射文件对于托管世界的开发人员来说似乎很陌生,但它确实已经是很远古的技术了,而且在操作系统中地位相当。实际上,任何想要共享数据的通信模型都会在幕后使用它。  内存映射文件究竟是个什么?内存映射...
  • windows 内存映射文件

    千次阅读 2012-09-20 08:21:21
    4.内存管理机制--内存映射文件 (Map)  和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。 ·使用场合 它有三个主要...
  • 内存映射文件原理

    千次阅读 2018-02-15 13:45:22
    一直都对内存映射文件这个概念很模糊,不知道它和虚拟内存有什么区别,而且映射这个词也很让人迷茫,今天终于搞清楚了。。。下面,我先解释一下我对映射这个词的理解,再区分一下几个容易混淆的概念,之后,什么是...
  • Org: ...通过内存映射文件函数可以将磁盘上文件的全部和部分映射到进程虚拟地址空间的某个位置,一旦完成了映射,对文件内容的访问就如同在该地址区域内直接对内存访问一样简单。这
  • 内存映射文件技术

    千次阅读 2013-02-02 15:19:22
    内存映射文件技术1. 用途和基本操作用于不同进程之间的内存共享操作, 可以将一个物理文件映射到内存当中然后直接利用分配到的或者打开的命名共享内存的地址空间实现资源共享访问2. 相关流程1) 新建命名共享内存首先...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 312,828
精华内容 125,131
关键字:

操作系统内存映射文件