1

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.

2
  • 1
    How are you looking at the data?
    – mmmmmm
    Commented Dec 29, 2021 at 9:54
  • @mmmmmm sudo xxd -l 500 /var/vm/sleepimage
    – 1110101001
    Commented Dec 30, 2021 at 5:46

1 Answer 1

1

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 within vm_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.

4
  • But it's the same thing (all null bytes) when I have 10G swap usage. Where is the data?
    – Joy Jin
    Commented 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)
    – 1110101001
    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 Jin
    Commented 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.
    – 1110101001
    Commented Jan 1, 2022 at 23:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .