CS 321 Spring 2013 > Lecture Notes for Friday, March 1, 2013 |
See the February 27 lecture notes.
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.
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.
(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_t
telling how much memory
to map.PROT_READ
, PROT_WRITE
, PROT_EXEC
.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).(-1)
.
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.
open
.mmap
.munmap
.close
.
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);
ggchappell@alaska.edu