356 lines
10 KiB
C
356 lines
10 KiB
C
/*
|
|
mmap.c - memory mapped access to files
|
|
|
|
Copyright (C) Lumiera.org
|
|
2008, Christian Thaeter <ct@pipapo.org>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
/** @file mmap.c
|
|
** Implementation of management for memory mapped file regions
|
|
** @todo development in this area is stalled since 2010
|
|
*/
|
|
|
|
|
|
#include "include/logging.h"
|
|
#include "lib/safeclib.h"
|
|
|
|
#include "backend/mmap.h"
|
|
#include "backend/mmapcache.h"
|
|
#include "common/config.h"
|
|
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
LUMIERA_ERROR_DEFINE (MMAP_NWRITE, "Backing file not writable");
|
|
LUMIERA_ERROR_DEFINE (MMAP_SPACE, "Address space exhausted");
|
|
|
|
|
|
|
|
|
|
|
|
LumieraMMap
|
|
lumiera_mmap_init (LumieraMMap self, LumieraFile file, off_t start, size_t size)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
|
|
TODO ("enforce size instead using chunksize (rounded to page size) for parsing headers");
|
|
|
|
REQUIRE (self);
|
|
REQUIRE (file);
|
|
REQUIRE (start >= 0);
|
|
REQUIRE (size);
|
|
|
|
/**
|
|
* default size for the mmapping window.
|
|
* - 128MB on 32 bit arch
|
|
* - 2GB on 64 bit arch
|
|
*
|
|
* \par
|
|
* Maintaining the right[tm] mmapping size is a bit tricky:
|
|
* - We have the default mmap_window_size which will be backed off when address space gets exhausted
|
|
* - When a bigger size is requested we have to fulfil it
|
|
* - The last mmapped chunk of a file can be as small as possible when the file is readonly
|
|
* - When the file is writable, the last chunk should be rounded up to chunksize
|
|
* - All boundaries will be aligned up/down to chunk boundaries
|
|
* - Requests beyond the file end must ftruncate and map additional pages
|
|
* - Create the 'refmap' which contains a refcounter per chunk
|
|
*/
|
|
TODO("move the setdefaults somewhere else, backend_defaults.c or so");
|
|
#if SIZE_MAX <= 4294967295U
|
|
lumiera_config_setdefault ("backend.mmap.window_size = 134217728");
|
|
#else
|
|
lumiera_config_setdefault ("backend.mmap.window_size = 2147483648");
|
|
#endif
|
|
|
|
long long mmap_window_size = 0;
|
|
lumiera_config_number_get ("backend.mmap.window_size", &mmap_window_size);
|
|
|
|
LumieraFiledescriptor descriptor = file->descriptor;
|
|
|
|
int fd = lumiera_file_handle_acquire (file);
|
|
TRACE (mmap_dbg, "got fd %d", fd);
|
|
if (fd == -1)
|
|
goto efile;
|
|
|
|
void* addr = (void*)-1;
|
|
off_t begin = 0;
|
|
size_t length = 0;
|
|
size_t chunksize = lumiera_file_chunksize_get (file);
|
|
size_t bias = lumiera_file_bias_get (file);
|
|
|
|
REQUIRE(start >= (off_t)bias, "begin before first chunk");
|
|
|
|
TODO ("error here? or just map as asked for?");
|
|
ENSURE(chunksize);
|
|
|
|
|
|
/**
|
|
* Recovering address space strategies:
|
|
* mmap() will fail when too much memory got mmapped after some time which is then
|
|
* recovered in the following way
|
|
* 1. create a new mmap while the cachelimit is not reached.
|
|
* 2. All unused mmaps are kept in a mrucache, drop the oldest one.
|
|
* mmap() still fails..
|
|
* 3.a When the intended mmapping size is the same as mmap_window_size then reduce (/2) the window size and retry.
|
|
* 3.b When the intended mmapping size was bigger than mmap_window_size then free more mmaps from the cache.
|
|
* 4 When the cache is empty (that means all mmaps in use), scan the mmaps in use if they can be reduced
|
|
* mmap_window_size is already reduced now (half of refmap free from either end)
|
|
**/
|
|
enum {
|
|
FIRST_TRY,
|
|
DROP_FROM_CACHE,
|
|
REDUCE_WINDOW,
|
|
REDUCE_IN_USE,
|
|
GIVE_UP
|
|
} strategy = FIRST_TRY;
|
|
|
|
while (addr == (void*)-1)
|
|
{
|
|
TODO ("check if current mmapped size exceeds configured as_size (as_size be smaller than retrieved from getrlimit())");
|
|
|
|
TODO ("use resourcecllector here");
|
|
switch (strategy++)
|
|
{
|
|
case FIRST_TRY:
|
|
TRACE (mmap_dbg, "FIRST_TRY");
|
|
/* align begin and end to chunk boundaries */
|
|
begin = ((start-bias) & ~(chunksize-1)) + bias;
|
|
length = ((start+size+chunksize-1) & ~(chunksize-1)) - begin;
|
|
|
|
if (begin+(off_t)length > descriptor->stat.st_size)
|
|
{
|
|
/* request past the end */
|
|
if ((descriptor->flags & O_ACCMODE) == O_RDWR)
|
|
{
|
|
/* extend file (writable) */
|
|
if (ftruncate (fd, begin+length) == -1)
|
|
{
|
|
LUMIERA_ERROR_SET (mmap, ERRNO, lumiera_filedescriptor_name (file->descriptor));
|
|
goto etruncate;
|
|
};
|
|
descriptor->stat.st_size = begin+length;
|
|
descriptor->realsize = start+size;
|
|
}
|
|
}
|
|
else if (length < (size_t)mmap_window_size)
|
|
length = mmap_window_size;
|
|
|
|
if ((descriptor->flags & O_ACCMODE) == O_RDONLY)
|
|
{
|
|
/* The last mmapped chunk of a file can be as small as possible when the file is readonly */
|
|
length = start+size - begin;
|
|
}
|
|
break;
|
|
|
|
case DROP_FROM_CACHE:
|
|
TRACE (mmap_dbg, "drop a mapping from cache");
|
|
UNIMPLEMENTED ("mmap cache drop");
|
|
break;
|
|
|
|
case REDUCE_WINDOW:
|
|
NOTICE (mmap_dbg, "mmaping window reduced to NN MB");
|
|
UNIMPLEMENTED ("mmap window reduce");
|
|
break;
|
|
|
|
case REDUCE_IN_USE:
|
|
NOTICE (mmap_dbg, "reduce mmapings in use");
|
|
UNIMPLEMENTED ("mmapings in use reduce");
|
|
break;
|
|
|
|
case GIVE_UP:
|
|
LUMIERA_ERROR_SET_ALERT (mmap, MMAP_SPACE, lumiera_filedescriptor_name (file->descriptor));
|
|
goto espace;
|
|
}
|
|
|
|
addr = mmap (NULL,
|
|
length,
|
|
(descriptor->flags & O_ACCMODE) == O_RDONLY ? PROT_READ : PROT_READ|PROT_WRITE,
|
|
MAP_SHARED,
|
|
fd,
|
|
begin);
|
|
|
|
INFO_IF (addr==(void*)-1, mmap_dbg, "mmap failed %s", strerror (errno));
|
|
ENSURE (errno == 0 || errno == ENOMEM, "unexpected mmap error %s", strerror (errno));
|
|
}
|
|
|
|
llist_init (&self->cachenode);
|
|
llist_init (&self->searchnode);
|
|
|
|
self->start = begin;
|
|
self->size = length;
|
|
self->address = addr;
|
|
self->refmap = lumiera_calloc ((length-1)/chunksize+1, sizeof (unsigned short));
|
|
self->refcnt = 1;
|
|
lumiera_mmapcache_announce (self);
|
|
|
|
lumiera_file_handle_release (file);
|
|
return self;
|
|
|
|
espace:
|
|
etruncate:
|
|
efile:
|
|
lumiera_file_handle_release (file);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
LumieraMMap
|
|
lumiera_mmap_init_exact (LumieraMMap self, LumieraFile file, off_t start, size_t size)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
|
|
REQUIRE (self);
|
|
REQUIRE (file);
|
|
REQUIRE (start >= 0);
|
|
REQUIRE (size);
|
|
|
|
LumieraFiledescriptor descriptor = file->descriptor;
|
|
|
|
int fd = lumiera_file_handle_acquire (file);
|
|
TRACE (mmap_dbg, "got fd %d", fd);
|
|
if (fd == -1)
|
|
goto efile;
|
|
|
|
if ((off_t)(start + size) > descriptor->stat.st_size)
|
|
{
|
|
/* request past the end */
|
|
if ((descriptor->flags & O_ACCMODE) == O_RDWR)
|
|
{
|
|
/* extend file (writable) */
|
|
if (ftruncate (fd, start+size) == -1)
|
|
{
|
|
LUMIERA_ERROR_SET (mmap, ERRNO, lumiera_filedescriptor_name (file->descriptor));
|
|
goto etruncate;
|
|
};
|
|
descriptor->stat.st_size = descriptor->realsize = start+size;
|
|
}
|
|
}
|
|
|
|
/////////////////////////TODO use resourcecllector here
|
|
void* addr = mmap (NULL,
|
|
size,
|
|
(descriptor->flags & O_ACCMODE) == O_RDONLY ? PROT_READ : PROT_READ|PROT_WRITE,
|
|
MAP_SHARED,
|
|
fd,
|
|
start);
|
|
|
|
INFO_IF (addr==(void*)-1, mmap_dbg, "mmap failed %s", strerror (errno));
|
|
ENSURE (errno == 0 || errno == ENOMEM, "unexpected mmap error %s", strerror (errno));
|
|
|
|
llist_init (&self->cachenode);
|
|
llist_init (&self->searchnode);
|
|
|
|
self->start = start;
|
|
self->size = size;
|
|
self->address = addr;
|
|
self->refmap = NULL;
|
|
self->refcnt = 1;
|
|
lumiera_mmapcache_announce (self);
|
|
|
|
lumiera_file_handle_release (file);
|
|
return self;
|
|
|
|
etruncate:
|
|
efile:
|
|
lumiera_file_handle_release (file);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
LumieraMMap
|
|
lumiera_mmap_new (LumieraFile file, off_t start, size_t size)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
|
|
LumieraMMap self = lumiera_mmapcache_mmap_acquire ();
|
|
|
|
if (lumiera_mmap_init (self, file, start, size))
|
|
return self;
|
|
else
|
|
{
|
|
lumiera_free (self);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
LumieraMMap
|
|
lumiera_mmap_new_exact (LumieraFile file, off_t start, size_t size)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
|
|
LumieraMMap self = lumiera_mmapcache_mmap_acquire ();
|
|
|
|
if (lumiera_mmap_init_exact (self, file, start, size))
|
|
return self;
|
|
else
|
|
{
|
|
lumiera_free (self);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
lumiera_mmap_delete (LumieraMMap self)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
if (self)
|
|
{
|
|
REQUIRE (self->refcnt <= 1);
|
|
lumiera_mmapcache_forget (self);
|
|
|
|
/* The matching mappings->lock must be hold or being irrelevant (mappings destructor) here, we can't asset this from here, good luck */
|
|
llist_unlink (&self->searchnode);
|
|
|
|
TRACE (mmap_dbg, "unmap at %p with size %zd", self->address, self->size);
|
|
munmap (self->address, self->size);
|
|
lumiera_free (self->refmap);
|
|
lumiera_free (self);
|
|
}
|
|
}
|
|
|
|
|
|
void*
|
|
lumiera_mmap_destroy_node (LList node)
|
|
{
|
|
TRACE (mmap_dbg);
|
|
REQUIRE (llist_is_empty (node));
|
|
LumieraMMap self = (LumieraMMap)node;
|
|
|
|
lumiera_mmapcache_forget (self);
|
|
|
|
llist_unlink (&self->searchnode); FIXME ("must lock mmappings -> deadlock");
|
|
|
|
munmap (self->address, self->size);
|
|
lumiera_free (self->refmap);
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
// Local Variables:
|
|
// mode: C
|
|
// c-file-style: "gnu"
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
*/
|