分享

深入理解内存映射:高效处理大型文件和数据

 深度Linux 2024-05-23 发布于湖南

内存映射是一种操作系统提供的机制,它将磁盘文件或其他设备的数据映射到进程的虚拟地址空间中的一段连续内存区域。通过内存映射,这些数据可以像访问内存一样直接读取和写入,而无需通过传统的读写操作进行IO访问。

具体而言,内存映射允许将一个文件或设备上的数据块作为一个连续的内存区域来对待。当应用程序需要访问该文件或设备时,它可以直接从该内存区域中读取或写入数据,而不需要通过使用read()、write()等系统调用进行IO操作。

一、内存映射系统调用mmap

1.1mmap系统调用概述

mmap 是实现内存映射的关键系统调用。它创建了文件内容和进程地址空间之间的直接映射,使得文件的一部分或全部可以直接映射到进程的地址空间中。这样,文件的读写就变得像内存访问一样高效。

在继续深入探索之前,我们需要理解,技术的每一次进步都是对人类思维方式的挑战和扩展。内存映射不仅仅是一种技术手段,它更是一种思维方式的革新。如同爱因斯坦所言:“逻辑会带你从 A 点到 B 点,想象力会带你到任何地方。” 当我们学习和应用内存映射这样的技术时,我们不仅是在学习一种新的编程技能,更是在扩展我们对计算和数据处理的整体理解。

1.2mmap的系统调用

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

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

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

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

参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。

flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

系统调用mmap()用于共享内存的两种方式:

