LUMIERA.clone/src/backend/mmap.c

357 lines
10 KiB
C
Raw Normal View History

/*
2008-12-26 23:17:51 +01:00
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
2010-12-17 23:28:49 +01:00
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.
* *****************************************************/
2016-11-03 18:26:43 +01:00
/** @file mmap.c
** Implementation of management for memory mapped file regions
** @todo development in this area is stalled since 2010
2016-11-03 18:26:43 +01:00
*/
#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>
2008-12-08 20:57:57 +01:00
#include <errno.h>
2008-12-26 23:17:51 +01:00
LUMIERA_ERROR_DEFINE (MMAP_NWRITE, "Backing file not writable");
LUMIERA_ERROR_DEFINE (MMAP_SPACE, "Address space exhausted");
LumieraMMap
2008-12-08 20:57:57 +01:00
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;
2008-12-08 20:57:57 +01:00
void* addr = (void*)-1;
off_t begin = 0;
size_t length = 0;
2008-12-08 20:57:57 +01:00
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);
2008-12-08 20:57:57 +01:00
/**
* Recovering address space strategies:
2008-12-26 23:17:51 +01:00
* mmap() will fail when too much memory got mmapped after some time which is then
2008-12-08 20:57:57 +01:00
* 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..
2008-12-26 23:17:51 +01:00
* 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.
2008-12-08 20:57:57 +01:00
* 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)
{
2008-12-26 23:17:51 +01:00
TODO ("check if current mmapped size exceeds configured as_size (as_size be smaller than retrieved from getrlimit())");
2009-01-14 14:50:25 +01:00
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)
{
2008-12-26 23:17:51 +01:00
/* 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)
2008-12-08 20:57:57 +01:00
{
2008-12-26 23:17:51 +01:00
/* The last mmapped chunk of a file can be as small as possible when the file is readonly */
2008-12-08 20:57:57 +01:00
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:
2009-01-14 14:50:25 +01:00
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);
2008-12-08 20:57:57 +01:00
INFO_IF (addr==(void*)-1, mmap_dbg, "mmap failed %s", strerror (errno));
2008-12-08 20:57:57 +01:00
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;
2008-12-08 20:57:57 +01:00
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
2008-12-08 20:57:57 +01:00
lumiera_mmap_new (LumieraFile file, off_t start, size_t size)
{
TRACE (mmap_dbg);
LumieraMMap self = lumiera_mmapcache_mmap_acquire ();
2008-12-08 20:57:57 +01:00
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
{
2008-12-08 20:57:57 +01:00
lumiera_free (self);
return NULL;
}
}
void
lumiera_mmap_delete (LumieraMMap self)
{
TRACE (mmap_dbg);
if (self)
{
REQUIRE (self->refcnt <= 1);
lumiera_mmapcache_forget (self);
2008-12-26 23:17:51 +01:00
/* 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);
2008-12-08 20:57:57 +01:00
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);
2008-12-08 20:57:57 +01:00
lumiera_free (self->refmap);
return self;
}
/*
// Local Variables:
// mode: C
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
*/