/* * memmappedfile.cpp * * Created on: May 27, 2013 * Author: gregor */ #if defined LINUX || defined ANDROID #include #include #include #include #endif #include "GLTB/exception.h" #include "GLTB/memmappedfile.h" #include "GLTB/stringconvert.h" namespace gltb { Mapping::Mapping(WeakPtr memMappedFile, void *realBase, void *base, unsigned long long realSize, unsigned long long size) : memMappedFile(memMappedFile), realBase(realBase), base(base), realSize(realSize), size(size), valid(true) { } Mapping::~Mapping() { remove(); } void Mapping::syncToDisk(void *base, unsigned long long size, bool synchronous) { if(!valid) { return; } #if defined LINUX || defined ANDROID int flags = MS_INVALIDATE; if(synchronous) { flags |= MS_SYNC; } else { flags |= MS_ASYNC; } msync(base, size, flags); #elif defined WIN32 FlushViewOfFile(base, size); if(synchronous) { RefPtr file = memMappedFile.getRefPtr(); if(file != nullptr) { FlushFileBuffers(file->_getFileHandle()); } } #else #error Mapping::syncToDisk is not yet implemented for this platform #endif } void Mapping::remove() { if(!valid) { return; } #if defined LINUX || defined ANDROID munmap(realBase,realSize); #elif defined WIN32 UnmapViewOfFile(realBase); #else #error Mapping::remove() is not yet implemented for this platform #endif valid=false; // remove from list within MemMappedFile RefPtr mappedFile = memMappedFile.getRefPtr(); if(mappedFile != nullptr) { mappedFile->_removeMapping(this); } } MemMappedFile::MemMappedFile(std::string filename, bool read, bool write, bool exec) : filename(filename), mode(read, write, exec) { init(); } MemMappedFile::MemMappedFile(std::string filename, Mode mode) : filename(filename), mode(mode) { init(); } MemMappedFile::MemMappedFile() : filename(""), mode(false,false,false), opened(false) { } MemMappedFile::~MemMappedFile() { close(); } RefPtr MemMappedFile::mapRegion(unsigned long long offset, unsigned long long size) { // file mapping has granularity restrictions - obey them unsigned int offsetGranularity; #if defined LINUX || defined ANDROID offsetGranularity = sysconf(_SC_PAGE_SIZE); #elif defined WIN32 SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); offsetGranularity = sysInfo.dwAllocationGranularity; #else offsetGranularity = 1; #endif unsigned long long realOffset = offset - (offset % offsetGranularity); unsigned long long realSize = size + offset - realOffset; void *realBase; #if defined LINUX || defined ANDROID int prot=0; if(mode.executable) { prot|=PROT_EXEC; } if(mode.readable) { prot|=PROT_READ; } if(mode.writeable) { prot|=PROT_WRITE; } int flags=MAP_SHARED; realBase=mmap(NULL,realSize,prot,flags,fileID,realOffset); if(realBase==MAP_FAILED) { throw Exception("failed to map file range to memory","gltb::MemMappedRegion::mapRegion()"); } #elif defined WIN32 int desiredAccess = 0; if(mode.readable && mode.writeable) { desiredAccess = FILE_MAP_ALL_ACCESS; } else if(mode.readable) { desiredAccess = FILE_MAP_READ; } else if(mode.writeable) { desiredAccess = FILE_MAP_WRITE; } if(mode.executable) { desiredAccess |= FILE_MAP_EXECUTE; } unsigned int offsetHigh = (realOffset >> 32) & 0xFFFFFFFF; unsigned int offsetLow = realOffset & 0xFFFFFFFF; realBase = MapViewOfFile(fileMapping, desiredAccess, offsetHigh, offsetLow, size); if(realBase == NULL) { throw Exception("failed to map file range to memory","gltb::MemMappedRegion::mapRegion()"); } #else #error MemMappdFile::mapRegion() not implemented yet for this platform #endif // realBase points to start of page before actually requested offset; shift base pointer accordingly void *base = (char*)realBase + (offset - realOffset); RefPtr mapping=new Mapping(RefPtr(this), realBase, base, realSize, size); mappings.push_back(mapping); return mapping; } void MemMappedFile::close() { if(!opened) { return; } // remove remaining mappings for(unsigned int i=0; i mapping=mappings[i].getRefPtr(); if(mapping != nullptr) { if(mapping->isValid()) { mapping->remove(); } } } #if defined LINUX || defined ANDROID ::close(fileID); #elif defined WIN32 CloseHandle(fileMapping); CloseHandle(file); #else #error MemMappedFile::close() not implemented yet for this platform #endif opened=false; } void MemMappedFile::init() { #if defined LINUX || defined ANDROID int flags=O_CLOEXEC; // this is to mirror Win32 if(mode.readable && mode.writeable) { flags|=O_RDWR; } else if(mode.readable) { flags|=O_RDONLY; } else if(mode.writeable) { flags|=O_WRONLY; } fileID=open(filename.c_str(),flags); if(fileID == -1) { throw Exception("unable to open backing file " + filename, "gltb::MemMappedFile::init()"); } #elif defined WIN32 int desiredAccess=0; if(mode.readable) { desiredAccess |= GENERIC_READ; } if(mode.writeable) { desiredAccess |= GENERIC_WRITE; } if(mode.executable) { // TODO is this required for a executable mapping? desiredAccess |= GENERIC_EXECUTE; } int shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; int flags = FILE_ATTRIBUTE_NORMAL; file=CreateFile(utf8ToUtf16WString(filename).c_str(), desiredAccess, shareMode, NULL, OPEN_EXISTING, flags, NULL); if(file == INVALID_HANDLE_VALUE) { throw Exception("unable to open backing file " + filename, "gltb::MemMappedFile::init()"); } int protection; if(mode.readable && mode.writeable && mode.executable) { protection = PAGE_EXECUTE_READWRITE; } else if(mode.readable && mode.writeable) { protection = PAGE_READWRITE; } else if(mode.readable) { protection = PAGE_READONLY; } else { CloseHandle(file); throw Exception("required a mapping mode that is not supported on Win32","gltb::MemMappedFile::init()"); } fileMapping = CreateFileMapping(file, NULL, protection, 0, 0, NULL); if(fileMapping == NULL) { // this is NULL, not INVALID_HANDLE_VALUE [sic] CloseHandle(file); throw Exception("unable to create file mapping for file " + filename, "gltb::MemMappedFile::init()"); } // TODO check GetLastError() here to see if we got an existing mapping that will not match our requested size #else #error MemMappedFile::init() not implemented yet for this platform #endif opened = true; } void MemMappedFile::_removeMapping(RefPtr mapping) { for(auto iter = mappings.begin(); iter != mappings.end(); ++iter) { RefPtr thisMapping=iter->getRefPtr(); if(thisMapping==mapping) { mappings.erase(iter); return; } } } }