(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

fd=open(name, flag, mode);
if(fd<0
...

ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。

(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

系统调用munmap()

int munmap( void * addr, size_t len )

该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

系统调用msync()

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

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

1.3mmap 系统调用和直接使用IPC共享内存之间的差异

mmap系统调用用于将文件映射到进程的地址空间中,而共享内存是一种不同的机制,用于进程间通信。这两种方法都用于数据共享和高效的内存访问,但它们有一些关键区别:

(1)数据源和持久化

  • mmap: 通过 mmap 映射的数据通常来自文件系统中的文件。这意味着数据是持久化的——即使程序终止,文件中的数据依然存在。当你通过映射的内存区域修改数据时,这些更改最终会反映到磁盘上的文件中。

  • 共享内存:共享内存是一块匿名的(或者有时与特定文件关联的)内存区域,它可以被多个进程访问。与 mmap 映射的文件不同,共享内存通常是非持久的,即数据仅在计算机运行时存在,一旦系统关闭或重启,存储在共享内存中的数据就会丢失。

(2)使用场景

  • mmap:mmap 特别适合于需要频繁读写大文件的场景,因为它可以减少磁盘 I/O 操作的次数。它也允许文件的一部分被映射到内存中,这对于处理大型文件尤为有用。

  • 共享内存:共享内存通常用于进程间通信(IPC),允许多个进程访问相同的内存区域,这样可以非常高效地在进程之间交换数据。

(3)性能和效率

  • mmap:映射文件到内存可以提高文件访问的效率,尤其是对于随机访问或频繁读写的场景。系统可以利用虚拟内存管理和页面缓存机制来优化访问。

  • 共享内存:共享内存提供了一种非常快速的数据交换方式,因为所有的通信都在内存中进行,没有文件 I/O 操作。

(4)同步和一致性

  • mmap:使用 mmap 时,必须考虑到文件内容的同步问题。例如,使用 msync 调用来确保内存中的更改被同步到磁盘文件中。

  • 共享内存:在共享内存的环境中,进程需要使用某种形式的同步机制(如信号量、互斥锁)来避免竞争条件和数据不一致。

二、存储映射I/O

在现在的项目中需要用到mmap建立内存映射文件,顺便把存储映射I/O看了一下,这个东西还真是加载索引的良好工具,存储映射I/O可以使一个磁盘文件与存储空间中的一个缓冲区相映射,这样可以从缓冲区中读取数据,就相当于读文件中的相应字节,而当将数据存入缓冲区时,最后相应字节就自动写入文件中。

利用mmap建立内存映射文件一般会分为两条线:写文件,读文件,在分别介绍这两条线之前首先将存储映射I/O的常用函数介绍一下。

2.1存储映射I/O基本函数

(1) mmap函数, 这个函数会告诉内核将一个给定的文件映射到一个存储区域中,其函数原型为:

void* mmap(void *addr,size_t len,int prot,int flags,int fields,off_t off);

其中,参数addr用于指定存储映射区的起始地址,通常设定为0,这表示由系统选择该映射区的起始地址,参数len是指定映射的字节数,参数port指定映射区的方式,如PROT_READ,PROT_WRITE,值得注意的是映射区的保护不能超过文件open模式访问权限。参数flags是设置映射区的属性,一般设为MAP_SHARED,这一标志说明本进程的存储操作相当于文件的write操作,参数fields是指定操作的文件描述符,参数off是要映射字节在文件中的起始偏移量。如果函数调用成功,函数的返回值是存储映射区的起始地址;如果调用失败,则返回MAP_FAILED。

(2) msync函数,这个函数会将存储映射区的修改冲洗到被映射的文件中,其函数原型为:

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

其中,参数flags参数设定如何控制冲洗存储区,可以选择MS_ASYNC,这表明是异步操作,函数调用立即返回,而选择MS_SYNC,函数调用则等待写操作完成后才会返回。

(3) munmap函数,这个函数会解除文件和存储映射区之间的映射。

int munmap(caddr_t addr,size_t len)

2.2写入映射缓冲区

当我们想向映射缓冲区中写入数据时,首先需要确定映射文件的大小,在打开文件后,可以利用修改文件大小的函数重新设定文件的大小,接下来就可以对该缓冲区进行写操作。

int fd = open(file_name,O_RDWR|O_CREAT);
ftruncate(fd,size);
mmap(0,size,PROT_WRITE,MAP_SHARED,fd,0);

2.3从映射缓冲区读取

当我们想从映射缓冲区中读取数据时,需要利用stat系列函数得到文件大小,进行利用在映射存储区中打开该文件。

int fd = open(file_name,O_RDONLY);
struct stat stat_buf;
fstat(fd,&stat_buf);
void *data = mmap(0,stat_buf.st_size,PROT_READ,
MAP_SHARED,fd,0);

2.4实例:用存储映射 I/O 复制文件

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

#define COPYINCR (1024*1024*1024) /* 1 GB */

int
main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
size_t copysz;
struct stat sbuf;
off_t fsz = 0;

if (argc != 3)
err_quit("usage: %s <fromfile> <tofile>", argv[0]);

if ((fdin = open(argv[1], O_RDONLY)) < 0)
err_sys("can't open %s for reading", argv[1]);

if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC,
FILE_MODE)) < 0)
err_sys("can't creat %s for writing", argv[2]);

if (fstat(fdin, &sbuf) < 0) /* need size of input file */
err_sys("fstat error");

if (ftruncate(fdout, sbuf.st_size) < 0) /* set output file size */
err_sys("ftruncate error");

while (fsz < sbuf.st_size) {
if ((sbuf.st_size - fsz) > COPYINCR)
copysz = COPYINCR;
else
copysz = sbuf.st_size - fsz;

if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED,
fdin, fsz)) == MAP_FAILED)
err_sys("mmap error for input");
if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE,
MAP_SHARED, fdout, fsz)) == MAP_FAILED)
err_sys("mmap error for output");

memcpy(dst, src, copysz); /* does the file copy */
munmap(src, copysz);
munmap(dst, copysz);
fsz += copysz;
}
exit(0);
}

三、内存映射

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()来强制同步, 这样所写的内容就能立即保存到文件里了。

3.1内存映射分类

按文件分:

  • 文件映射:将一个文件的一部分直接映射到调用进程的虚拟内存中

  • 匿名映射:没有对应文件,被映射的分页会初始化为0

按权限分:

  • 私有映射:写时复制,变更不会再底层文件进行

  • 共享映射:变更发生在底层文件

