Last changed 18 February 2002
Home | Papers | Object Pascal | Memory Mapping in Object Pascal
 

Memory Mapping in Object Pascal

Abstract

Memory mapping files uses the operating system's virtual memory capabilities to provide rapid access to files without allocating in-memory buffers. File contents appear as a region of memory and are accessed by dereferencing an ordinary pointer.

Introduction

Object Pascal provides a number of very useful facilities for operating on files. Files may be accessed by character, line, fixed-length records, or one of the most popular methods: file streams. Memory mapping files provides an additional technique that one may find optimal under certain circumstances. This paper will give a brief introduction to memory mapping and discuss a cross-platform object and interface for mapping files on both Linux and Windows.

Scope of Discussion

Most developers have a general understanding of virtual memory. Many people usually associate virtual memory with the CPU's ability to access more address space than physically exists. Virtual memory has many other interesting facets which are beyond the scope of this paper.

Memory Mapping Overview

The term memory mapping files refers to the ability of an operating system to redirect memory access to a section of a file on disk. A program sees a mapped file as a pointer. The valid region of memory that the pointer points to corresponds directly to the bytes in the mapped file. Reading and writing memory relative to the pointer reads and writes the corresponding bytes in the mapped file. Figure one depicts the file foo.txt mapped into a process' memory at location $0123000.

Figure 1

Uses of Memory Mapping, or "Why Should I Care?"

Memory mapping has some distinct strengths that one ought to keep in mind when choosing a file access method.

  1. Memory mapping provides highly efficient reads and writes. To access a part of a file, the operating system pages data into memory on demand. The OS performs no extraneous memory-to-memory copying.
  2. One may operate on files that may be orders of magnitude larger than physical memory and swap space combined as if they were a single huge in-memory array. This frees the programmer from having to seek, read, write, and otherwise manage a limited memory pool.

As with everything, there are drawbacks to consider.

  1. Memory mapping is not suited to files that change size. One should use streams and related techniques for this.
  2. Memory mapping can operate on files of any size (subject to the file system's limiations). If the file is bigger than addressible memory (roughly 2GB on the Intel IA32 architecture) then one must take care to position the mapping over interesting regions of the file. This produces a loss of convenience. Be sure to flush and remap as needed.

Memory Mapping Under Linux

Mapping and unmapping a file is operating-system dependent. This section describes the system calls requried to perform memory mapping with Kylix.

Mapping a File

  1. Ensure that the file exists, and store the file's size.
      uses Libc;
      . . .
      const
        INVALID_HANDLE = -1;
      . . .
        sb :TStatBuf;
        FSize :Integer;
        FData :Pointer;
      . . .
      if stat(PChar(FileName), sb)=0 then
        FSize := sb.st_size
      else
        ... // Fail; can't stat (retrieve information about) the file.
      
  2. Open a file descriptor (low-level file handle).

    Note that the open() system call determines whether the mapping is readable and/or writable. The parameters passed to mmap() must not conflict with the access requested during open(). See the man pages for mmap() for more information.

      fd := open(PChar(FileName), O_RDWR);
      if fd=INVALID_HANDLE then ... // Fail; cannot open file
      
  3. Map the file using mmap() . Note that one needs to supply the size of the file as the number of bytes to map.
        FData :Pointer;
      . . .
      FData := mmap(nil, FSize, PROT_READ or PROT_WRITE, MAP_SHARED, fd, 0);
      
      if FData=MAP_FAILED then ... // Fail; cannot create file mapping
      

Unmapping a File

When one is done with a mapped file, one must unmap the file.

  1. If changes were made the mapped file, flush the buffers to ensure the OS writes the data to disk.
      msync(FData, FSize, MS_SYNC);
      
  2. Unmap the file. Note! One ought to supply the same pointer and size obtained from the mapping unless one knows what one is doing!
      munmap(FData, FSize);
      
  3. Close the file descriptor.
      __close((fd);
      

Memory Mapping Under Windows

Mapping and unmapping a file is operating-system dependent. This section describes the system calls requried to perform memory mapping with Delphi.

Mapping a File

  1. Ensure that the file exists, obtain a file handle, and store the file's size.
      . . .
        FSize :Integer;
        hFile :THandle;
        hMap :THandle;
      . . .
      //  Sanity check
      if not FileExists(FileName) then ... // Fail; File not found.
    
      //  Get Win32 file handle
      hFile := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
      if hFile=INVALID_HANDLE_VALUE then ... // Fail; Cannot open file.
    
      //  Get size of file
      FSize := Windows.GetFileSize(hFile, nil);
      
  2. Mapping the file requires two API calls, one to create the map, the other to bind the map to a pointer.
      //  Create memory map of file for direct access
      hMap := CreateFileMapping(hFile, nil, PAGE_READWRITE, 0, 0, nil);
      if hMap=INVALID_HANDLE_VALUE then ... // Fail; Cannot create file mapping.
    
      FData := MapViewOfFile(hMap, FILE_MAP_READ or FILE_MAP_WRITE, 0, 0, 0);
      if FData = nil then ... // Fail; Cannot map view of file.
      

Unmapping a File

When one is done with a mapped file, one must unmap the file.

  1. Unmap the file using the corresponding API functions in reverse order:
        if FData<>nil then  
        begin
          FlushViewOfFile(FData, 0); // This is needed if changes were made.
          UnmapViewOfFile(FData);
        end;
        if hMap<>INVALID_HANDLE_VALUE then CloseHandle(hMap);
      
  2. Close the file handle.
        if hFile <> INVALID_HANDLE_VALUE  then  CloseHandle( hFile );
      

Credits

Pat Felt Pointed out typos in this document.
Dennis Passmore Pointed out bad Windows bug.
Pointed out the fact that only files up to 2GB can be addressed by the sample code. This is due to the limitations of the Intel IA32 architecutre, and was something that I knew about but never made explicit. Comment added to header to warn about this assumed knowledge.
Ray Lichner Pointed out small inefficiencies in the code.

Downloads

If one plans on using memory mapping, it's always nice to have an easy-to-use wrapper. Please e-mail me if you find any bugs so others may benefit!

The form
fMapExample.pas and fMapExample.xfm
The wrapper
MemoryMapper.pas
The project file
MappingExample.dpr

References


Version History:

2002-04-18
Fixed typos. Fixed bugs in code.
2002-04-17
Initial version.
Home | Papers | Object Pascal | Memory Mapping in Object Pascal
Copyright © 2002 James Knowles
All rights reserved.