Overview

Most of my kernel work sits in the local file system stack, mostly ext4 and the jbd2 journalling layer. Some of it is user-visible features, some is performance work, and a good share is correctness fixes that could not stay inside ext4, because the actual problem was in VFS, memory management or the block layer.

198 commits · 8,012 insertions(+) · 3,697 deletions(-)

Notable highlights

FITRIM ioctl: discard in batches, not on every delete

The original way to tell a device about freed blocks was to discard them as soon as the file system freed them, through the -o discard mount option. On some devices that works fine. On others, depending on how efficient the wear-leveling algorithm is, it costs a great deal of performance, because the drive gets a steady stream of small discard requests and handles them synchronously.

I designed the FITRIM ioctl to do it the other way around. Instead of discarding on every delete, the file system searches for contiguous free extents larger than the minlen the caller asks for and discards those in one pass. Searching for large extents is far more efficient, and it hands the administrator the actual trade-off: how much space gets reclaimed for wear-leveling, and what that costs in performance. FITRIM is file system independent, so any file system can implement it.

On top of the ioctl I wrote fstrim, which is now part of util-linux and normally runs from a periodic timer rather than by hand.

Lazy inode table initialization

Creating an ext4 file system used to get slower as devices got bigger, because mkfs zeroed the entire inode table up front. On a large volume you sat there and waited.

I moved that work into the kernel. A background thread walks the allocation groups, finds the ones whose inode table is not initialized yet, zeroes them, and sleeps between groups so it does not compete with real I/O. The file system is usable immediately and the initialization finishes on its own, exposed through the lazy_itable_init option.

The work does not disappear, of course. It still happens, just later and out of your way.

fallocate() PUNCH_HOLE and the invalidatepage rewrite

This one started as an ext4 bug and ended up somewhere else entirely. truncate_inode_pages_range() could not handle a range that was not aligned to the end of a page. It hit a BUG_ON(). Every file system that implemented punch hole therefore had to write its own invalidation code for the unaligned case.

Fixing it properly meant changing shared infrastructure. I changed the ->invalidatepage() address space operation to take a length in addition to an offset, updated every implementation of it in the tree, and made truncate_inode_pages_range() handle unaligned ranges. Ext4, XFS, ocfs2 and jbd2 all needed work. After that the file systems could drop their private workarounds.

fallocate() ZERO_RANGE

If you wanted a range of a file to read back as zeros, the portable way was to write zeros to it. That means real I/O, for data nobody will ever read.

I designed and implemented FALLOC_FL_ZERO_RANGE, which asks the file system to arrange it instead. The range stays allocated and reads back as zeros, usually by converting it to unwritten extents rather than writing anything at all. XFS already had this as the XFS_IOC_ZERO_RANGE ioctl; the point was to make it a generic fallocate() mode that any file system can implement. It works past EOF as well, with FALLOC_FL_KEEP_SIZE.

It is useful for loop devices, virtual block layers, and any storage backend that can zero a range internally far more cheaply than you can write it.

Online ext4 label get and set

Changing the label on an ext4 file system used to mean writing to the superblock from userspace. On a mounted file system that is a bad idea, and it got worse as the kernel started validating metadata more strictly, because then the kernel and the tool disagree about what the superblock says.

I implemented online label read and write in ext4 through the generic FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls, so the label can be changed on a mounted file system without touching the raw device.

ext4 conversion to the new mount API (fs_context)

The kernel mount infrastructure moved to an fs_context based API and every file system had to follow. I worked on converting ext4.

Most of it is plumbing: mount option parsing, validation and superblock setup all get restructured. The result is more deterministic, though. Options are parsed and validated before anything is committed, rather than half-applied and then unwound when the next one turns out to be wrong.

tmpfs and shmem user and group quota

People had been asking for quota support in tmpfs for years, mostly to stop one misbehaving user or program from consuming all the memory on the machine. The size mount option solves part of that. But /dev/shm is generally left unprotected, getting per-user limits means administering a lot of separate tmpfs mounts, and size gives you no per-user or per-group control at all.

I initiated and largely implemented user and group quota for tmpfs (shmem). It limits inode counts as well, and with the additional quota mount options it can replace size entirely.

I left Red Hat before the final upstream merge. My friend Carlos Maiolino finished the work and shepherded it upstream, and it is in mainline Linux now.

Debugging and cross-subsystem fixes

A large share of the work never shows up as a feature at all. It is chasing something that reproduces once a week on one machine, and following it wherever it goes:

  • ext4 and jbd2 journalling behaviour
  • VFS semantics
  • page cache and memory management interactions
  • block layer behaviour
  • hardware and firmware edge cases, found through file system testing

File system bugs have a habit of not being file system bugs. This kind of investigation starts as reported corruption and ends in the page cache, in the block layer, or once, memorably, in a CPU firmware bug.