将上面两两组合:

  • 私有文件映射:使用一个文件的内容来初始化一块内存区域

  • 私有匿名映射:为一个进程分配新的内存

  • 共享文件映射:代替 read() 和 write() 、IPC

  • 共享匿名映射:实现相关进程实现类似于共享内存

进程执行 exec() 时映射会丢失,但通过 fork() 的子进程会继承映射

3.2API函数

(1)创建一个映射

#include <sys/mman.h>
void *mmap( void *addr, size_t length, int prot, int flags, int fd, off_t offset );

成功返回新映射的起始地址,失败返回 MAP_FAILED。

参数 addr:映射被放置的虚拟地址,推荐为NULL(内核会自动选择合适地址)
参数 length:映射的字节数
参数 prot:位掩码,可以取OR

违反了保护信息,内核会向进程发送SIGSEGV信号。

  • PROT_NONE:区域无法访问,可以作为一个进程分配的起始位置或结束位置的守护分页

  • PROT_WRITE:区域内容可修改

  • PROT_READ:区域内容可读取

  • PROT_EXEC:区域内容可执行

参数 flags:位掩码,必须包含下列值中的一个
MAP_PROVATE:创建私有映射
MAP_SHARED:创建共享映射

参数 fd:被映射的文件的文件描述符(调用之后就能够关闭文件描述符)。在打开描述符 fd 引用的文件时必须要具备与 prot 和 flags参数值匹配的权限。特别的,文件必须总是被打开允许读取。

参数 offset:映射在文件中的起点

(2)解除映射区域

#include <sys/mman.h>
int munmap( void *addr, size_t length );
  • 参数 addr:待解除映射的起始地址

  • 参数 length:待解除映射区域的字节数

可以解除一个映射的部分映射,这样原来的映射要么收缩,要么被分成两个,这取决于在何处开始解除映射。还可以指定一个跨越多个映射的地址范围,这样的话所有在范围内的映射都会被解除。

(3)同步映射区域

#include <sys/mman.h>
int msync( void *addr, size_t length, int flags );

参数 flags:

  • MS_SYNC:阻塞直到内存区域中所有被修改过的分页被写入磁盘

  • MS_ASYNC:在某个时刻被写入磁盘

(4)重写映射一个映射区域

#define _GNU_SOURCE
#include <sys/mman.h>
void *mremap( void *old_address, size_t old_size, size_t new_size, int fflags, ... );

参数 old_address 和 old_size 指既有映射的位置和大小。

参数 new_size 指定新映射的大小

参数 flags:
0
MREMAP_MAYMOVE:为映射在进程的虚拟地址空间中重新指定一个位置
MREMAP_FIXED:配合 MREMAP_MAYMOVE 一起使用,mremap 会接收一个额外的参数 void *new_address

(5)创建私有文件映射

创建一个私有文件映射,并打印文件内容

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main( int argc, char **argv )
{
int fd = open( argv[1], O_RDONLY );
if( fd == -1 ) {
perror("open");
}

/*获取文件信息*/
struct stat sb;
if( fstat( fd, &sb ) == -1 ) {
perror("fstat");
}

/*私有文件映射*/
char *addr = mmap( NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
if( addr == MAP_FAILED ) {
perror("mmap");
}

/*将addr的内容写到标准输出*/
if( write( STDOUT_FILENO, addr, sb.st_size ) != sb.st_size ) {
perror("write");
}

exit( EXIT_SUCCESS );
}

(6)创建共享匿名映射

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>



int main( int argc, char **argv )
{
/*获取虚拟设备的文件描述符*/
int fd = open( "/dev/zero", O_RDWR );
if( fd == -1 ) {
perror("open");
}

int *addr = mmap( NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
if( addr == MAP_FAILED ) {
perror("mmap");
}

if( close( fd ) == -1 ) {
perror("close");
}

*addr = 1;

switch( fork() ) {
case -1:
perror("fork");
break;
case 0:
printf("child *addr = %d\n", *addr);
(*addr)++;

/*解除映射*/
if( munmap(addr, sizeof(int)) == -1 ) {
perror("munmap");
}
_exit( EXIT_SUCCESS );
break;
default:
/*等待子进程结束*/
if( wait(NULL) == -1 ) {
perror("wait");
}

printf("parent *addr = %d\n", *addr );
if( munmap( addr, sizeof(int) ) == -1 ) {
perror("munmap");
}
exit( EXIT_SUCCESS );
break;
}
}

案例:分块内存映射处理大文件

内存映射文件可以用于3个不同的目的

· 系统使用内存映射文件,以便加载和执行. exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。

· 可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存。

· 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Windows确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。

使用内存映射数据文件

若要使用内存映射文件,必须执行下列操作步骤:

1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件。

2) 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件。

3) 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中。

