From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) (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 ADC443803D1 for ; Fri, 29 May 2026 17:18:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780075140; cv=none; b=GAPgjJsyQKEAUtt0QIxdkvwdN1xvgmwWFPYu120V4xDYkjGlooeJlE0/N3KvLSL9y2XdYqUNl9DQFUgoo3t5TihhHC1Qo9R2YJgc+OejpELk7d+OV0GAO7BjuI0zwgIJXCgPuMAO9ZuhqpnjfdGMn6zqOuVgqZFP1jpsD2RA3IA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780075140; c=relaxed/simple; bh=qO8gKdZWVCp3u2z9buUYizj/kCjZNyvtTAd/qWerhzQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=irYfJtD8qn51pqCOEzKWcS3jW2h771vXKyhTcd+q2JQQ7QLoZXqmL+mE2PGT8zwx/WvaZQcuJS31uHURGa/esQ3y1S6ylpJH5id8xxUiQC9uBRP5LupJNZNp28cYSZxZAAp+I56mfw4PYLE9dTzEjpMhZpBR7orofW/UpYP6Mxg= 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=B2tEfGFO; arc=none smtp.client-ip=209.85.128.51 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="B2tEfGFO" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-4891e5b9c1fso123048315e9.2 for ; Fri, 29 May 2026 10:18:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780075137; x=1780679937; 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=gjNfddSx1CZYCGwuNq70lpBd2sPOv8YHgLj4FuhfVY4=; b=B2tEfGFOCA2x3DjCT7GqiBTbSlM4Y0n4aouCWZKLBdbOp1v0BWXlvmegMVS6rNNyxy aAiWo7Q03je5ilO982YxaiAqzr9OxW3CRE8a1OdaFfAdsxYzye1SPyasBcjPOHUpeoon V2Vs3QG4tU0AInQl+dSBLYl94Slabal484M+gj7mhUDu91koEDqsWswaf+xCXMEwtDjX X3WRDelg+UlM4AOOlGx2Kd70YC5Pk9JZhvUcO0pkFxpN/PmLGKq03ca7VTeMFhNx906T 6416aGA1jwQmAErUKgdr2t2IX82Q/Hqrj7pRDSwo4LV9RCCK/GAgcFfk2Bri0vznfl2S Q7BA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780075137; x=1780679937; 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=gjNfddSx1CZYCGwuNq70lpBd2sPOv8YHgLj4FuhfVY4=; b=a83kaxErxSFYErRObUhtIqGd7/zKTOsqv0Kl4J2pqKQzdwoGHlhVYp+DbA2CQmptLt j23JYqZMlXnWc4239U6BJk3nUdpXt8iTMCHCTNZWJrXmXgGKGW4bMJs34yXNGm8vkPYJ IRrE7qqTheFueSmZhPMFuuUQVM+tJ5XEmaWxKomtbWTAr6ZJ51kE5GndtcGWLWR+SIQX yWnNJ4KWagdtBOmXqDBW52MKAdrmuw87OSaMg6rNdNoLmmn9w4gnOpxxr40WwtjLZmYF vbjBxvcU3bUrDiQGnf4aJ2aNujpu6XQ7Vu9XuqIuiBKIYxQY7hdD00WkTFQpI6WaJJju EqfA== X-Forwarded-Encrypted: i=1; AFNElJ9IINgxKSrequvT0TF93hDXkonN2tih4aeE14uydUQnd/Nz6i9qR6YxkDIfSpqwP4T77rdj68lBm2OhoYU=@vger.kernel.org X-Gm-Message-State: AOJu0YxLiflwJ5obhqvzytzBCcI1VQEenh5Yg3eoKfAqa04rhGPgXkpB XEI6qeh/p2i0ObQfd5bjPzqQchTFxGxFjmNIy6uiW9DlLmhHTNiRUhub X-Gm-Gg: Acq92OFFkJ0r/orhKPuRAY12LArXW+xqR20+JwusoG4WGcA8lsQcQZnNdMUZmy+pjaS ssZrEVStxroPqBbL/vS8QQ/kmWEgFeQN5j0kG4kEcd4cyncJK+XX93gEx2zuSmLrY0hTBMzEtum jG+IKdmN8lGGM7x/z49djWYwKBtx+8J7LNDvvOqYfrzxfPIsDHWbxOSbfTTUyxL7yzSsrJYvB2C NuY2D3PtRK7lO+0M+kTCc0Ep2R6w9LO7VP8ByNlEr24tbKGBSVb7wrGdvOu1uOVZQlZaIXj6WSy wvYP3VRykzd60+AfB00rU60H0FAa+vKFZ0Pt7u8UcM5fAV+LAwZGNmKjfZ0dAnOSI0XQxgF5tFe yS7tjC4c/fA7V50CKaOb9xWuNr52X+RqJKBa1crvZuQNAiTGfzpxWV2VZdTzJ5hmaWndDYw8r5t mIq+WT9zrd4eFyywMAQOUbpL+B+OZdkMnUxqXYAaiLKMvZSMMLksUueqiJ32rc1BBw4Hzj5wlOr 40plv/i90W3 X-Received: by 2002:a05:600d:4448:20b0:48f:e26a:1744 with SMTP id 5b1f17b1804b1-490a29096admr7694415e9.9.1780075137144; Fri, 29 May 2026 10:18:57 -0700 (PDT) Received: from f.. (cst-prg-92-135.cust.vodafone.cz. [46.135.92.135]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4909caa7faasm56255375e9.11.2026.05.29.10.18.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 29 May 2026 10:18:56 -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 v4 3/3] fs: cache the string generated by reading /proc/filesystems Date: Fri, 29 May 2026 19:18:40 +0200 Message-ID: <20260529171840.2576445-4-mjguzik@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260529171840.2576445-1-mjguzik@gmail.com> References: <20260529171840.2576445-1-mjguzik@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@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 | 153 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/fs/filesystems.c b/fs/filesystems.c index 7976366d4197..673a03b5f32b 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 __read_mostly __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,100 @@ 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) { + spin_unlock(&file_systems_lock); + kfree(new); + return 0; + } + + /* + * Did the list change in the meantime? + */ + if (gen != file_systems_gen) { + spin_unlock(&file_systems_lock); + 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); + kfree(new); + return -EINVAL; + } + + rcu_assign_pointer(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 +347,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