精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>编程开发>>C/C++>>技术精解:内存、进程、线程等>>Win98 内存管理>>Win98 内存管理(十)

主题:Win98 内存管理(十)
发信人: skyice()
整理人: skyice(2000-06-22 00:30:54), 站内信件
内存映射文件 I/O

  内存映射文件 I/O是 Win32 API 的一个特色,用以简化数据在磁盘文
件和某范围的内存地址之间的移动。当文件被映射到一个内存区段时,除虚
拟内存管理器处理数据缓冲外,从映射内存读取数据和从文件读取数据产生
同样的结果。写入映射的内存和写入磁盘文件也产生同样的结果,只是虚拟
内存管理器又一次处理了数据缓冲。
  内存映射文件 I/O 效率很高。由于使用了创建延迟(creative proc-
rastination),虚拟内存管理器不再做任何不必要的工作。第一次创建内
映射文件的视图时,预留了一个地址范围,但是并没有实际读取任何数据。
我们第一次遇到顶留内存范围的概念是在关于用 VirtualAlloc() 预留私
有地址范围的讨论中。使用内存映射文件时,访问为内存映射页保存的内存
位置会导致一个页故障,对此,虚拟内存管理器通过从磁盘读取页并重新执
行出错的指令做出响应。修改过的页只在虚拟内存管理器需要更多的页时(
或应用程序调用 Win32 的 FlushFileBuffers() API 强制写盘时)才会
写回磁盘。这种懒惰方法使内存和处理器的使用都降至最少。
  内存映射文件 I/O 允许两个(或更多)进程共享文件数据。每个涉及
共享的进程都可以直接访问通用页集合。因为共享只有很小的附加开销,所
以当有大量数据必须共享时,共享内存映射文件 I/O 会非常有用。而且因
为每个进程都直接访问数据,所以可以非常有效地处理高带宽共享。
  你不必非要用共享内存的办法来利用内存映射文件 I/O 的优势。可共
享的内存和实际上已经被两个或更多进程共享的内存有明显的区别。为内存
映射文件页设立的页是可共享的,也就是说,你可以共享它们,但是如果它
不符合你的要求,就不必共享。使用内存映射文件 I/O 来简化对磁盘文件
的访问而不热衷于共享不会产生任何问题。共享的能力只是一个你需要时就
可以使用的特色。
  内存映射文件支持需要三个 Windows KERNEL 对象: 文件对象、文件
映射对象和视图对象。(MFC 没有为这些对象提供包装。)要把一个文件映
射到内存,首先要打开文件,这将导致一个文件对象的创建。然后必须把文
件对象连接到一个内存映射文件对象,它提供向磁盘文件的逻辑连接,用以
使不同进程同步使用数据。然而,内存映射文件对象并没有提供指向数据的
指针。因此,还需要一个视图对象。视图对象提供一个指针,指向从文件来
的一些数据块。对一个内存映射文件对象,可以创建多个视图,这使进程可
以选择访问文件的特定部分。

创建文件对象

  有两个函数可以用来打开文件:一个来自Win16 API,一个来自Win32
API。虽然 Win16 函数更容易使用,但我们还是首先讨论Win32 函数。打
开文件的 Win32 函数是 CreateFile(),由于它将创建一个操作系统文件
对象的事实而得名。这个函数可以创建文件、打开文件,还可以打开其他操
作系统对象, 诸如通讯端口和命名的管道等。 打开文件的 Win16 函数是 
OpenFile()。我们喜欢这个函数是因为它带的参数很少,而且还有一个附加
的特性:可用于删除文件。它不象 CreateFile() 功能那么强大,那个函数
还可以打开其他类型的操作系统文件并设置文件和安全性属性。要打开普通
的磁盘文件,我们喜欢用OpenFile(),它多少简单地包装了对CreateFile()
的调用。要打开文件,我们更偏爱 OpenFile() 函数,其定义如下:
  HFILE WINAPI OpenFile(
    LPCSTR lpFileName,
    LPOFSTRUCT lpReOpenBuff,
    UINT uStyle);
  lpFileName 参数是一个文本字符串, 描述了要打开的文件的路径。
