If I look at the contents of either sleepimage or swapfile in /var/vm
, I find that it's just 1.07G of null bytes. I'd expect them to contain actual data, or if they're not being used to be a 0B file. I've checked a few different OS versions and verified that this is the case from 10.9 to 11.6, so I doubt it's something particular to a given setup or filesystem.
1 Answer
I decided to do some digging into the kernel source and I have a rough answer, but it's still not 100% clear. If you take a look at IOHibernateIO.cpp
inside the IOHibernateSystemPostWake
method you see the following call (note: slightly different for 10.11+, but the same function ends up being called)
if (kFSOpened == gFSState)
{
// invalidate & close the image file
gIOHibernateCurrentHeader->signature = kIOHibernateHeaderInvalidSignature;
if ((fileRef = gIOHibernateFileRef))
{
gIOHibernateFileRef = 0;
IOSleep(TRIM_DELAY);
kern_close_file_for_direct_io(fileRef,
#if DISABLE_TRIM
0, 0, 0, 0, 0);
#else
0, (caddr_t) gIOHibernateCurrentHeader,
sizeof(IOHibernateImageHeader),
0,
gIOHibernateCurrentHeader->imageSize);
#endif
}
gFSState = kFSIdle;
}
return (kIOReturnSuccess);
Importantly, DISABLE_TRIM
is defined as 0, so we end up calling kern_close_file_for_direct_io
whose signature is as follows:
void
kern_close_file_for_direct_io(struct kern_direct_file_io_ref_t * ref,
off_t write_offset, caddr_t addr, vm_size_t write_length,
off_t discard_offset, off_t discard_end)
and whose implementation first seems to issue a DKIOCUNMAP
ioctl for the extents belonging to the hibernate file, then copies write_length
bytes from memory address addr
into the file. If we look a bit back, gIOHibernateCurrentHeader
is zeroed out in memory after the hibernate image is written during IOHibernateSystemSleep
.
So this leads to a few conclusions, as well as a few lingering questions:
This does indeed seem to be intended behavior, and I can see a similar
DKIOCUNMAP
being issued withinvm_compressor_backing_file.c
so I assume that something similar happens for swap files as well.However, I'm uncertain as to what this means for the space physically taken on disk. For APFS this is trivially answerable since it supports sparse files (so even if the file is actually 1.7G of null bytes it won't matter), but what happens for HFS+? My understanding is that the
DKIOCUNMAP
ioctl effectively translates into the SSD TRIM command to clean up used blocks:
The doUnmap method was introduced as a replacement for the doDiscard method. It performs a similar function, which is to release disk blocks that are not used by the file system. Unlike the doDiscard method, which is capable of releasing only a single physically contiguous run of disk blocks, the doUnmap method is provided with an array containing one or more ranges of disk blocks that are no longer in use. A user space process can perform this action by sending the ioctl DKIOCUNMAP.
which would seem to imply that despite the file-system level thinking that the size of the file is 1.7G, none of those extents actually point to valid blocks on disk, and so the physical file size is far less. This would also explain why it's 1.7G of zeros, since I assume the SSD controller upon receiving the trim command might implement RZAT (return zeros after trim). This hypothesis could be tested by trying this on a spinning disk and seeing if data is returned.
-
But it's the same thing (all null bytes) when I have 10G swap usage. Where is the data?– Joy JinCommented Dec 29, 2021 at 9:09
-
@JoyJin That's a mystery to me too, and my assumption is that what Activity Monitor displays as swap might also include compressed memory? You can dig around in vm_compressor kernel source to see how swap memory is accounted for, but based on some other threads about compressor modes it seems that both compressed & swap might be treated in the same class? Another mystery is that
sleepimage
is only 1.7G even though with ~8G of memory in use (which should be about 3G assuming 60% memory compression) Commented Dec 30, 2021 at 5:50 -
My
sleepimage
is also 1.7G even though I have 16G of memory. My guess is that the system directly writes to the raw device, and the files are just for consistency in the APFS filesystem?– Joy JinCommented Jan 1, 2022 at 10:41 -
@JoyJin Yeah I have 16GB memory too, with ~8G in use at any given time. I don't think it's writing it directly at the block level since the kernel code very clearly performs writes to that filepath. And I've verified that this happens on both HFS and APFS (10.8 and 10.12). There are 2 possible options I think. 1) Perhaps it is writing more than 1.7G, but then when the file is trimmed during resume the size gets reset to 1.7G for some reason. 2) The sleepimage itself may be used in combination with the swapfiles during hibernation. Commented Jan 1, 2022 at 23:45
sudo xxd -l 500 /var/vm/sleepimage