CS 321 Spring 2013  >  Lecture Notes for Friday, March 1, 2013

CS 321 Spring 2013
Lecture Notes for Friday, March 1, 2013

Dynamic Allocation (cont’d)

See the February 27 lecture notes.

Virtual Memory

Ideas

In modern virtual memory, logical memory is divided into equal-sized pages. Traditionally a page is 4K, but this may vary. Memory is allotted to a process, swapped out to disk, marked as copy-on-write, etc., individually for each page.

Pages are allotted to processes using memory mapping, which means establishing a correspondence between three things:

Memory mapping is a very versatile facility. In particular, once we are able to establish the above correspondence, all we need to do is give a name to the file, and we can do memory-mapped I/O: treating the contents of a file as an array of bytes, so that read and writing the file can be done simply by reading and writing memory in the usual way.

In modern virtual memory, address translation is done by means of a page table. This stores information on each page. For example, it might store the page’s permissions, whether it is present in memory, does it correspond to a named file, what is its copy-on-write status, etc.

A page table could be simply an array with one item for each page. However, as memory sizes grow, this does not scale well. A multi-level page table stores the table as an array of pointers to arrays. If one of the second-level arrays is not used, then it does not need to be allocated. An inverted page table stores only entries for allocated pages.

All of these methods reduce the space required by the page table, but increase the time required to access it. To speed this up, some entries in the page table are cached for fast access, in the translation lookaside buffer (TLB).

When the processor accesses a memory location, we would like the page’s entry to be in the TLB. If it is not, but is in memory, then we have a soft miss. If the page is not in memory at all, then we have a hard miss, also called a page fault; the page must then be loaded from disk.

Using mmap

The *ix system call that does memory mapping is mmap. This call, along with its companion munmap and associated constants, is declared in the header <sys/mman.h>.

An mmap call is always considered to establish a correspondence with a file. When we want some plain old memory—i.e., when we are not doing memory-mapped I/O—we map to an “unnamed file”. (The documentation says this unnamed file is /dev/zero. That strikes me as a meaningless statement; I have no idea why the documentation says that.)

Function mmap has 6 parameters.

Address
A (void *) giving the logical address to use. This should be a multiple of the page size. By default this is allowed to be ignored. Make it zero if you do not care (and you almost always do not care). See the file & sharing flags for more information.
Size
A size_t telling how much memory to map.
Access flags
The permissions requested for the mapped memory. This should be the bitwise-OR of one or more of the following: PROT_READ, PROT_WRITE, PROT_EXEC.
File & sharing flags
The bitwise-OR of various flags. You must include exactly one of the following two: MAP_SHARED, MAP_PRIVATE. These are strangely named; MAP_SHARED is normal operation, while MAP_PRIVATE establishes a copy-on-write mapping. Include MAP_ANONYMOUS if you are not mapping a named file. Include MAP_FIXED if you wish to specify the logical address for the mapping, using the first parameter (this is not recommended).
File descriptor
If you are mapping a named file, then this should be the file descriptor for an open file. Otherwise, make this (-1).
Offset
The byte offset in the file at which you want the mapping to start. This should be a multiple of the page size. Make it zero when using an unnamed file.

The return value of mmap is a (void *) giving the logical address of the mapped memory, or the special value MAP_FAILED if the call failed.

So a “normal” call to mmap, using an unnamed file, might look something like this.

[C++]

const size_t MEMSIZE = 1000;
char * p = (char *)mmap(0, MEMSIZE,
                        PROT_READ | PROT_WRITE,
                        MAP_SHARED | MAP_ANONYMOUS,
                        -1, 0);
if (p == MAP_FAILED)
{
    ...

Function munmap deletes a mapping created by mmap. It has two parameters: address (the value returned by mmap) and size (the second parameter of mmap). It returns zero on success, nonzero on failure.

[C++]

int failure = munmap(p, MEMSIZE);
if (failure)
{
    cout << "munmap failed and I have no idea what to do";
    ...

We can do memory-mapped I/O by mapping a named file. We must first open the file (with the open system call). On success this call returns a file descriptor, which we pass to mmap. We also leave MAP_ANONYMOUS out of the file & sharing flags.

The call to open should request the permissions (read, write) needed to do the mmap call. For example, if the call to mmap requests write access to the mapped memory, then the call to open should request write access to the file.

If the open and the map both succeed, then we must break the map (with munmap) and then close the file (with close). Thus, if all goes well, we do the following sequence of operations.

The result is a read or write to the file, without ever using a read or write system call.

[C++]

int fd = open("myfile.txt", O_RDONLY);
if (fd == -1)
{
    // ERROR
    ...
    return;
}

size_t MEMSIZE = 10000;
off_t offset = 4096;
char * p = (char *)mmap(0, MEMSIZE,
                        PROT_READ,  // We are only reading the file
                        MAP_SHARED,
                        fd, OFFSET);
if (p == MAP_FAILED)
{
    // ERROR
    ...
    return;
}

// Do some I/O by treating p as an array of 10,000 bytes.
// And then eventually:

int failure = munmap(p, MEMSIZE);
...
close(fd);


CS 321 Spring 2013: Lecture Notes for Friday, March 1, 2013 / Updated: 6 May 2013 / Glenn G. Chappell / ggchappell@alaska.edu