lpReOpenBuff 参数是一个指针,指向一个 OFSTRUCT 类型的结构。这个
结构的创建是为支持一种访问软盘文件所必需的文件 I/O 风格,这种风格
要求在大多数时间里,文件是关闭的。否则,如果用户改变了磁盘,整个软
盘文件系统就会被毁坏。实际上,在这个结构中,唯一有用的域是 fFixe-
dDisk,它指出软驱是否被打开了。尽管它的名称中有 “Fixed”,但它并
不检测 CD-ROM 驱动器,而是把它当作网络驱动器。在 Windows 包含文件
中,OFSTRUCT 的定义如下:
  typedef struct OFSTRUCT{
    BYTE cBytes;
    BYTE fFixedDisk;
    WORD nErrCode;
    WORD Reserved1;
    WORD Reserved2;
    CHAR szPathName[OFS_MAXPATHNAME];
  }OFSTRUCT,* LPOFSTRUCT,* POFSTRUCT;
  uStyle 参数包含文件打开标志。各种各样的标志指示着这块内存允许
怎样访问(OF_BEAD、OF_WRITE、OF_REZDWRITE),这个文件怎样和其他
进程共享(OF_SHARE_EXCLUSIVE、OF_SHARE_DENY_WRITE 等等),还有
要做的动作(OF_CREATE、OF_DELETE、OF_EXIST 等等)。完整的标志集
合见 Win32文档。下面是一个例子,调用这个函数打开一个文件:
  HFILE hfile;
  OFSTRUCT of;
  hfile = OpenFile("FILE.DAT",&of,
    OF_READ|OF_SHARE_EXCLUSIVE);
  if(hfile ==(HFILE)-1)
  {
    // Error opening file.
  }

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Windows 文件句柄错误处理
  谈到了文件的话题,我仍就要提到文件和其他类型的 Windows 对象之间
的一个不同点。检测打开文件时所发生错误的方法不同于检测创建其他 Win-
32 对象时所发生错误的方法。对于其他类型的对象,一个 NULL(0) 句柄值
表示失败。文件打开失败时,文件打开函数返回一个值为-1 的句柄值。这个
值用 INVALID_HANDLE_VALUE 表示,这是一个冗长的、但很具描述性的预处
理符号。一些开发人员更喜欢用这个符号硬编码来检查 -1 返回值。
  对还是Windows 编程新手的程序员来说,符号INVALID_HANDLE_VALUE
有两点令人困惑。第一,其名称暗示它通用于所有类型的句柄,而实际上,
它只用于文件句柄。第二,不存在表示其他类型的非法句柄的符号。在检测
其他类型对象的创建是否失败时,不要用这个值来验证,这样的对象包括:
窗口、菜单、对话框、光标和图标;GDI 对象,诸如画笔、画刷、字体、DC、
位图和区段;KERNEL 对象,诸如进程、线程或信号量。(作为对付这个不一
致性的方式,可以使用硬编码的 -1 来检测文件打开失败。
  你可能想知道为什么在 Win32 程序中打开文件的错误会产生一个为 -1
的句柄值,而这是历史造成的。Win32 API 提供这个特性以与 Win16 API 
向后兼容。但是你可能会惊奇,为什么 Win16 也按这种方式工作,在Win16
中假设 NULL 也是一个无效句柄值?答案是,Win16 程序(在 Windows3.x
及更早的版本下)依赖于 MS-DOS 执行文件 I/O,这样 Win16 程序必须处
理 MS-DOS 提供的东西,即返回 -1 值表示文件打开失败。这个故事还可以
继续,如果你考虑 MS-DOS 自己,它是由 Tim Patterson 在 Seattle C-
omputer 上完成的,和 CP/M 同出一辙,而 CP/M 本身使用 -1(实际上是
0xff)作为无效句柄值。最终的结果是运行在 Windows 98(还有 Windows
NT!)下的 Win32 程序以 19 世纪 70 年代中期建立的一个操作系统为基础
提供文件失败的返回值。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 
 
这是一个具有可比性的对Crl咖如1e()的调用:
  HFILE hfiIe;
  hfile = CreateFile(achFile,   // Filename
    GENERIC_READ,             // Access mode
    0,                        // Share
    0,                        // Security
    OPEN_EXISTING,            // Create flags
    FILE_ATTRIBUTE_NORMAL,    // File attributes
    0);                       // File emulate
  if(hfiIe == HFILE)-1)
  {
    // Error opening file
  }
  如果操作成功,OpenFile() 函数和它的相近函数 CreateFile() 都会
返回一个有效的 HFILE 文件句柄。要关闭这个文件,调用CloseHandle()。
使用这一个函数可以关闭许多不同类型的 KERNEL 对象,包括我们下面要讨
论的文件映射对象。然而,它不能用来关闭用户界面对象(诸如窗口、菜单、
光标和图标)或者GDI绘图对象(诸如此、画笔、画刷和字体)。不幸的是,
这个函数的非常具有普遍性的名称所暗示的广泛用途在 Win32 API中其实不
存在。

--
独人独剑独马
浪迹天涯 ...

※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.99.91.168]

[关闭][返回]