Merge patch series "fs: don't let idle threads pin the initramfs"

Christian Brauner <brauner@kernel.org> says:

Since v7.0 nullfs is the real rootfs and userspace can pivot_root() away
from the initramfs and unmount it. systemd does exactly that and the
initramfs is supposed to be freed afterwards. But it isn't (cf. [1]).
The unpacked initramfs stays around forever. It shows up as Unevictable
in /proc/meminfo with ramfs and as Shmem with tmpfs. In the reports
that's between 70MB and 150MB.

Two things keep it alive and neither is visible from userspace:

(1) On v7.0 to v7.2 devtmpfsd unshares a copy of the initial mount
    namespace which since nullfs contains the initramfs mount. It
    doesn't show up in /proc/<pid>/mountinfo because it's below the
    thread's root. That's already fixed in mainline by commit
    66d2faeccb ("devtmpfs: create private mount namespace").

(2) On all kernels since v7.0 the idle threads of the secondary cpus
    are forked from PID 1 and each of them gets a private copy of the
    fs_struct of PID 1 with the initramfs as root and pwd. Idle threads
    aren't on the task list so pivot_root() never moves them over to
    the new root and they never exit. With a single possible cpu there
    are no such idle threads which is why booting a VM with -smp 1
    hides it.

This series fixes (2) by letting the idle threads share init_fs like
init_task already does. The first patch turns the umh boolean of
copy_fs() into flags. The second patch is the actual fix and carries a
reproducer.

systemd could go back to emptying the initramfs when switching root like
it did before nullfs and that's what [2] does. But that only frees the
contents. The mount and the superblock stay pinned. And I don't think
userspace should have to do that. With nullfs it's supposed to be able
to just unmount the initramfs and be done with it.

We could also teach chroot_fs_refs() about idle threads. But they are
kernel threads and have no business holding a reference to the root of
userspace to begin with.

Of the stable kernels only v7.2 is affected and the backport looks
different there. PID 1 still shares init_fs with all kthreads so passing
CLONE_FS in fork_idle() is enough. It also needs 66d2faeccb which
applies cleanly.

I've tested this with the reproducer from the second patch and with a
systemd 262 initrd on 4 cpus. On vfs.fixes about 170MB stay Unevictable
after switching root and with this series they're freed. Same for the
backport on v7.2.

[1]: https://github.com/systemd/systemd/issues/43911
[2]: https://github.com/systemd/systemd/pull/44144

* patches from https://patch.msgid.link/20261013-work-fs-idle_fs-v1-0-2063d1bcb5aa@kernel.org:
  fs: let idle threads share init_fs
  fs: pass flags to copy_fs()

Link: https://patch.msgid.link/20261013-work-fs-idle_fs-v1-0-2063d1bcb5aa@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-10-13 20:46:08 +02:00
commit a7064be65f
No known key found for this signature in database
GPG key ID: 91C61BC06578DCA2