mmap(): what happens if underlying file changes (shrinks)? - c++

If you memory map a file using mmap(), but then the underlying file changes to a much smaller size. What happens if you access a memory offset that was shaved off from the file?

IBM says it is undefined http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fapis%2Fmmap.htm
If the size of the mapped file is decreased after mmap(), attempts to reference beyond the end of the file are undefined and may result in an MCH0601 exception.
If the size of the file increases after the mmap() function completes, then the whole pages beyond the original end of file will not be accessible via the mapping.
The same is said in SingleUnixSpecification: http://pubs.opengroup.org/onlinepubs/7908799/xsh/mmap.html
If the size of the mapped file changes after the call to mmap() as a result of some other operation on the mapped file, the effect of references to portions of the mapped region that correspond to added or removed portions of the file is unspecified.
'undefined' or 'unspecified' means - the OS is allowed to start formatting of disk or anything. Most probable is SIGSEGV-killing your application.

It depends on what flags you gave to mmap, the man page:
MAP_SHARED Share this mapping. Updates to the mapping are visible to
other processes that map this file, and are carried through to the
underlying file. The file may not actually be updated until msync(2)
or munmap() is called.
and
MAP_PRIVATE Create a private copy-on-write mapping. Updates to the
mapping are not visible to other processes mapping the same file, and
are not carried through to the underlying file. It is unspecified
whether changes made to the file after the mmap() call are visible in
the mapped region.
So for MAP_PRIVATE, doesn't matter, each writer effectively has a "private" copy. (though it is only copies when a mutating operation occurs).
I would think that if you use MAP_SHARED, then no other process would be allowed to open the file with write privileged. But that's a guess.
EDIT: ninjalj is right, the file can be modified even when you mmap with MAP_SHARED.

According to the man pages mmap returns EINVAL error when you try to access an address that is too large for the current file mapping.
"dnotify" and "inotify" are the current file change notification services in the Linux kernel.
Presumably, they would inform the mmap subsystem of changes to the file.

Related

mmap's worst case memory usage when using MAP_PRIVATE vs MAP_SHARED

I haven't seen this explicitly anywhere so I just wanted to clarify. This is all in the context of a single threaded program: say we have a 10GB text file when we open with mmap, using the MAP_PRIVATE option. Initially of course, I should expect to see 0GB resident memory used. Now say I modify every character in the file. Will this then require 10GB in resident memory? And if not, why not?
Now what if we did the same thing but with MAP_SHARED, what should I expect the resident memory usage to look like?
MAP_SHARED creates a mapping that is backed by the original file. Any changes to the data are written back to that file (assuming a read/write mapping).
MAP_PRIVATE creates a mapping that is backed by the original file for reads only. If you change bytes in the mapping, then the OS creates a new page that is occupies physical memory and is backed by swap (if any).
The impact on resident set size is not dependent on the mapping type: pages will be in your resident set if they're actively accessed (read or write). If the OS needs physical memory, then pages that are not actively accessed are dropped (if clean), or written to either the original file or swap (if dirty, and depending on mapping type).
Where the two types differ is in total commitment against physical memory and swap. A shared mapping doesn't increase this commitment, a private mapping does. If you don't have enough combined memory and swap to hold every page of the private mapping, and you write to every page, then you (or possibly some other process) will be killed by the out-of-memory daemon.
Update: what I wrote above applies to memory-mapped files. You can map an anonymous block (MAP_ANONYMOUS) with MAP_SHARED, in which case the memory is backed by swap, not a file.

Is it kosher to memory map a file a second time but with a larger size?

In my application, one file on disk is created and memory mapped with an initial size. If I apply a second memory mapping to the file, with a larger size, the file expands to the new size. Windows lets me do this without error but I don't know if it's actually kosher. Are there any problems with a second memory mapping with a larger size?
The CreateFileMapping documentation has this to say:
After a file mapping object is created, the size of the file must not exceed the size of the file mapping object; if it does, not all of the file contents are available for sharing.
This would appear to imply (since no other downsides or prohibitions are mentioned) that the only side effect of mapping a file a second time with a larger size is that the first mapping won't be able to see the whole thing (obviously).

std::ofstream::open will it read the entire file into memory?

I'm writing things from my memory to the disk in order to free my memory.
I wonder each time I call open(), and appendix new elements to the end of the file, will it read the entire file into memory? or it is just a pointer to the end of the file?
The fstream implementation doesn't specify exactly what happens if you use the ofstream::app, ios::app, ofstream::ate or ios::ate mode to open the file.
But in any sane implementation, the file is not read into memory, all that happens is that the fstream implementation positions the "current position" to the end of the file.
To read the entire file into memoiry would be rather terrible if you have a system with 2GB of RAM and you wanted to append to a file that is bigger than 2GB.
Being very pedantic, when writing something to a text-file, it is likely that the filesystem that is part of the operating system will read the last few (kilo)bytes of the file, as most hard-disks and similar storage requires that the data is being written to a "block", which is a fixed size (e.g. 512 bytes or 4 kilobytes). So, unless the current filesize is exactly at a boundary of such a block, the filesystem must read the last block of the file and write it back with the additional data that you asked to write.
If you are worried about appending to a log-file that gets very large, no, it's not an issue. If you are worried about memory safety because your file has secret data that you won't want stored in memory, then may be a problem, because a portion of that will probably be loaded into memory, and there is nothing you can do to control that.