当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:

  • 1) 告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像。

  • 2) 关闭文件映射内核对象。

  • 3) 关闭文件内核对象。

文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。所以可以使用内存文件映射来处理数据,网上也有铺天盖地的文章,但是映射大文件的时候又往往会出错,需要进行文件分块内存映射,这里就是这样的一个例子,教你如何把文件分块映射到内存。

//
// 该函数用于读取从CCD摄像头采集来的RAW视频数据当中的某一帧图像,
// RAW视频前596字节为头部信息,可以从其中读出视频总的帧数,
// 帧格式为1024*576*8
/*
参数:
pszPath:文件名
dwFrame: 要读取第几帧,默认读取第2帧
*/
BOOL MyFreeImage::LoadXRFrames(TCHAR *pszPath, DWORD dwFrame/* = 2*/ )
{

// get the frames of X-Ray frames
BOOL bLoop = TRUE;
int i;
int width = 1024;
int height = 576;
int bitcount = 8; //1, 4, 8, 24, 32

//
//Build bitmap header
BITMAPFILEHEADER bitmapFileHeader;
BITMAPINFOHEADER bitmapInfoHeader;
BYTE rgbquad[4]; // RGBQUAD
int index = 0;

DWORD widthbytes = ((bitcount*width + 31)/32)*4; //每行都是4的倍数 DWORD的倍数 这里是 576-
TRACE1("widthbytes=%d\n", widthbytes);

switch(bitcount) {
case 1:
index = 2;
bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2*4);
break;
case 4:
index = 16;
bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*4);
break;
case 8:
index = 256;
bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD));
break;
case 24:
case 32:
index = 0;
bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
break;
default:
break;
}

//构造Bitmap文件头BITMAPFILEHEADER
bitmapFileHeader.bfType = 0x4d42; // 很重要的标志位 BM 标识
bitmapFileHeader.bfSize = (DWORD)(bitmapFileHeader.bfOffBits + height * widthbytes); //bmp文件长度
bitmapFileHeader.bfReserved1 = 0;
bitmapFileHeader.bfReserved2 = 0;

//构造Bitmap文件信息头BITMAPINFOHEADER
bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfoHeader.biWidth = width;
bitmapInfoHeader.biHeight = height;
bitmapInfoHeader.biPlanes = 1;
bitmapInfoHeader.biBitCount = bitcount;
bitmapInfoHeader.biCompression = BI_RGB; // 未压缩
bitmapInfoHeader.biSizeImage = height * widthbytes;
bitmapInfoHeader.biXPelsPerMeter = 3780;
bitmapInfoHeader.biYPelsPerMeter = 3780;
bitmapInfoHeader.biClrUsed = 0;
bitmapInfoHeader.biClrImportant = 0;

//创建BMP内存映像,写入位图头部
BYTE *pMyBmp = new BYTE[bitmapFileHeader.bfSize]; // 我的位图pMyBmp
BYTE *curr = pMyBmp; // curr指针指示pMyBmp的位置
memset(curr, 0, bitmapFileHeader.bfSize);

//写入头信息
memcpy(curr, &bitmapFileHeader,sizeof(BITMAPFILEHEADER));
curr = pMyBmp + sizeof(BITMAPFILEHEADER);
memcpy(curr, &bitmapInfoHeader,sizeof(BITMAPINFOHEADER));
curr += sizeof(BITMAPINFOHEADER);

