Ever wanted to know how the F2FS works? (There’s filesystem already in the acronym, let’s not repeat ourselves)
Starting with an hex editor, the first thing we see is…. Not much.

… Looking a bit further, the superblock (more on this in a bit) appears a bit later, at offset 0x400 in the first page (again we’ll talk about it in a jiffy1).

As far as the OS is concerned, a chunk of information on the disk is always 512 bytes long. I haven’t seen exceptions to this rule so far, so I’ll stick to it for simplicity. It is important to understand that one block of F2FS information is always 4KB in size (0x1000), which happens by design to be also the page size on x86 computers. That means that memory is efficiently moved around the computer, because the memory pager tracks pages of 4KB, and the flash disk can accomodate this size as it is a multiple of the traditional 512 sector. And all is well.
Now then, the on-disk layout is an extension of what originally came out during DOS days, and while UEFI is everywhere nowadays, some disks are still formatted in this old PC style and ship with an embedded boot block. The boot sector of 512 bytes at offset 0 might not be in use anymore, or it might just be, and sometimes block 1 had to be used as well to embedded a bigger loading program. So by convention sector 0, and sector 1 are reserved and the usable information as far as the filesystem is concerned starts at sector 2.
Let’s refer to the “PC blocks” of 512 bytes as sectors. In a F2FS disk, the first block (4KB chunk of data) contains the superpage at offset 0x400. So we have the numbers. Then looking at header files2 we note the following values.
struct f2fs_super_block {
__le32 magic; /* Magic Number */ // f2fs2010
__le16 major_ver; /* Major Version */ // version 1.1
__le16 minor_ver; /* Minor Version */
__le32 log_sectorsize; /* log2 sector size in bytes */ // (1 << 9) == 512
__le32 log_sectors_per_block; /* log2 # of sectors per block */ // (1 << 3) == 8 (4 KB blocks)
__le32 log_blocksize; /* log2 block size in bytes */ // (1 << 12) ==4096 (4KB blocks)
__le32 log_blocks_per_seg; /* log2 # of blocks per segment */ // (1 << 9) == 512 (2 MB)
__le32 segs_per_sec; /* # of segments per section */ // 1 segment per section
__le32 secs_per_zone; /* # of sections per zone */ // 1 section per zone
__le32 checksum_offset; /* checksum offset inside super block */ // checksum offset == 0
/** ........... OTHER FIELDS **/
__le32 feature; /* defined features */ // features == 0
/** ............ OTHER FIELDS **/
} __packed;The first thing to note is the so-called magic number is 0xf2fs2010, nothing too special about it here beyond the easter egg. It is tradition for the filesystem’s designers to put some sort of message there, such as the birth date of someone, or the year of the publication of the filesystem’s whitepaper or something. But this is purely symbolic, and not needed in any technical sense beyond identification of the partition.
Next up, the major and minor versions. As far as I’m aware, version 1.1 has been in use for a long time, since ~2012 probably.
The following quantities are set at filesystem creation time, (actually everything in the superblock is set in stone at filesystem creation time – mkfs.f2fs). The sector size is, as previously discussed, probably always 512. The F2FS block size if predictably 4096 bytes, so 8 sectors long.
The segment is a new concept, it represents a contiguous sequence of blocks, a chunk of 2MiB with a bitshift3 of 9. You can use segments and blocks interchangeably in F2FS, making sure to scale the value appropriately to get the right byte count. The section is also a new concept. The section is simply a number of contiguous segments, just as the segment is a contiguous number of blocks. Likewise the zone4 is a contiguous number of segments – an optimisation for some special large drive types. The checksum offset is 0. Features’s bit number 11 is off. This means Superblock checksumming is turned off. For other disks, it might be turned on, it depends on how the filesystem was created.
One last thing. The information in the superblock is important. For that reason, there’s a copy of the superblock in the next block. Taking into consideration the offset of 0x400, we’d expect the copy superblock to start at 0x1400.

- The jiffy is a silly unit that roughly means 1/Hz, the time between system ticks. It just means ‘soon’ in practice. ↩︎
- https://elixir.bootlin.com/linux/v7.0.11/source/include/linux/f2fs_fs.h#L112 ↩︎
- https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts ↩︎
- https://zonedstorage.io/docs/introduction/zns ↩︎