Fast resize of a mmap file

I need a copy-free re-size of a very large mmap file while still allowing concurrent access to reader threads.
The simple way is to use two MAP_SHARED mappings (grow the file, then create a second mapping that includes the grown region) in the same process over the same file and then unmap the old mapping once all readers that could access it are finished. However, I am curious if the scheme below could work, and if so, is there any advantage to it.
mmap a file with MAP_PRIVATE
do read-only access to this memory in multiple threads
either acquire a mutex for the file, write to the memory (assume this is done in a way that the readers, which may be reading that memory, are not messed up by it)
or acquire the mutex, but increase the size of the file and use mremap to move it to a new address (resize the mapping without copying or unnecessary file IO.)
The crazy part comes in at (4). If you move the memory the old addresses become invalid, and the readers, which are still reading it, may suddenly have an access violation. What if we modify the readers to trap this access violation and then restart the operation (i.e. don't re-read the bad address, re-calculate the address given the offset and the new base address from mremap.) Yes I know that's evil, but to my mind the readers can only successfully read the data at the old address or fail with an access violation and retry. If sufficient care is taken, that should be safe. Since re-sizing would not happen often, the readers would eventually succeed and not get stuck in a retry loop.
A problem could occur if that old address space is re-used while a reader still has a pointer to it. Then there will be no access violation, but the data will be incorrect and the program enters the unicorn and candy filled land of undefined behavior (wherein there is usually neither unicorns nor candy.)
But if you controlled allocations completely and could make certain that any allocations that happen during this period do not ever re-use that old address space, then this shouldn't be a problem and the behavior shouldn't be undefined.
Am I right? Could this work? Is there any advantage to this over using two MAP_SHARED mappings?
It is hard for me to imagine a case where you don't know the upper bound on how large the file can be. Assuming that's true, you could "reserve" the address space for the maximum size of the file by providing that size when the file is first mapped in with mmap(). Of course, any accesses beyond the actual size of the file will cause an access violation, but that's how you want it to work anyway -- you could argue that reserving the extra address space ensures the access violation rather than leaving that address range open to being used by other calls to things like mmap() or malloc().
Anyway, the point is with my solution, you never move the address range, you only change its size and now your locking is around the data structure that provides the current valid size to each thread.
My solution doesn't work if you have so many files that the maximum mapping for each file runs you out of address space, but this is the age of the 64-bit address space so hopefully your maximum mapping size is no problem.
(Just to make sure I wasn't forgetting something stupid, I did write a small program to convince myself creating the larger-than-file-size mapping gives an access violation when you try to access beyond the file size, and then works fine once you ftruncate() the file to be larger, all with the same address returned from the first mmap() call.)

Are Windows Memory Mapped File contents always zeroed by default?

I've determined empirically that, on my system, a memory mapped file created to be a certain size is always completely zeroed by default. For example, using the call
HANDLE hMM =
CreateFileMapping (h,
NULL,
PAGE_READWRITE,
0,
0x01400000,//20MB
NULL);
.. and writing into a mapped view of that file always results in a 20MB file that is completely zeroed, except where I have written non-zero data.
I'm wondering if uninitialized parts of the file can be assumed to be zeros. Is this behavior guaranteed on Windows in general?
The CreateFileMapping documentation (Remarks section) explicitly states that
If the file is extended, the contents of the file between the old end of the file and the new end of the file are not guaranteed to be zero; the behavior is defined by the file system.
so, if your file on disk starts empty, it's not guaranteed to be zeroed (since you are expanding it); I don't think that file system drivers would take the risk of leaking potentially sensitive information that way, but who knows, maybe some file system driver recycles pages already used for your process (and this shouldn't be a security risk).
On the other hand, I don't know if filesystems that do not offer security at all (e.g. FAT) would be so concerned to give you the content of the clusters that they happened to allocate for the new part of the file.
If, instead, you are creating a memory section not backed by a file on disk but by the paging file it's guaranteed that the memory you get is all zeroed:
The initial contents of the pages in a file mapping object backed by the operating system paging file are 0 (zero).
This is guaranteed probably because when creating a memory-only paging file the memory manager has the complete control on what's going on, and it takes the pages from the blanked pages pool.
All newly allocated pages are zeroed before they are made accessible to user-mode, because otherwise sensitive information could be leaked from kernel-mode or other processes. This applies to things like NtAllocateVirtualMemory/VirtualAlloc and NtCreateSection/CreateFileMapping.
I imagine the same concept extends to files, because any decent file system wouldn't want to leak information in this way.
EDIT: However, take that last paragraph with a grain of salt - both the documentation for CreateFileMapping and SetEndOfFile claim that the extended portion of the file is not defined. I'll do some more investigation.
EDIT 2: OK, the Win32 MSDN documentation is definitely wrong. The documentation for ZwSetInformationFile states:
If you set FileInformationClass to
FileEndOfFileInformation, and the
EndOfFile member of
FILE_END_OF_FILE_INFORMATION specifies
an offset beyond the current
end-of-file mark, ZwSetInformationFile
extends the file and pads the
extension with zeros.
So there you go. The extended portion is guaranteed to be zero.
Yes, as pointed out by wj32. This is related to c2 requirements which NT has met since its birth. However depending on what you are trying to do, you should probably look into sparse files.