//构造调色板 , 当像素大于8位时,就没有调色板了。
if(bitcount == 8)
{
rgbquad[3] = 0; //rgbReserved
for(i = 0; i < index; i++)
{
rgbquad[0] = rgbquad[1] = rgbquad[2] = i;
memcpy(curr, rgbquad, sizeof(RGBQUAD));
curr += sizeof(RGBQUAD);
}
}else if(bitcount == 1)
{
rgbquad[3] = 0; //rgbReserved
for(i = 0; i < index; i++)
{
rgbquad[0] = rgbquad[1] = rgbquad[2] = (256 - i)%256;
memcpy(curr, rgbquad, sizeof(RGBQUAD));
curr += sizeof(RGBQUAD);
}
}

//
// 文件映射,从文件中查找图像的数据
//Open the real file on the file system
HANDLE hFile = CreateFile(pszPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DWORD dwError = GetLastError();
ATLTRACE(_T("MapFile, Failed in call to CreateFile, Error:%d\n"), dwError);
SetLastError(dwError);
bLoop = FALSE;
return FALSE;
}

//Create the file mapping object
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hMapping == NULL)
{
DWORD dwError = GetLastError();
ATLTRACE(_T("MapFile, Failed in call to CreateFileMapping, Error:%d\n"), dwError);

// Close handle
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}

SetLastError(dwError);
bLoop = FALSE;
return FALSE;
}

// Retrieve allocation granularity
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;

// Retrieve file size
// Retrieve file size
DWORD dwFileSizeHigh;
__int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
qwFileSize |= (((__int64)dwFileSizeHigh) << 32);

CloseHandle(hFile);

// Read Image
__int64 qwFileOffset = 0; // 偏移地址
DWORD dwBytesInBlock = 0, // 映射的块大小
dwStandardBlock = 100* dwAllocationGranularity ; // 标准块大小
DWORD dwFrameSize = height*width; // 计算一帧图像的数据量,不包括头部信息
DWORD dwCurrentFrame = 1;

dwBytesInBlock = dwStandardBlock;
if (qwFileSize < dwStandardBlock)
dwBytesInBlock = (DWORD)qwFileSize;

//Map the view
LPVOID lpData = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS,
static_cast<DWORD>((qwFileOffset & 0xFFFFFFFF00000000) >> 32), static_cast<DWORD>(qwFileOffset & 0xFFFFFFFF), dwBytesInBlock);
if (lpData == NULL)
{
DWORD dwError = GetLastError();
ATLTRACE(_T("MapFile, Failed in call to MapViewOfFile, Error:%d\n"), dwError);

// Close Handle
if (hMapping != NULL)
{
CloseHandle(hMapping);
hMapping = NULL;
}
SetLastError(dwError);
bLoop = FALSE;
return FALSE;
}


BYTE *lpBits = (BYTE *)lpData;
BYTE *curr1, *curr2, *lpEnd;
curr1 = lpBits; // seek to start
curr2 = lpBits + 596; // seek to first frame
lpEnd = lpBits + dwBytesInBlock; // seek to end

// Read video infomation
KMemDataStream streamData( curr1, dwBytesInBlock);
ReadXRHeader(streamData);

while(bLoop)
{
DWORD dwTmp = lpEnd - curr2; //内存缓冲剩余的字节
if ( dwTmp >= dwFrameSize )
{
if(dwCurrentFrame == dwFrame)
{
memcpy(curr, curr2, dwFrameSize);
bLoop = FALSE;
}
curr2 += dwFrameSize;
}else //内存中不够一帧数据
{
DWORD dwTmp2 = dwFrameSize - dwTmp; // 一副完整的帧还需要dwTmp2字节

if (dwCurrentFrame == dwFrame)
{
memcpy(curr, curr2, dwTmp);
curr += dwTmp;
}


//1、首先计算文件的偏移位置
qwFileOffset += dwBytesInBlock;

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多