From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0CAF13806AF for ; Sat, 25 Apr 2026 22:09:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777154945; cv=none; b=u9NL18zYSQkbm+rRKP3h+8NZNciqNuiYtuCng4vkpf0tP5W7PqGJABbOEsHrD2N+ggVMs5wor+j1/ZOS5TGVoNefaKWhu4Z1OlNdB8MBs0ptMUR2hR88iM6kFjItq0xTOV4oCHm6An0MW4a0E0g4f/qPlHaWYAiqJt3Ce61jOqQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777154945; c=relaxed/simple; bh=VtZOPW1/LZKA5x5UFQabaxvf5lmI+Tr3n7dvFdW2Njo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=R+lcbdUEKZvR47au++0AZ3v3OPlapW0yyT5GzFqbWmWIyGk58PydbUA9dr7yWVaB7eRh0Vdu19lGRGKTG2OQO7EF/jM5WcHIIvaBIlXQpLzGQZ7B5MZk78KuVDSoqvUaEbleVl/S5i7RyIO5MyGzr/hp/TZOPOKA77OEk4eis68= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OxEDFPAY; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OxEDFPAY" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-4893940bb5eso44769865e9.3 for ; Sat, 25 Apr 2026 15:09:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777154942; x=1777759742; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=pFZlcdvkhCsVbbo9Dh0LekEgPNA2Ljy464GXlS0CXEM=; b=OxEDFPAYspITjxdQPV+8eAkXPBCu/at3xJWWiqFvVKx1ZgZRNnBOT3WXInSB/bVzHW tqKPLr601Rc4iJABn8+C2cFo0h2bqiqPVNgzb1LTIq1cbrMgBUVHpX9jyWDHkGwQNZ4T +D0nfNKr/wNXwY/dk/zd/4j0eLomPQOQpNuwuP48QwRgL8rAlqpfAetd+Xu+DhF4iAGI 8YovNQOwUjDL5J90UTeZgqhvOWUSm0koo2POqdy9KPucGdfLjNd2cVXlcq/WKqOzzv12 XDpduKXAxBKVCvY5MgaABQbaSPlvgSNdxyROdOuTNScyoJfb333b0wP+FC0sA8cg1qE0 Ntqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777154942; x=1777759742; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=pFZlcdvkhCsVbbo9Dh0LekEgPNA2Ljy464GXlS0CXEM=; b=dywwDNS7jS4UUJsqwOnUhr31zUdp0tWkX28k8e4YLmTWbSbj8+97MkFpmXH1xT62pL 2L8fTeRakUfRY7c+FpVwUCLyN+BiSZ4rOk1LIkSGGaGxqW5ESUalzWoloL1XGuAfSZ5D 2YJfpUZH2ErF6KALN/BxaE0kSx4FRINwpINnTP5yM01sep4KO5+jw7wj4poR8zInlCuW Az5wl33R5BvFTE3CXbX+RGuBMJjcuz8iaOYf81BymICGaw8fHnXCwqvJoFp6II2pLBdP +JZnTgVKzX34b2SDBLV3haIRHvqyXM1hBKD57kZCRIO/E3+8RtyPYT9xss5xiRQFYLEP /j9w== X-Forwarded-Encrypted: i=1; AFNElJ9wKlCNLP5UcBNeus27X3xh5zpnjz2j6gZ0aRN30I3TApVKw3ZilD7iI605zK04suOFGqkAzmkLuB46ppxx@vger.kernel.org X-Gm-Message-State: AOJu0YwfDmGLCSg0hgv4Jha+Y0yv05ZIzToD1xwsb5+cbXUm6+gjuTwc tfwRZWyIjPYftJg9oiNs65zRvfjD4gGvzlfqOPFjU6TKwB25W327t3sZ X-Gm-Gg: AeBDies2BGEgTdXWTHIjFwCM/2etGPSYU84EAEQ/Tg3vTMuTuXwsQ2s8uIZKZuZrfeM nTyHUCDBYaPLmyuIm3Ua9XWVPVPdovoLJYZw2nXxJC+JXV4W0xxwnTMQs9ss7Hti2oIgoZck+eA BxYnAwSAolZspDPh/Bi98ObjS40v4dYK2676VAUwfP4wOYmN/V675rZcn0LzzQEdMhl4aFAcNNN Sj5IRhCDlZ0ht6b5ZVi+zCIyK2K/txSvk5+hDEzIFX0+5saDbeFsfheVsgoEx4NVX+NAIdZ/z5K 9vQjr0uKgukjdfxbA1k3A+15KVxGt2wBG95L9rIygzko+KDnm33gRny4adbsel/zxuiutz5zjkN x18z6UVYJa4CZSYCjNuxXdTyHTwQnw6IxZCeMSPmwC3y69Dw84xm1AbX1Xe+SZfdSd5RyAQeltR IauYP2UEj0D0tDFrJRevnqM9tn9WcJ4LI4FkRJghOvLbHRd9m3/khs5i00CTLNlmUTQE1zg3x8A 612yQrp7eNA X-Received: by 2002:a05:600c:8284:b0:489:1f3e:5f69 with SMTP id 5b1f17b1804b1-4891f3e629bmr384462475e9.18.1777154942431; Sat, 25 Apr 2026 15:09:02 -0700 (PDT) Received: from f.. (cst-prg-93-232.cust.vodafone.cz. [46.135.93.232]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488fc0b4c85sm651984545e9.0.2026.04.25.15.09.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 25 Apr 2026 15:09:01 -0700 (PDT) From: Mateusz Guzik To: brauner@kernel.org Cc: viro@zeniv.linux.org.uk, jack@suse.cz, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, adobriyan@gmail.com, Mateusz Guzik Subject: [PATCH v3 3/3] fs: cache the string generated by reading /proc/filesystems Date: Sun, 26 Apr 2026 00:08:44 +0200 Message-ID: <20260425220844.1763933-4-mjguzik@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260425220844.1763933-1-mjguzik@gmail.com> References: <20260425220844.1763933-1-mjguzik@gmail.com> Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit It is being read surprisingly often (e.g., by mkdir, ls and even sed!). This is lock-protected pointer chasing over a linked list to pay for sprintf for every fs (32 on my boxen). Instead cache the result. While here make the file as permanent to avoid spurious ref trips in procfs. Signed-off-by: Mateusz Guzik --- fs/filesystems.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/fs/filesystems.c b/fs/filesystems.c index 7976366d4197..771fc31a69b8 100644 --- a/fs/filesystems.c +++ b/fs/filesystems.c @@ -31,6 +31,36 @@ static HLIST_HEAD(file_systems); static DEFINE_SPINLOCK(file_systems_lock); +#ifdef CONFIG_PROC_FS +/* + * Cache a stringified version of the filesystem list. + * + * The fs list gets queried a lot by userspace because of libselinux, including + * rather surprising programs (would you guess *sed* is on the list?). In order + * to reduce the overhead we cache the resulting string, which normally hangs + * around below 512 bytes in size. + * + * As the list almost never changes, its creation is not particularly optimized + * to keep things simple. + * + * We sort it out on read in order to not introduce a failure point for fs + * registration (in principle we may be unable to alloc memory for the list). + */ +struct file_systems_string { + struct rcu_head rcu; + unsigned long gen; + size_t len; + char string[]; +}; + +static unsigned long file_systems_gen; +static struct file_systems_string __rcu *file_systems_string; + +static void invalidate_filesystems_string(void); +#else +static inline void invalidate_filesystems_string(void) { } +#endif + /* WARNING: This can be used only if we _already_ own a reference */ struct file_system_type *get_filesystem(struct file_system_type *fs) { @@ -80,6 +110,7 @@ int register_filesystem(struct file_system_type *fs) if (find_filesystem(fs->name, strlen(fs->name))) return -EBUSY; hlist_add_tail_rcu(&fs->list, &file_systems); + invalidate_filesystems_string(); return 0; } EXPORT_SYMBOL(register_filesystem); @@ -101,6 +132,7 @@ int unregister_filesystem(struct file_system_type *fs) if (hlist_unhashed(&fs->list)) return -EINVAL; hlist_del_init_rcu(&fs->list); + invalidate_filesystems_string(); } synchronize_rcu(); return 0; @@ -209,7 +241,102 @@ int __init list_bdev_fs_names(char *buf, size_t size) } #ifdef CONFIG_PROC_FS -static int filesystems_proc_show(struct seq_file *m, void *v) +static void invalidate_filesystems_string(void) +{ + struct file_systems_string *old; + + lockdep_assert_held_write(&file_systems_lock); + file_systems_gen++; + old = rcu_replace_pointer(file_systems_string, NULL, + lockdep_is_held(&file_systems_lock)); + if (old) + kfree_rcu(old, rcu); +} + +static __cold noinline int regen_filesystems_string(void) +{ + struct file_system_type *p; + struct file_systems_string *old, *new; + size_t newlen, usedlen; + unsigned long gen; + +retry: + newlen = 0; + + /* pre-calc space for each fs */ + spin_lock(&file_systems_lock); + gen = file_systems_gen; + hlist_for_each_entry_rcu(p, &file_systems, list) { + if (!(p->fs_flags & FS_REQUIRES_DEV)) + newlen += strlen("nodev"); + newlen += strlen("\t") + strlen(p->name) + strlen("\n"); + } + spin_unlock(&file_systems_lock); + + new = kmalloc(offsetof(struct file_systems_string, string) + newlen + 1, + GFP_KERNEL); + if (!new) + return -ENOMEM; + + new->gen = gen; + new->len = newlen; + new->string[newlen] = '\0'; + + spin_lock(&file_systems_lock); + old = file_systems_string; + + /* + * Did someone beat us to it? + */ + if (old && old->gen == file_systems_gen) { + kfree(new); + return 0; + } + + /* + * Did the list change in the meantime? + */ + if (gen != file_systems_gen) { + kfree(new); + goto retry; + } + + /* + * Populate the string. + * + * We know we have just enough space because we calculated the right + * size the previous time we had the lock and confirmed the list has + * not changed after reacquiring it. + */ + usedlen = 0; + hlist_for_each_entry_rcu(p, &file_systems, list) { + usedlen += sprintf(&new->string[usedlen], "%s\t%s\n", + (p->fs_flags & FS_REQUIRES_DEV) ? "" : "nodev", + p->name); + } + + if (WARN_ON_ONCE(new->len != strlen(new->string))) { + /* + * Should never happen of course, keep this in case someone changes string + * generation above and messes it up. + */ + spin_unlock(&file_systems_lock); + if (old) + kfree_rcu(old, rcu); + return -EINVAL; + } + + /* + * Paired with consume fence in READ_ONCE() in filesystems_proc_show() + */ + smp_store_release(&file_systems_string, new); + spin_unlock(&file_systems_lock); + if (old) + kfree_rcu(old, rcu); + return 0; +} + +static __cold noinline int filesystems_proc_show_fallback(struct seq_file *m, void *v) { struct file_system_type *p; @@ -222,9 +349,33 @@ static int filesystems_proc_show(struct seq_file *m, void *v) return 0; } +static int filesystems_proc_show(struct seq_file *m, void *v) +{ + struct file_systems_string *fss; + + for (;;) { + scoped_guard(rcu) { + fss = rcu_dereference(file_systems_string); + if (likely(fss)) { + seq_write(m, fss->string, fss->len); + return 0; + } + } + + int err = regen_filesystems_string(); + if (unlikely(err)) + return filesystems_proc_show_fallback(m, v); + } +} + static int __init proc_filesystems_init(void) { - proc_create_single("filesystems", 0, NULL, filesystems_proc_show); + struct proc_dir_entry *pde; + + pde = proc_create_single("filesystems", 0, NULL, filesystems_proc_show); + if (!pde) + return -ENOMEM; + proc_make_permanent(pde); return 0; } module_init(proc_filesystems_init); -- 2.48.1