假设使用标准的系统调用open()、read()和write()顺序读取磁盘文件,每次文件访问都需要系统调用和磁盘访问。或者采用虚拟内存技术,将文件I/O作为常规内存来访问。这种方法被称为内存映射文件,它允许一部分虚拟内存与文件逻辑关联,这将导致显著的性能提高。
基本机制
文件的内存映射是将每个磁盘块映射到一个或多个内存页。【/br/】一开始是按照页面调整的一般要求进行文件访问,导致缺页错误。这样,文件的页面大小部分从文件系统读取到物理页面(有些系统可以选择一次读取多个页面大小的数据块)。今后,文件的读写将作为正常的内存访问来处理。通过内存中的文件操作,不采用系统调用read()和write()的开销,简化了文件的访问和使用。
请注意,写入内存映射文件不一定是立即(同步)写入磁盘文件。有些操作系统会定期检查一个文件的内存映射页是否被修改,从而选择是否更新到物理文件。当文件关闭时,所有内存映射数据将被写入磁盘,并从进程虚拟内存中删除。
有些操作系统只通过特定的系统调用来提供内存映射,通过标准的系统调用来处理所有其他的文件I/O。然而,一些系统选择存储映射文件,而不管它们是否被指定为存储映射。
以Solaris为例。如果某个文件被指定为内存映射(使用系统调用mmaP()),Solaris会将该文件映射到进程地址空。如果通过常见的系统调用(如open()、read()和write())打开和访问文件,Solaris仍然使用内存映射文件。但是,这个文件被映射到内核地址空。无论文件是如何打开的,Solaris都将所有文件I/O视为内存映射,以允许在高效的内存子系统中访问文件。
多个进程可以允许同一个文件的并发内存映射,从而允许数据共享。任何进程的写入都会修改虚拟内存中的数据,映射相同文件部分的其他进程都能看到。
根据关于虚拟内存的知识,我们可以清楚地看到如何共享内存映射部分:每个共享进程的虚拟内存映射都指向物理内存的同一个页面,这个页面有磁盘块的副本,如下图所示。
内存映射文件内存映射文件
内存映射系统调用也可以支持写入时复制功能,允许进程以只读模式共享文件或拥有它们修改的任何数据的副本。为了协调对共享数据的访问,相关进程可以使用一种机制来实现互斥。
很多时候,共享内存其实是通过内存映射来实现的。在这种情况下,进程可以通过共享内存进行通信,这是通过将同一个文件映射到通信进程的虚拟地址空来实现的。存储器映射文件充当通信进程之间的共享存储器区域。
共享内存Windows API通过内存映射文件的Windows API创建共享内存区域的一般过程如下:首先为要映射的文件创建一个文件映射,然后在进程虚拟地址空中建立映射文件的视图。另一个进程可以打开映射文件,并在虚拟地址空中创建它的视图。映射文件表示共享内存对象,以便进程可以通信。
接下来,将更详细地解释这些步骤。首先,生产者进程使用Windows API中的内存映射函数来创建共享内存对象。接下来,生成器将消息写入共享内存。然后,生产者进程打开到共享内存对象的映射,并读取生产者编写的消息。
为了创建内存映射文件,进程首先通过函数CreateFile()打开要映射的文件,并获取打开文件的句柄。接下来,该流程通过函数CreateFileMapping()创建该文件的映射。
一旦建立了文件映射,该进程就会通过MapViewOfFile()函数在虚拟地址空中创建映射文件的视图。映射文件的视图表示位于进程虚拟地址空之间的映射文件部分,它可以是整个文件,也可以是映射文件的一部分。下面显示的程序说明了这个顺序。(为了使代码简洁,这里省略了很多错误检查。)
# include & ltwindows.h & gt# include & ltstdio.h & gtint main(int argc,char *argv[]){ HANDLE hFile,hMapFileLPVOID lpMapAddresshFile = create file(& # 34;temp.txt & # 34,/*文件名*/ GENERIC_READ I GENERIC_WRITE,/*读/写权限*/ 0,/*不共享文件*/ NULL,/*默认安全性*/ OPEN_ALWAYS,/*打开新的或现有的文件*/ FILE_ATTRIBUTE_NORMAL,/*例程文件属性*/NULL);/* no file template */hMapFile = create file mapping(hFile,/* file handle */ NULL,/* default security */PAGE _ read write,/*对映射页面的读/写访问权限*/ 0,/* map整个文件*/ 0,TEXT(& # 34;SharedObject & # 34));/*命名共享内存对象*/lpMapAddress = MapViewOfFile(hMapFile,/*映射对象句柄*/ FILE_MAP_ALL_ACCESS,/*读/写访问*/ 0,/*整个文件的映射视图*/ 0,0);/*写入共享内存*/ sprintf(lpMapAddress,& # 34;共享内存消息& # 34;);UnmapViewOfFile(lpMapAddress);close handle(hFile);close handle(hMapFile);}调用CreateFileMapping()创建一个名为SharedObject的命名共享内存对象。消费者进程创建这个命名对象的映射,以便与这个共享内存段通信。接下来,生成器在其虚拟地址空中创建内存映射文件的视图。将0传递给最后三个参数表示映射的视图是整个文件。通过传递指定的偏移量和大小,以这种方式创建的视图只包含文件的一部分。请注意,建立映射后,可能不会将整个映射加载到内存中。映射文件可能会请求页面调整,因此所需的页面只有在被访问时才会被加载到内存中。MapViewOfFile()函数返回一个指向共享内存对象的指针,所以对这个内存位置的任何访问都是对共享内存文件的访问。在这个例子中,生产者进程发送消息& # 34;共享内存消息& # 34;写入共享内存。下面显示的程序说明了一个消费者进程如何创建一个命名共享内存对象的视图。这个程序比前一个程序简单,因为这个过程需要做的只是创建一个到现有命名共享内存对象的映射。消费者进程还必须创建映射文件的视图,就像前面程序的生产者进程一样。然后,消费者从共享内存中读取生产者进程编写的消息& # 34;共享内存消息& # 34;。调用CreateFileMapping()创建一个名为SharedObject的命名共享内存对象。消费者进程创建这个命名对象的映射,以便与这个共享内存段通信。
接下来,生成器在其虚拟地址空中创建内存映射文件的视图。将0传递给最后三个参数表示映射的视图是整个文件。通过传递指定的偏移量和大小,以这种方式创建的视图只包含文件的一部分。
请注意,建立映射后,可能不会将整个映射加载到内存中。映射文件可能会请求页面调整,因此所需的页面只有在被访问时才会被加载到内存中。
MapViewOfFile()函数返回一个指向共享内存对象的指针,所以对这个内存位置的任何访问都是对共享内存文件的访问。在这个例子中,生产者进程发送消息& # 34;共享内存消息& # 34;写入共享内存。
下面显示的程序说明了使用者进程如何创建命名共享内存对象的视图。这个程序比前一个程序简单,因为这个过程需要做的只是创建一个到现有命名共享内存对象的映射。消费者进程还必须创建映射文件的视图,就像前面程序的生产者进程一样。然后,消费者从共享内存中读取生产者进程编写的消息& # 34;共享内存消息& # 34;。
# include & ltwindows.h & gt
# include & ltstdio.h & gt
int main(int argc,char *argv[])
{
处理hMapFile
LPVOID lpMapAddress
hMapFile = openfile mapping(FILE _ MAP _ ALL _ ACCESS,/* R/W access */
FALSE,/*没有继承*/
文本(& # 34;SharedObj ect & # 34));/*映射文件对象的名称*/
lpMapAddress = MapViewOfFile(hmap file,/*映射的对象句柄*/
文件映射所有访问权限,/*读/写访问权限*/
0,/*整个文件的映射视图*/
0,
0);
/*从共享内存中读取*/
printf(& # 34;阅读邮件% s & # 34,lpMapAddress);
UnmapViewOfFile(lpMapAddress);
close handle(hMapFile);
}
最后,两个进程调用UnmapViewOfFile()来删除映射文件的视图。
内存映射 I/O
在I/O的情况下,每个I/O控制器包括用于存储命令和传输数据的寄存器。通常,专用I/O指令允许这些寄存器和系统存储器之间的数据传输。为了更方便地访问I/O设备,许多计算机体系结构都提供了内存映射I/O,
在这种情况下,一组内存地址被专门映射到设备寄存器。对这些存储器地址的读写导致数据被传输到器件寄存器或从器件寄存器中取出。这种方法适用于响应时间快的设备,如视频控制器。对于IBMPC,屏幕上的每个位置都映射到一个内存位置。在屏幕上显示文本几乎和将文本写到适当的内存映射位置一样简单。
内存映射I/O也适用于其他设备,如用于连接调制解调器和打印机的计算机串行端口和并行端口。通过读写这些设备寄存器(称为I/O端口),CPU可以向这些设备传输数据。
为了通过存储器映射的串行端口发送一长串字节,CPU将一个数据字节写入数据寄存器,并在控制寄存器中置位一位,以指示字节可用。器件读取数据字节,并将控制寄存器的指示位清零,以表示已准备好接收下一个字节。然后,CPU可以传输下一个字节。如果CPU使用轮询来监控控制位,并连续检查设备是否准备好,这种操作称为程序I/O。如果CPU不轮询控制位,但在设备准备好接收一个字节时收到中断,则数据传输称为中断驱动。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。