Memory Mapped I/O |
Memory mapped operations are useful because they are more efficient than the alternative and also save on file descriptors.
Consider the case of a file opened for reading by two processes. If the processes use read calls, then on a typical machine, the file is read from disk into a buffer cache. Then two in memory copy operations are required, one for each process to copy the data into the address space of each process. There will be three copies of the data in memory.
If the two processes use memory mapping instead, then the mapping is made, but no copying is done. Instead when one or the other process requires the data, a page fault occurs and at that time a single copy is obtained off disk, which can be used by either process. Equally significant is that access to the data is through user functions, not through system calls (read/write) which are much slower.
Memory mapping is used for dynamic libraries.
On a Solaris system,
truss
ing almost
any program will show numerous mmap( )
calls.
Use strace
instead of
truss
on Linux systems.
Shared memory is a related idea;
it is used as a form of interprocess communication;
the system calls used are
shmget( )
,
shmctl( )
,
shmat( )
, and
shmdt( )
but should be used by careful experienced programmers only.
The basic idea of memory mapping is to map a byte range of a file
to a range of process addresses. The system calls used are
mmap( )
,
munmap( )
,
mprotect( )
and a number of
calls in the
memcpy
family,
which are described below.
On BSD systems bcopy( )
is used. See
BSD porting notes.
For a good example of the use of
mmap
and
memcpy
to copy
a file, check page 412, Chapter 12.9 of the reference.
Note that read
and
write
are atomic, but memory ops are not.
Any change made by one process is immediately seen by any other.
Also no file locking is possible.
mmap - map pages of memory
#include <sys/mman.h>
addr =
void *mmap
(void *addr /* Specify NULL */,
size_t len, int prot, int flags, int fd, off_t off /* 0 for start of file */);
addr
and
len
give a (memory) address range
[addr, addr + len)
, where
len
is rounded
up to a multiple of the page size.
To obtain the page size use:
#include <unistd.h>
sysconf(_SC_PAGESIZE); /* or _SC_PAGE_SIZE */
If the file is less than a full page then
rest is zero byte filled and ignored - will not be written to the file.
addr
is usually specified as NULL or 0 which lets the system determine
where to position the memory.
off
determines the (file) byte range
[off, off + len)
for the file given by
file descriptor fd
.
Note that a bus or segmentation error will occur if an attempt is
made to reference addresses beyond the end of the file; do not
use mmap
to implicitly extend a file (append to a file) but instead
lseek
to the desired end and write a byte,
then mmap
the file.
prot
is the protection which is
PROT_READ
(readable),
PROT_WRITE
(writable),
PROT_EXEC
(executable) or
PROT_NONE
(inaccessible) or some ORed combination.
flag
should be either
MAP_SHARED
(share changes will all other processes
that map this file) or
MAP_PRIVATE
(a private copy-on-write mapping).
For the MAP_PRIVATE
case, each page is not private and will reflect
the current state of the file until the first write is attempted on that
page; any changes will never effect the underlying file.addr
is the memory address, which subsequent code will
need to use. Thus file range
[off, off + len)
maps to process address space
[addr, addr + len)
unless
MAP_FAILED
(-1) is returned through
addr
.
Closing the file descriptor does not remove the mapping as mmap
increments the reference count to the file; instead use
munmap
or terminate the process.
munmap - unmap pages of memory
munmap
(void *addr, size_t len);
mprotect - set protection of memory mapping
mprotect
(void *addr, size_t len, int prot);
prot
.
msync - synchronize memory with physical storage
msync
(void *addr, size_t len, int flags);
memccpy, memchr, memcmp, memcpy,
memmove, memset - memory operations
from
<string.h>
void *memccpy
(void *dest, const void *src, int c, size_t n);
void *memchr
(const void *s, int c, size_t n);
int memcmp
(const void *s1, const void *s2, size_t n);
void *memcpy
(void *dest, const void *src, size_t n);
void *memmove
(void *dest, const void *src, size_t n);
void *memset
(void *s, int c, size_t n);
c
is interpreted as an unsigned char throughout.
memccpy
copies no more than
n
bytes from
memory area src
into memory area
dest
, stopping after the
first c
is found.
It returns a pointer to the byte
after the copy of c
in
dest
, or NULL if
c
was not
found in the first n
bytes of
src
.
memchr
returns a pointer to the first occurrence of
c
in the first n
bytes of memory area
s
or
NULL if c
does not occur.
memcmp
compares the first
n
bytes of memory areas
s1
and
s2
.
It returns an integer less than, equal to, or greater than 0, according as
s1
is lexicographically less than, equal to, or greater than
s2
.
memcpy
and
memmove
copy
n
bytes from memory area
src
to
dest
,
and return dest
.
For memcpy
memory areas may not overlap.
Use memmove
if memory areas overlap.
memset
fills the first
n
bytes in memory area
s
to the
value of c
and returns s
.
Last update: 2000 December 29