2022-10-31 21:53:26 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2019-08-19 02:18:48 +08:00
|
|
|
/*
|
2022-10-31 21:53:26 +08:00
|
|
|
* The NFSD open file cache.
|
2019-08-19 02:18:48 +08:00
|
|
|
*
|
|
|
|
* (c) 2015 - Jeff Layton <jeff.layton@primarydata.com>
|
2022-11-02 01:30:46 +08:00
|
|
|
*
|
|
|
|
* An nfsd_file object is a per-file collection of open state that binds
|
|
|
|
* together:
|
|
|
|
* - a struct file *
|
|
|
|
* - a user credential
|
|
|
|
* - a network namespace
|
|
|
|
* - a read-ahead context
|
|
|
|
* - monitoring for writeback errors
|
|
|
|
*
|
|
|
|
* nfsd_file objects are reference-counted. Consumers acquire a new
|
|
|
|
* object via the nfsd_file_acquire API. They manage their interest in
|
|
|
|
* the acquired object, and hence the object's reference count, via
|
|
|
|
* nfsd_file_get and nfsd_file_put. There are two varieties of nfsd_file
|
|
|
|
* object:
|
|
|
|
*
|
|
|
|
* * non-garbage-collected: When a consumer wants to precisely control
|
|
|
|
* the lifetime of a file's open state, it acquires a non-garbage-
|
|
|
|
* collected nfsd_file. The final nfsd_file_put releases the open
|
|
|
|
* state immediately.
|
|
|
|
*
|
|
|
|
* * garbage-collected: When a consumer does not control the lifetime
|
|
|
|
* of open state, it acquires a garbage-collected nfsd_file. The
|
|
|
|
* final nfsd_file_put allows the open state to linger for a period
|
|
|
|
* during which it may be re-used.
|
2019-08-19 02:18:48 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/hash.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/file.h>
|
2022-02-14 06:23:58 +08:00
|
|
|
#include <linux/pagemap.h>
|
2019-08-19 02:18:48 +08:00
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/list_lru.h>
|
|
|
|
#include <linux/fsnotify_backend.h>
|
|
|
|
#include <linux/fsnotify.h>
|
|
|
|
#include <linux/seq_file.h>
|
2022-07-09 02:26:23 +08:00
|
|
|
#include <linux/rhashtable.h>
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
#include "vfs.h"
|
|
|
|
#include "nfsd.h"
|
|
|
|
#include "nfsfh.h"
|
2019-09-03 01:02:55 +08:00
|
|
|
#include "netns.h"
|
2019-08-19 02:18:48 +08:00
|
|
|
#include "filecache.h"
|
|
|
|
#include "trace.h"
|
|
|
|
|
|
|
|
#define NFSD_LAUNDRETTE_DELAY (2 * HZ)
|
|
|
|
|
2022-07-09 02:26:16 +08:00
|
|
|
#define NFSD_FILE_CACHE_UP (0)
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
/* We only care about NFSD_MAY_READ/WRITE for this cache */
|
|
|
|
#define NFSD_FILE_MAY_MASK (NFSD_MAY_READ|NFSD_MAY_WRITE)
|
|
|
|
|
|
|
|
static DEFINE_PER_CPU(unsigned long, nfsd_file_cache_hits);
|
2022-07-09 02:23:59 +08:00
|
|
|
static DEFINE_PER_CPU(unsigned long, nfsd_file_acquisitions);
|
2022-07-09 02:24:05 +08:00
|
|
|
static DEFINE_PER_CPU(unsigned long, nfsd_file_releases);
|
2022-07-09 02:24:12 +08:00
|
|
|
static DEFINE_PER_CPU(unsigned long, nfsd_file_total_age);
|
2022-07-09 02:24:38 +08:00
|
|
|
static DEFINE_PER_CPU(unsigned long, nfsd_file_evictions);
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2020-01-07 02:18:05 +08:00
|
|
|
struct nfsd_fcache_disposal {
|
|
|
|
spinlock_t lock;
|
|
|
|
struct list_head freeme;
|
|
|
|
};
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
static struct kmem_cache *nfsd_file_slab;
|
|
|
|
static struct kmem_cache *nfsd_file_mark_slab;
|
|
|
|
static struct list_lru nfsd_file_lru;
|
2022-07-09 02:26:16 +08:00
|
|
|
static unsigned long nfsd_file_flags;
|
2019-08-19 02:18:48 +08:00
|
|
|
static struct fsnotify_group *nfsd_file_fsnotify_group;
|
|
|
|
static struct delayed_work nfsd_filecache_laundrette;
|
2022-11-25 04:09:04 +08:00
|
|
|
static struct rhltable nfsd_file_rhltable
|
2022-07-09 02:26:23 +08:00
|
|
|
____cacheline_aligned_in_smp;
|
|
|
|
|
|
|
|
static bool
|
|
|
|
nfsd_match_cred(const struct cred *c1, const struct cred *c2)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!uid_eq(c1->fsuid, c2->fsuid))
|
|
|
|
return false;
|
|
|
|
if (!gid_eq(c1->fsgid, c2->fsgid))
|
|
|
|
return false;
|
|
|
|
if (c1->group_info == NULL || c2->group_info == NULL)
|
|
|
|
return c1->group_info == c2->group_info;
|
|
|
|
if (c1->group_info->ngroups != c2->group_info->ngroups)
|
|
|
|
return false;
|
|
|
|
for (i = 0; i < c1->group_info->ngroups; i++) {
|
|
|
|
if (!gid_eq(c1->group_info->gid[i], c2->group_info->gid[i]))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct rhashtable_params nfsd_file_rhash_params = {
|
|
|
|
.key_len = sizeof_field(struct nfsd_file, nf_inode),
|
|
|
|
.key_offset = offsetof(struct nfsd_file, nf_inode),
|
2022-11-25 04:09:04 +08:00
|
|
|
.head_offset = offsetof(struct nfsd_file, nf_rlist),
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start with a single page hash table to reduce resizing churn
|
|
|
|
* on light workloads.
|
|
|
|
*/
|
|
|
|
.min_size = 256,
|
2022-07-09 02:26:23 +08:00
|
|
|
.automatic_shrinking = true,
|
|
|
|
};
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
static void
|
2020-01-07 02:18:05 +08:00
|
|
|
nfsd_file_schedule_laundrette(void)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-11-03 02:44:50 +08:00
|
|
|
if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags))
|
|
|
|
queue_delayed_work(system_wq, &nfsd_filecache_laundrette,
|
|
|
|
NFSD_LAUNDRETTE_DELAY);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_file_slab_free(struct rcu_head *rcu)
|
|
|
|
{
|
|
|
|
struct nfsd_file *nf = container_of(rcu, struct nfsd_file, nf_rcu);
|
|
|
|
|
|
|
|
put_cred(nf->nf_cred);
|
|
|
|
kmem_cache_free(nfsd_file_slab, nf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_file_mark_free(struct fsnotify_mark *mark)
|
|
|
|
{
|
|
|
|
struct nfsd_file_mark *nfm = container_of(mark, struct nfsd_file_mark,
|
|
|
|
nfm_mark);
|
|
|
|
|
|
|
|
kmem_cache_free(nfsd_file_mark_slab, nfm);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfsd_file_mark *
|
|
|
|
nfsd_file_mark_get(struct nfsd_file_mark *nfm)
|
|
|
|
{
|
2020-01-15 01:02:44 +08:00
|
|
|
if (!refcount_inc_not_zero(&nfm->nfm_ref))
|
2019-08-19 02:18:48 +08:00
|
|
|
return NULL;
|
|
|
|
return nfm;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_file_mark_put(struct nfsd_file_mark *nfm)
|
|
|
|
{
|
2020-01-15 01:02:44 +08:00
|
|
|
if (refcount_dec_and_test(&nfm->nfm_ref)) {
|
2019-08-19 02:18:48 +08:00
|
|
|
fsnotify_destroy_mark(&nfm->nfm_mark, nfsd_file_fsnotify_group);
|
|
|
|
fsnotify_put_mark(&nfm->nfm_mark);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfsd_file_mark *
|
2022-07-09 02:27:09 +08:00
|
|
|
nfsd_file_mark_find_or_create(struct nfsd_file *nf, struct inode *inode)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct fsnotify_mark *mark;
|
|
|
|
struct nfsd_file_mark *nfm = NULL, *new;
|
|
|
|
|
|
|
|
do {
|
2022-04-22 20:03:20 +08:00
|
|
|
fsnotify_group_lock(nfsd_file_fsnotify_group);
|
2024-03-18 02:41:47 +08:00
|
|
|
mark = fsnotify_find_inode_mark(inode,
|
|
|
|
nfsd_file_fsnotify_group);
|
2019-08-19 02:18:48 +08:00
|
|
|
if (mark) {
|
|
|
|
nfm = nfsd_file_mark_get(container_of(mark,
|
|
|
|
struct nfsd_file_mark,
|
|
|
|
nfm_mark));
|
2022-04-22 20:03:20 +08:00
|
|
|
fsnotify_group_unlock(nfsd_file_fsnotify_group);
|
2020-01-07 02:20:47 +08:00
|
|
|
if (nfm) {
|
|
|
|
fsnotify_put_mark(mark);
|
2019-08-19 02:18:48 +08:00
|
|
|
break;
|
2020-01-07 02:20:47 +08:00
|
|
|
}
|
|
|
|
/* Avoid soft lockup race with nfsd_file_mark_put() */
|
|
|
|
fsnotify_destroy_mark(mark, nfsd_file_fsnotify_group);
|
|
|
|
fsnotify_put_mark(mark);
|
2022-04-22 20:03:20 +08:00
|
|
|
} else {
|
|
|
|
fsnotify_group_unlock(nfsd_file_fsnotify_group);
|
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
/* allocate a new nfm */
|
|
|
|
new = kmem_cache_alloc(nfsd_file_mark_slab, GFP_KERNEL);
|
|
|
|
if (!new)
|
|
|
|
return NULL;
|
|
|
|
fsnotify_init_mark(&new->nfm_mark, nfsd_file_fsnotify_group);
|
|
|
|
new->nfm_mark.mask = FS_ATTRIB|FS_DELETE_SELF;
|
2020-01-15 01:02:44 +08:00
|
|
|
refcount_set(&new->nfm_ref, 1);
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
err = fsnotify_add_inode_mark(&new->nfm_mark, inode, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the add was successful, then return the object.
|
|
|
|
* Otherwise, we need to put the reference we hold on the
|
|
|
|
* nfm_mark. The fsnotify code will take a reference and put
|
|
|
|
* it on failure, so we can't just free it directly. It's also
|
|
|
|
* not safe to call fsnotify_destroy_mark on it as the
|
|
|
|
* mark->group will be NULL. Thus, we can't let the nfm_ref
|
|
|
|
* counter drive the destruction at this point.
|
|
|
|
*/
|
|
|
|
if (likely(!err))
|
|
|
|
nfm = new;
|
|
|
|
else
|
|
|
|
fsnotify_put_mark(&new->nfm_mark);
|
|
|
|
} while (unlikely(err == -EEXIST));
|
|
|
|
|
|
|
|
return nfm;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct nfsd_file *
|
2022-11-25 04:09:04 +08:00
|
|
|
nfsd_file_alloc(struct net *net, struct inode *inode, unsigned char need,
|
|
|
|
bool want_gc)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
|
|
|
struct nfsd_file *nf;
|
|
|
|
|
|
|
|
nf = kmem_cache_alloc(nfsd_file_slab, GFP_KERNEL);
|
2022-11-25 04:09:04 +08:00
|
|
|
if (unlikely(!nf))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&nf->nf_lru);
|
|
|
|
nf->nf_birthtime = ktime_get();
|
|
|
|
nf->nf_file = NULL;
|
|
|
|
nf->nf_cred = get_current_cred();
|
|
|
|
nf->nf_net = net;
|
|
|
|
nf->nf_flags = want_gc ?
|
|
|
|
BIT(NFSD_FILE_HASHED) | BIT(NFSD_FILE_PENDING) | BIT(NFSD_FILE_GC) :
|
|
|
|
BIT(NFSD_FILE_HASHED) | BIT(NFSD_FILE_PENDING);
|
|
|
|
nf->nf_inode = inode;
|
|
|
|
refcount_set(&nf->nf_ref, 1);
|
|
|
|
nf->nf_may = need;
|
|
|
|
nf->nf_mark = NULL;
|
2019-08-19 02:18:48 +08:00
|
|
|
return nf;
|
|
|
|
}
|
|
|
|
|
2023-02-08 01:02:46 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_check_write_error - check for writeback errors on a file
|
|
|
|
* @nf: nfsd_file to check for writeback errors
|
|
|
|
*
|
|
|
|
* Check whether a nfsd_file has an unseen error. Reset the write
|
|
|
|
* verifier if so.
|
|
|
|
*/
|
2022-11-03 02:44:48 +08:00
|
|
|
static void
|
|
|
|
nfsd_file_check_write_error(struct nfsd_file *nf)
|
|
|
|
{
|
|
|
|
struct file *file = nf->nf_file;
|
|
|
|
|
2023-02-08 01:02:46 +08:00
|
|
|
if ((file->f_mode & FMODE_WRITE) &&
|
|
|
|
filemap_check_wb_err(file->f_mapping, READ_ONCE(file->f_wb_err)))
|
|
|
|
nfsd_reset_write_verifier(net_generic(nf->nf_net, nfsd_net_id));
|
2022-11-03 02:44:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_file_hash_remove(struct nfsd_file *nf)
|
|
|
|
{
|
|
|
|
trace_nfsd_file_unhash(nf);
|
2022-11-25 04:09:04 +08:00
|
|
|
rhltable_remove(&nfsd_file_rhltable, &nf->nf_rlist,
|
|
|
|
nfsd_file_rhash_params);
|
2022-11-03 02:44:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
nfsd_file_unhash(struct nfsd_file *nf)
|
|
|
|
{
|
|
|
|
if (test_and_clear_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
|
|
|
|
nfsd_file_hash_remove(nf);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
static void
|
2019-08-19 02:18:48 +08:00
|
|
|
nfsd_file_free(struct nfsd_file *nf)
|
|
|
|
{
|
2022-07-09 02:24:12 +08:00
|
|
|
s64 age = ktime_to_ms(ktime_sub(ktime_get(), nf->nf_birthtime));
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-11-03 02:44:48 +08:00
|
|
|
trace_nfsd_file_free(nf);
|
|
|
|
|
2022-07-09 02:24:05 +08:00
|
|
|
this_cpu_inc(nfsd_file_releases);
|
2022-07-09 02:24:12 +08:00
|
|
|
this_cpu_add(nfsd_file_total_age, age);
|
2022-07-09 02:24:05 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_unhash(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
if (nf->nf_mark)
|
|
|
|
nfsd_file_mark_put(nf->nf_mark);
|
|
|
|
if (nf->nf_file) {
|
2023-02-08 01:02:46 +08:00
|
|
|
nfsd_file_check_write_error(nf);
|
2023-12-15 09:18:31 +08:00
|
|
|
nfsd_filp_close(nf->nf_file);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
2022-07-09 02:25:04 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If this item is still linked via nf_lru, that's a bug.
|
|
|
|
* WARN and leak it to preserve system stability.
|
|
|
|
*/
|
|
|
|
if (WARN_ON_ONCE(!list_empty(&nf->nf_lru)))
|
2022-12-11 19:19:33 +08:00
|
|
|
return;
|
2022-07-09 02:25:04 +08:00
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
call_rcu(&nf->nf_rcu, nfsd_file_slab_free);
|
|
|
|
}
|
|
|
|
|
2019-09-03 01:02:57 +08:00
|
|
|
static bool
|
|
|
|
nfsd_file_check_writeback(struct nfsd_file *nf)
|
|
|
|
{
|
|
|
|
struct file *file = nf->nf_file;
|
|
|
|
struct address_space *mapping;
|
|
|
|
|
2023-02-15 19:53:54 +08:00
|
|
|
/* File not open for write? */
|
|
|
|
if (!(file->f_mode & FMODE_WRITE))
|
2019-09-03 01:02:57 +08:00
|
|
|
return false;
|
2023-02-15 19:53:54 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Some filesystems (e.g. NFS) flush all dirty data on close.
|
|
|
|
* On others, there is no need to wait for writeback.
|
|
|
|
*/
|
|
|
|
if (!(file_inode(file)->i_sb->s_export_op->flags & EXPORT_OP_FLUSH_ON_CLOSE))
|
|
|
|
return false;
|
|
|
|
|
2019-09-03 01:02:57 +08:00
|
|
|
mapping = file->f_mapping;
|
|
|
|
return mapping_tagged(mapping, PAGECACHE_TAG_DIRTY) ||
|
|
|
|
mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK);
|
|
|
|
}
|
|
|
|
|
2023-02-15 19:53:54 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
static bool nfsd_file_lru_add(struct nfsd_file *nf)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-07-09 02:25:17 +08:00
|
|
|
set_bit(NFSD_FILE_REFERENCED, &nf->nf_flags);
|
list_lru: allow explicit memcg and NUMA node selection
Patch series "workload-specific and memory pressure-driven zswap
writeback", v8.
There are currently several issues with zswap writeback:
1. There is only a single global LRU for zswap, making it impossible to
perform worload-specific shrinking - an memcg under memory pressure
cannot determine which pages in the pool it owns, and often ends up
writing pages from other memcgs. This issue has been previously
observed in practice and mitigated by simply disabling
memcg-initiated shrinking:
https://lore.kernel.org/all/20230530232435.3097106-1-nphamcs@gmail.com/T/#u
But this solution leaves a lot to be desired, as we still do not
have an avenue for an memcg to free up its own memory locked up in
the zswap pool.
2. We only shrink the zswap pool when the user-defined limit is hit.
This means that if we set the limit too high, cold data that are
unlikely to be used again will reside in the pool, wasting precious
memory. It is hard to predict how much zswap space will be needed
ahead of time, as this depends on the workload (specifically, on
factors such as memory access patterns and compressibility of the
memory pages).
This patch series solves these issues by separating the global zswap LRU
into per-memcg and per-NUMA LRUs, and performs workload-specific (i.e
memcg- and NUMA-aware) zswap writeback under memory pressure. The new
shrinker does not have any parameter that must be tuned by the user, and
can be opted in or out on a per-memcg basis.
As a proof of concept, we ran the following synthetic benchmark: build the
linux kernel in a memory-limited cgroup, and allocate some cold data in
tmpfs to see if the shrinker could write them out and improved the overall
performance. Depending on the amount of cold data generated, we observe
from 14% to 35% reduction in kernel CPU time used in the kernel builds.
This patch (of 6):
The interface of list_lru is based on the assumption that the list node
and the data it represents belong to the same allocated on the correct
node/memcg. While this assumption is valid for existing slab objects LRU
such as dentries and inodes, it is undocumented, and rather inflexible for
certain potential list_lru users (such as the upcoming zswap shrinker and
the THP shrinker). It has caused us a lot of issues during our
development.
This patch changes list_lru interface so that the caller must explicitly
specify numa node and memcg when adding and removing objects. The old
list_lru_add() and list_lru_del() are renamed to list_lru_add_obj() and
list_lru_del_obj(), respectively.
It also extends the list_lru API with a new function, list_lru_putback,
which undoes a previous list_lru_isolate call. Unlike list_lru_add, it
does not increment the LRU node count (as list_lru_isolate does not
decrement the node count). list_lru_putback also allows for explicit
memcg and NUMA node selection.
Link: https://lkml.kernel.org/r/20231130194023.4102148-1-nphamcs@gmail.com
Link: https://lkml.kernel.org/r/20231130194023.4102148-2-nphamcs@gmail.com
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Tested-by: Bagas Sanjaya <bagasdotme@gmail.com>
Cc: Chris Li <chrisl@kernel.org>
Cc: Dan Streetman <ddstreet@ieee.org>
Cc: Domenico Cerasuolo <cerasuolodomenico@gmail.com>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Seth Jennings <sjenning@redhat.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vitaly Wool <vitaly.wool@konsulko.com>
Cc: Yosry Ahmed <yosryahmed@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-12-01 03:40:18 +08:00
|
|
|
if (list_lru_add_obj(&nfsd_file_lru, &nf->nf_lru)) {
|
2022-07-09 02:25:11 +08:00
|
|
|
trace_nfsd_file_lru_add(nf);
|
2022-12-11 19:19:33 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2022-07-09 02:25:11 +08:00
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
static bool nfsd_file_lru_remove(struct nfsd_file *nf)
|
2022-07-09 02:25:11 +08:00
|
|
|
{
|
list_lru: allow explicit memcg and NUMA node selection
Patch series "workload-specific and memory pressure-driven zswap
writeback", v8.
There are currently several issues with zswap writeback:
1. There is only a single global LRU for zswap, making it impossible to
perform worload-specific shrinking - an memcg under memory pressure
cannot determine which pages in the pool it owns, and often ends up
writing pages from other memcgs. This issue has been previously
observed in practice and mitigated by simply disabling
memcg-initiated shrinking:
https://lore.kernel.org/all/20230530232435.3097106-1-nphamcs@gmail.com/T/#u
But this solution leaves a lot to be desired, as we still do not
have an avenue for an memcg to free up its own memory locked up in
the zswap pool.
2. We only shrink the zswap pool when the user-defined limit is hit.
This means that if we set the limit too high, cold data that are
unlikely to be used again will reside in the pool, wasting precious
memory. It is hard to predict how much zswap space will be needed
ahead of time, as this depends on the workload (specifically, on
factors such as memory access patterns and compressibility of the
memory pages).
This patch series solves these issues by separating the global zswap LRU
into per-memcg and per-NUMA LRUs, and performs workload-specific (i.e
memcg- and NUMA-aware) zswap writeback under memory pressure. The new
shrinker does not have any parameter that must be tuned by the user, and
can be opted in or out on a per-memcg basis.
As a proof of concept, we ran the following synthetic benchmark: build the
linux kernel in a memory-limited cgroup, and allocate some cold data in
tmpfs to see if the shrinker could write them out and improved the overall
performance. Depending on the amount of cold data generated, we observe
from 14% to 35% reduction in kernel CPU time used in the kernel builds.
This patch (of 6):
The interface of list_lru is based on the assumption that the list node
and the data it represents belong to the same allocated on the correct
node/memcg. While this assumption is valid for existing slab objects LRU
such as dentries and inodes, it is undocumented, and rather inflexible for
certain potential list_lru users (such as the upcoming zswap shrinker and
the THP shrinker). It has caused us a lot of issues during our
development.
This patch changes list_lru interface so that the caller must explicitly
specify numa node and memcg when adding and removing objects. The old
list_lru_add() and list_lru_del() are renamed to list_lru_add_obj() and
list_lru_del_obj(), respectively.
It also extends the list_lru API with a new function, list_lru_putback,
which undoes a previous list_lru_isolate call. Unlike list_lru_add, it
does not increment the LRU node count (as list_lru_isolate does not
decrement the node count). list_lru_putback also allows for explicit
memcg and NUMA node selection.
Link: https://lkml.kernel.org/r/20231130194023.4102148-1-nphamcs@gmail.com
Link: https://lkml.kernel.org/r/20231130194023.4102148-2-nphamcs@gmail.com
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Tested-by: Bagas Sanjaya <bagasdotme@gmail.com>
Cc: Chris Li <chrisl@kernel.org>
Cc: Dan Streetman <ddstreet@ieee.org>
Cc: Domenico Cerasuolo <cerasuolodomenico@gmail.com>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Seth Jennings <sjenning@redhat.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vitaly Wool <vitaly.wool@konsulko.com>
Cc: Yosry Ahmed <yosryahmed@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-12-01 03:40:18 +08:00
|
|
|
if (list_lru_del_obj(&nfsd_file_lru, &nf->nf_lru)) {
|
2022-07-09 02:25:11 +08:00
|
|
|
trace_nfsd_file_lru_del(nf);
|
2022-12-11 19:19:33 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2022-07-09 02:25:11 +08:00
|
|
|
}
|
|
|
|
|
2022-11-03 02:44:48 +08:00
|
|
|
struct nfsd_file *
|
|
|
|
nfsd_file_get(struct nfsd_file *nf)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2023-01-06 23:33:47 +08:00
|
|
|
if (nf && refcount_inc_not_zero(&nf->nf_ref))
|
2022-11-03 02:44:48 +08:00
|
|
|
return nf;
|
|
|
|
return NULL;
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_put - put the reference to a nfsd_file
|
|
|
|
* @nf: nfsd_file of which to put the reference
|
|
|
|
*
|
|
|
|
* Put a reference to a nfsd_file. In the non-GC case, we just put the
|
|
|
|
* reference immediately. In the GC case, if the reference would be
|
|
|
|
* the last one, the put it on the LRU instead to be cleaned up later.
|
|
|
|
*/
|
2019-08-19 02:18:48 +08:00
|
|
|
void
|
|
|
|
nfsd_file_put(struct nfsd_file *nf)
|
|
|
|
{
|
2022-05-12 01:02:21 +08:00
|
|
|
might_sleep();
|
2022-12-11 19:19:33 +08:00
|
|
|
trace_nfsd_file_put(nf);
|
2022-05-12 01:02:21 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
if (test_bit(NFSD_FILE_GC, &nf->nf_flags) &&
|
|
|
|
test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
|
|
|
|
/*
|
|
|
|
* If this is the last reference (nf_ref == 1), then try to
|
|
|
|
* transfer it to the LRU.
|
|
|
|
*/
|
|
|
|
if (refcount_dec_not_one(&nf->nf_ref))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Try to add it to the LRU. If that fails, decrement. */
|
|
|
|
if (nfsd_file_lru_add(nf)) {
|
|
|
|
/* If it's still hashed, we're done */
|
|
|
|
if (test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
|
|
|
|
nfsd_file_schedule_laundrette();
|
|
|
|
return;
|
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/*
|
|
|
|
* We're racing with unhashing, so try to remove it from
|
|
|
|
* the LRU. If removal fails, then someone else already
|
|
|
|
* has our reference.
|
|
|
|
*/
|
|
|
|
if (!nfsd_file_lru_remove(nf))
|
|
|
|
return;
|
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
2022-12-11 19:19:33 +08:00
|
|
|
if (refcount_dec_and_test(&nf->nf_ref))
|
|
|
|
nfsd_file_free(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_dispose_list(struct list_head *dispose)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
|
|
|
struct nfsd_file *nf;
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
while (!list_empty(dispose)) {
|
2019-08-19 02:18:48 +08:00
|
|
|
nf = list_first_entry(dispose, struct nfsd_file, nf_lru);
|
2022-07-09 02:25:04 +08:00
|
|
|
list_del_init(&nf->nf_lru);
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_free(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-15 05:31:44 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_dispose_list_delayed - move list of dead files to net's freeme list
|
|
|
|
* @dispose: list of nfsd_files to be disposed
|
|
|
|
*
|
|
|
|
* Transfers each file to the "freeme" list for its nfsd_net, to eventually
|
|
|
|
* be disposed of by the per-net garbage collector.
|
|
|
|
*/
|
2020-01-07 02:18:05 +08:00
|
|
|
static void
|
|
|
|
nfsd_file_dispose_list_delayed(struct list_head *dispose)
|
|
|
|
{
|
|
|
|
while(!list_empty(dispose)) {
|
2023-04-15 05:31:44 +08:00
|
|
|
struct nfsd_file *nf = list_first_entry(dispose,
|
|
|
|
struct nfsd_file, nf_lru);
|
|
|
|
struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
|
|
|
|
struct nfsd_fcache_disposal *l = nn->fcache_disposal;
|
|
|
|
|
|
|
|
spin_lock(&l->lock);
|
|
|
|
list_move_tail(&nf->nf_lru, &l->freeme);
|
|
|
|
spin_unlock(&l->lock);
|
2023-12-15 09:18:30 +08:00
|
|
|
svc_wake_up(nn->nfsd_serv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nfsd_file_net_dispose - deal with nfsd_files waiting to be disposed.
|
|
|
|
* @nn: nfsd_net in which to find files to be disposed.
|
|
|
|
*
|
|
|
|
* When files held open for nfsv3 are removed from the filecache, whether
|
|
|
|
* due to memory pressure or garbage collection, they are queued to
|
|
|
|
* a per-net-ns queue. This function completes the disposal, either
|
|
|
|
* directly or by waking another nfsd thread to help with the work.
|
|
|
|
*/
|
|
|
|
void nfsd_file_net_dispose(struct nfsd_net *nn)
|
|
|
|
{
|
|
|
|
struct nfsd_fcache_disposal *l = nn->fcache_disposal;
|
|
|
|
|
|
|
|
if (!list_empty(&l->freeme)) {
|
|
|
|
LIST_HEAD(dispose);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
spin_lock(&l->lock);
|
|
|
|
for (i = 0; i < 8 && !list_empty(&l->freeme); i++)
|
|
|
|
list_move(l->freeme.next, &dispose);
|
|
|
|
spin_unlock(&l->lock);
|
|
|
|
if (!list_empty(&l->freeme))
|
|
|
|
/* Wake up another thread to share the work
|
|
|
|
* *before* doing any actual disposing.
|
|
|
|
*/
|
|
|
|
svc_wake_up(nn->nfsd_serv);
|
|
|
|
nfsd_file_dispose_list(&dispose);
|
2020-01-07 02:18:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-09 02:25:17 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_lru_cb - Examine an entry on the LRU list
|
|
|
|
* @item: LRU entry to examine
|
|
|
|
* @lru: controlling LRU
|
|
|
|
* @lock: LRU list lock (unused)
|
|
|
|
* @arg: dispose list
|
|
|
|
*
|
|
|
|
* Return values:
|
|
|
|
* %LRU_REMOVED: @item was removed from the LRU
|
2022-07-09 02:25:24 +08:00
|
|
|
* %LRU_ROTATE: @item is to be moved to the LRU tail
|
2022-07-09 02:25:17 +08:00
|
|
|
* %LRU_SKIP: @item cannot be evicted
|
2019-08-19 02:18:48 +08:00
|
|
|
*/
|
|
|
|
static enum lru_status
|
|
|
|
nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
|
|
|
|
spinlock_t *lock, void *arg)
|
|
|
|
__releases(lock)
|
|
|
|
__acquires(lock)
|
|
|
|
{
|
|
|
|
struct list_head *head = arg;
|
|
|
|
struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru);
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/* We should only be dealing with GC entries here */
|
|
|
|
WARN_ON_ONCE(!test_bit(NFSD_FILE_GC, &nf->nf_flags));
|
2019-09-03 01:02:57 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't throw out files that are still undergoing I/O or
|
|
|
|
* that have uncleared errors pending.
|
|
|
|
*/
|
2022-07-09 02:25:11 +08:00
|
|
|
if (nfsd_file_check_writeback(nf)) {
|
|
|
|
trace_nfsd_file_gc_writeback(nf);
|
|
|
|
return LRU_SKIP;
|
|
|
|
}
|
2019-09-03 01:02:57 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/* If it was recently added to the list, skip it */
|
2022-07-09 02:25:11 +08:00
|
|
|
if (test_and_clear_bit(NFSD_FILE_REFERENCED, &nf->nf_flags)) {
|
|
|
|
trace_nfsd_file_gc_referenced(nf);
|
2022-07-09 02:25:24 +08:00
|
|
|
return LRU_ROTATE;
|
2022-07-09 02:25:11 +08:00
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/*
|
|
|
|
* Put the reference held on behalf of the LRU. If it wasn't the last
|
|
|
|
* one, then just remove it from the LRU and ignore it.
|
|
|
|
*/
|
|
|
|
if (!refcount_dec_and_test(&nf->nf_ref)) {
|
|
|
|
trace_nfsd_file_gc_in_use(nf);
|
|
|
|
list_lru_isolate(lru, &nf->nf_lru);
|
|
|
|
return LRU_REMOVED;
|
2022-07-09 02:25:11 +08:00
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/* Refcount went to zero. Unhash it and queue it to the dispose list */
|
|
|
|
nfsd_file_unhash(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
list_lru_isolate_move(lru, &nf->nf_lru, head);
|
2022-07-09 02:24:38 +08:00
|
|
|
this_cpu_inc(nfsd_file_evictions);
|
2022-07-09 02:25:11 +08:00
|
|
|
trace_nfsd_file_gc_disposed(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
return LRU_REMOVED;
|
|
|
|
}
|
|
|
|
|
2020-01-07 02:18:05 +08:00
|
|
|
static void
|
|
|
|
nfsd_file_gc(void)
|
|
|
|
{
|
2022-07-09 02:24:25 +08:00
|
|
|
LIST_HEAD(dispose);
|
2022-07-09 02:24:38 +08:00
|
|
|
unsigned long ret;
|
2022-07-09 02:24:25 +08:00
|
|
|
|
2022-07-09 02:24:38 +08:00
|
|
|
ret = list_lru_walk(&nfsd_file_lru, nfsd_file_lru_cb,
|
2022-07-09 02:25:24 +08:00
|
|
|
&dispose, list_lru_count(&nfsd_file_lru));
|
2022-07-09 02:24:38 +08:00
|
|
|
trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru));
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_dispose_list_delayed(&dispose);
|
2020-01-07 02:18:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_file_gc_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
nfsd_file_gc();
|
2022-11-03 02:44:50 +08:00
|
|
|
if (list_lru_count(&nfsd_file_lru))
|
|
|
|
nfsd_file_schedule_laundrette();
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long
|
|
|
|
nfsd_file_lru_count(struct shrinker *s, struct shrink_control *sc)
|
|
|
|
{
|
|
|
|
return list_lru_count(&nfsd_file_lru);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long
|
|
|
|
nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc)
|
|
|
|
{
|
2022-07-09 02:24:31 +08:00
|
|
|
LIST_HEAD(dispose);
|
|
|
|
unsigned long ret;
|
|
|
|
|
|
|
|
ret = list_lru_shrink_walk(&nfsd_file_lru, sc,
|
|
|
|
nfsd_file_lru_cb, &dispose);
|
2022-07-09 02:24:38 +08:00
|
|
|
trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru));
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_dispose_list_delayed(&dispose);
|
2022-07-09 02:24:31 +08:00
|
|
|
return ret;
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
2023-09-11 17:44:11 +08:00
|
|
|
static struct shrinker *nfsd_file_shrinker;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2023-01-21 03:52:14 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_cond_queue - conditionally unhash and queue a nfsd_file
|
|
|
|
* @nf: nfsd_file to attempt to queue
|
|
|
|
* @dispose: private list to queue successfully-put objects
|
|
|
|
*
|
|
|
|
* Unhash an nfsd_file, try to get a reference to it, and then put that
|
|
|
|
* reference. If it's the last reference, queue it to the dispose list.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
nfsd_file_cond_queue(struct nfsd_file *nf, struct list_head *dispose)
|
|
|
|
__must_hold(RCU)
|
|
|
|
{
|
|
|
|
int decrement = 1;
|
|
|
|
|
|
|
|
/* If we raced with someone else unhashing, ignore it */
|
|
|
|
if (!nfsd_file_unhash(nf))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* If we can't get a reference, ignore it */
|
|
|
|
if (!nfsd_file_get(nf))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Extra decrement if we remove from the LRU */
|
|
|
|
if (nfsd_file_lru_remove(nf))
|
|
|
|
++decrement;
|
|
|
|
|
|
|
|
/* If refcount goes to 0, then put on the dispose list */
|
|
|
|
if (refcount_sub_and_test(decrement, &nf->nf_ref)) {
|
|
|
|
list_add(&nf->nf_lru, dispose);
|
|
|
|
trace_nfsd_file_closing(nf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_queue_for_close: try to close out any open nfsd_files for an inode
|
|
|
|
* @inode: inode on which to close out nfsd_files
|
|
|
|
* @dispose: list on which to gather nfsd_files to close out
|
|
|
|
*
|
2022-11-25 04:09:04 +08:00
|
|
|
* An nfsd_file represents a struct file being held open on behalf of nfsd.
|
|
|
|
* An open file however can block other activity (such as leases), or cause
|
2022-12-11 19:19:33 +08:00
|
|
|
* undesirable behavior (e.g. spurious silly-renames when reexporting NFS).
|
|
|
|
*
|
|
|
|
* This function is intended to find open nfsd_files when this sort of
|
|
|
|
* conflicting access occurs and then attempt to close those files out.
|
|
|
|
*
|
|
|
|
* Populates the dispose list with entries that have already had their
|
|
|
|
* refcounts go to zero. The actual free of an nfsd_file can be expensive,
|
|
|
|
* so we leave it up to the caller whether it wants to wait or not.
|
2022-07-09 02:25:57 +08:00
|
|
|
*/
|
2022-12-11 19:19:33 +08:00
|
|
|
static void
|
|
|
|
nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-11-25 04:09:04 +08:00
|
|
|
struct rhlist_head *tmp, *list;
|
2022-07-09 02:26:30 +08:00
|
|
|
struct nfsd_file *nf;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-07-09 02:26:30 +08:00
|
|
|
rcu_read_lock();
|
2022-11-25 04:09:04 +08:00
|
|
|
list = rhltable_lookup(&nfsd_file_rhltable, &inode,
|
|
|
|
nfsd_file_rhash_params);
|
|
|
|
rhl_for_each_entry_rcu(nf, tmp, list, nf_rlist) {
|
|
|
|
if (!test_bit(NFSD_FILE_GC, &nf->nf_flags))
|
|
|
|
continue;
|
2023-01-21 03:52:14 +08:00
|
|
|
nfsd_file_cond_queue(nf, dispose);
|
2022-11-25 04:09:04 +08:00
|
|
|
}
|
2022-07-09 02:26:30 +08:00
|
|
|
rcu_read_unlock();
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-11 19:19:33 +08:00
|
|
|
* nfsd_file_close_inode - attempt a delayed close of a nfsd_file
|
2019-08-19 02:18:48 +08:00
|
|
|
* @inode: inode of the file to attempt to remove
|
|
|
|
*
|
2022-12-11 19:19:33 +08:00
|
|
|
* Close out any open nfsd_files that can be reaped for @inode. The
|
|
|
|
* actual freeing is deferred to the dispose_list_delayed infrastructure.
|
|
|
|
*
|
|
|
|
* This is used by the fsnotify callbacks and setlease notifier.
|
2019-08-19 02:18:48 +08:00
|
|
|
*/
|
2022-12-11 19:19:33 +08:00
|
|
|
static void
|
|
|
|
nfsd_file_close_inode(struct inode *inode)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
|
|
|
LIST_HEAD(dispose);
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_queue_for_close(inode, &dispose);
|
|
|
|
nfsd_file_dispose_list_delayed(&dispose);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-11 19:19:33 +08:00
|
|
|
* nfsd_file_close_inode_sync - attempt to forcibly close a nfsd_file
|
2019-08-19 02:18:48 +08:00
|
|
|
* @inode: inode of the file to attempt to remove
|
|
|
|
*
|
2022-12-11 19:19:33 +08:00
|
|
|
* Close out any open nfsd_files that can be reaped for @inode. The
|
|
|
|
* nfsd_files are closed out synchronously.
|
|
|
|
*
|
|
|
|
* This is called from nfsd_rename and nfsd_unlink to avoid silly-renames
|
|
|
|
* when reexporting NFS.
|
2019-08-19 02:18:48 +08:00
|
|
|
*/
|
2022-12-11 19:19:33 +08:00
|
|
|
void
|
|
|
|
nfsd_file_close_inode_sync(struct inode *inode)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-12-11 19:19:33 +08:00
|
|
|
struct nfsd_file *nf;
|
2019-08-19 02:18:48 +08:00
|
|
|
LIST_HEAD(dispose);
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
trace_nfsd_file_close(inode);
|
|
|
|
|
|
|
|
nfsd_file_queue_for_close(inode, &dispose);
|
|
|
|
while (!list_empty(&dispose)) {
|
|
|
|
nf = list_first_entry(&dispose, struct nfsd_file, nf_lru);
|
|
|
|
list_del_init(&nf->nf_lru);
|
|
|
|
nfsd_file_free(nf);
|
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
nfsd_file_lease_notifier_call(struct notifier_block *nb, unsigned long arg,
|
|
|
|
void *data)
|
|
|
|
{
|
2024-07-12 19:26:44 +08:00
|
|
|
struct file_lease *fl = data;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
/* Only close files for F_SETLEASE leases */
|
2024-02-01 07:02:23 +08:00
|
|
|
if (fl->c.flc_flags & FL_LEASE)
|
|
|
|
nfsd_file_close_inode(file_inode(fl->c.flc_file));
|
2019-08-19 02:18:48 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block nfsd_file_lease_notifier = {
|
|
|
|
.notifier_call = nfsd_file_lease_notifier_call,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
2020-07-22 20:58:48 +08:00
|
|
|
nfsd_file_fsnotify_handle_event(struct fsnotify_mark *mark, u32 mask,
|
|
|
|
struct inode *inode, struct inode *dir,
|
2020-12-02 20:07:07 +08:00
|
|
|
const struct qstr *name, u32 cookie)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2021-10-26 03:27:26 +08:00
|
|
|
if (WARN_ON_ONCE(!inode))
|
|
|
|
return 0;
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
trace_nfsd_file_fsnotify_handle_event(inode, mask);
|
|
|
|
|
|
|
|
/* Should be no marks on non-regular files */
|
|
|
|
if (!S_ISREG(inode->i_mode)) {
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* don't close files if this was not the last link */
|
|
|
|
if (mask & FS_ATTRIB) {
|
|
|
|
if (inode->i_nlink)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nfsd_file_close_inode(inode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const struct fsnotify_ops nfsd_file_fsnotify_ops = {
|
2020-07-22 20:58:48 +08:00
|
|
|
.handle_inode_event = nfsd_file_fsnotify_handle_event,
|
2019-08-19 02:18:48 +08:00
|
|
|
.free_mark = nfsd_file_mark_free,
|
|
|
|
};
|
|
|
|
|
|
|
|
int
|
|
|
|
nfsd_file_cache_init(void)
|
|
|
|
{
|
2022-07-09 02:26:36 +08:00
|
|
|
int ret;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-07-09 02:26:16 +08:00
|
|
|
lockdep_assert_held(&nfsd_mutex);
|
|
|
|
if (test_and_set_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 1)
|
2019-08-19 02:18:48 +08:00
|
|
|
return 0;
|
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
ret = rhltable_init(&nfsd_file_rhltable, &nfsd_file_rhash_params);
|
2022-07-09 02:26:23 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = -ENOMEM;
|
2024-01-31 14:56:53 +08:00
|
|
|
nfsd_file_slab = KMEM_CACHE(nfsd_file, 0);
|
2019-08-19 02:18:48 +08:00
|
|
|
if (!nfsd_file_slab) {
|
|
|
|
pr_err("nfsd: unable to create nfsd_file_slab\n");
|
|
|
|
goto out_err;
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:56:53 +08:00
|
|
|
nfsd_file_mark_slab = KMEM_CACHE(nfsd_file_mark, 0);
|
2019-08-19 02:18:48 +08:00
|
|
|
if (!nfsd_file_mark_slab) {
|
|
|
|
pr_err("nfsd: unable to create nfsd_file_mark_slab\n");
|
|
|
|
goto out_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = list_lru_init(&nfsd_file_lru);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("nfsd: failed to init nfsd_file_lru: %d\n", ret);
|
|
|
|
goto out_err;
|
|
|
|
}
|
|
|
|
|
2023-09-11 17:44:11 +08:00
|
|
|
nfsd_file_shrinker = shrinker_alloc(0, "nfsd-filecache");
|
|
|
|
if (!nfsd_file_shrinker) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
pr_err("nfsd: failed to allocate nfsd_file_shrinker\n");
|
2019-08-19 02:18:48 +08:00
|
|
|
goto out_lru;
|
|
|
|
}
|
|
|
|
|
2023-09-11 17:44:11 +08:00
|
|
|
nfsd_file_shrinker->count_objects = nfsd_file_lru_count;
|
|
|
|
nfsd_file_shrinker->scan_objects = nfsd_file_lru_scan;
|
|
|
|
nfsd_file_shrinker->seeks = 1;
|
|
|
|
|
|
|
|
shrinker_register(nfsd_file_shrinker);
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
ret = lease_register_notifier(&nfsd_file_lease_notifier);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("nfsd: unable to register lease notifier: %d\n", ret);
|
|
|
|
goto out_shrinker;
|
|
|
|
}
|
|
|
|
|
2022-04-22 20:03:15 +08:00
|
|
|
nfsd_file_fsnotify_group = fsnotify_alloc_group(&nfsd_file_fsnotify_ops,
|
2022-04-22 20:03:20 +08:00
|
|
|
FSNOTIFY_GROUP_NOFS);
|
2019-08-19 02:18:48 +08:00
|
|
|
if (IS_ERR(nfsd_file_fsnotify_group)) {
|
|
|
|
pr_err("nfsd: unable to create fsnotify group: %ld\n",
|
|
|
|
PTR_ERR(nfsd_file_fsnotify_group));
|
2020-11-25 16:39:33 +08:00
|
|
|
ret = PTR_ERR(nfsd_file_fsnotify_group);
|
2019-08-19 02:18:48 +08:00
|
|
|
nfsd_file_fsnotify_group = NULL;
|
|
|
|
goto out_notifier;
|
|
|
|
}
|
|
|
|
|
2020-01-07 02:18:05 +08:00
|
|
|
INIT_DELAYED_WORK(&nfsd_filecache_laundrette, nfsd_file_gc_worker);
|
2019-08-19 02:18:48 +08:00
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
out_notifier:
|
|
|
|
lease_unregister_notifier(&nfsd_file_lease_notifier);
|
|
|
|
out_shrinker:
|
2023-09-11 17:44:11 +08:00
|
|
|
shrinker_free(nfsd_file_shrinker);
|
2019-08-19 02:18:48 +08:00
|
|
|
out_lru:
|
|
|
|
list_lru_destroy(&nfsd_file_lru);
|
|
|
|
out_err:
|
|
|
|
kmem_cache_destroy(nfsd_file_slab);
|
|
|
|
nfsd_file_slab = NULL;
|
|
|
|
kmem_cache_destroy(nfsd_file_mark_slab);
|
|
|
|
nfsd_file_mark_slab = NULL;
|
2022-11-25 04:09:04 +08:00
|
|
|
rhltable_destroy(&nfsd_file_rhltable);
|
2019-08-19 02:18:48 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2022-12-11 19:19:33 +08:00
|
|
|
/**
|
|
|
|
* __nfsd_file_cache_purge: clean out the cache for shutdown
|
|
|
|
* @net: net-namespace to shut down the cache (may be NULL)
|
|
|
|
*
|
|
|
|
* Walk the nfsd_file cache and close out any that match @net. If @net is NULL,
|
2023-01-27 01:21:16 +08:00
|
|
|
* then close out everything. Called when an nfsd instance is being shut down,
|
|
|
|
* and when the exports table is flushed.
|
2022-12-11 19:19:33 +08:00
|
|
|
*/
|
2022-07-09 02:26:16 +08:00
|
|
|
static void
|
|
|
|
__nfsd_file_cache_purge(struct net *net)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-07-09 02:26:30 +08:00
|
|
|
struct rhashtable_iter iter;
|
|
|
|
struct nfsd_file *nf;
|
2019-08-19 02:18:48 +08:00
|
|
|
LIST_HEAD(dispose);
|
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
rhltable_walk_enter(&nfsd_file_rhltable, &iter);
|
2022-07-09 02:26:30 +08:00
|
|
|
do {
|
|
|
|
rhashtable_walk_start(&iter);
|
2019-09-03 01:02:55 +08:00
|
|
|
|
2022-07-09 02:26:30 +08:00
|
|
|
nf = rhashtable_walk_next(&iter);
|
|
|
|
while (!IS_ERR_OR_NULL(nf)) {
|
2023-01-21 03:52:14 +08:00
|
|
|
if (!net || nf->nf_net == net)
|
|
|
|
nfsd_file_cond_queue(nf, &dispose);
|
2022-07-09 02:26:30 +08:00
|
|
|
nf = rhashtable_walk_next(&iter);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
2022-07-09 02:26:30 +08:00
|
|
|
|
|
|
|
rhashtable_walk_stop(&iter);
|
|
|
|
} while (nf == ERR_PTR(-EAGAIN));
|
|
|
|
rhashtable_walk_exit(&iter);
|
|
|
|
|
|
|
|
nfsd_file_dispose_list(&dispose);
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
2020-01-07 02:18:05 +08:00
|
|
|
static struct nfsd_fcache_disposal *
|
2021-12-01 07:58:14 +08:00
|
|
|
nfsd_alloc_fcache_disposal(void)
|
2020-01-07 02:18:05 +08:00
|
|
|
{
|
|
|
|
struct nfsd_fcache_disposal *l;
|
|
|
|
|
|
|
|
l = kmalloc(sizeof(*l), GFP_KERNEL);
|
|
|
|
if (!l)
|
|
|
|
return NULL;
|
|
|
|
spin_lock_init(&l->lock);
|
|
|
|
INIT_LIST_HEAD(&l->freeme);
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_free_fcache_disposal(struct nfsd_fcache_disposal *l)
|
|
|
|
{
|
|
|
|
nfsd_file_dispose_list(&l->freeme);
|
2021-12-01 07:58:14 +08:00
|
|
|
kfree(l);
|
2020-01-07 02:18:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfsd_free_fcache_disposal_net(struct net *net)
|
|
|
|
{
|
2021-12-01 07:58:14 +08:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
struct nfsd_fcache_disposal *l = nn->fcache_disposal;
|
2020-01-07 02:18:05 +08:00
|
|
|
|
2021-12-01 07:58:14 +08:00
|
|
|
nfsd_free_fcache_disposal(l);
|
2020-01-07 02:18:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
nfsd_file_cache_start_net(struct net *net)
|
|
|
|
{
|
2021-12-01 07:58:14 +08:00
|
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
|
|
|
|
nn->fcache_disposal = nfsd_alloc_fcache_disposal();
|
|
|
|
return nn->fcache_disposal ? 0 : -ENOMEM;
|
2020-01-07 02:18:05 +08:00
|
|
|
}
|
|
|
|
|
2022-07-09 02:26:16 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_cache_purge - Remove all cache items associated with @net
|
|
|
|
* @net: target net namespace
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
nfsd_file_cache_purge(struct net *net)
|
|
|
|
{
|
|
|
|
lockdep_assert_held(&nfsd_mutex);
|
|
|
|
if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 1)
|
|
|
|
__nfsd_file_cache_purge(net);
|
|
|
|
}
|
|
|
|
|
2020-01-07 02:18:05 +08:00
|
|
|
void
|
|
|
|
nfsd_file_cache_shutdown_net(struct net *net)
|
|
|
|
{
|
|
|
|
nfsd_file_cache_purge(net);
|
|
|
|
nfsd_free_fcache_disposal_net(net);
|
|
|
|
}
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
void
|
|
|
|
nfsd_file_cache_shutdown(void)
|
|
|
|
{
|
2022-07-09 02:24:51 +08:00
|
|
|
int i;
|
|
|
|
|
2022-07-09 02:26:16 +08:00
|
|
|
lockdep_assert_held(&nfsd_mutex);
|
|
|
|
if (test_and_clear_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 0)
|
|
|
|
return;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
lease_unregister_notifier(&nfsd_file_lease_notifier);
|
2023-09-11 17:44:11 +08:00
|
|
|
shrinker_free(nfsd_file_shrinker);
|
2019-08-19 02:18:48 +08:00
|
|
|
/*
|
|
|
|
* make sure all callers of nfsd_file_lru_cb are done before
|
|
|
|
* calling nfsd_file_cache_purge
|
|
|
|
*/
|
|
|
|
cancel_delayed_work_sync(&nfsd_filecache_laundrette);
|
2022-07-09 02:26:16 +08:00
|
|
|
__nfsd_file_cache_purge(NULL);
|
2019-08-19 02:18:48 +08:00
|
|
|
list_lru_destroy(&nfsd_file_lru);
|
|
|
|
rcu_barrier();
|
|
|
|
fsnotify_put_group(nfsd_file_fsnotify_group);
|
|
|
|
nfsd_file_fsnotify_group = NULL;
|
|
|
|
kmem_cache_destroy(nfsd_file_slab);
|
|
|
|
nfsd_file_slab = NULL;
|
|
|
|
fsnotify_wait_marks_destroyed();
|
|
|
|
kmem_cache_destroy(nfsd_file_mark_slab);
|
|
|
|
nfsd_file_mark_slab = NULL;
|
2022-11-25 04:09:04 +08:00
|
|
|
rhltable_destroy(&nfsd_file_rhltable);
|
2022-07-09 02:24:51 +08:00
|
|
|
|
|
|
|
for_each_possible_cpu(i) {
|
|
|
|
per_cpu(nfsd_file_cache_hits, i) = 0;
|
|
|
|
per_cpu(nfsd_file_acquisitions, i) = 0;
|
|
|
|
per_cpu(nfsd_file_releases, i) = 0;
|
|
|
|
per_cpu(nfsd_file_total_age, i) = 0;
|
|
|
|
per_cpu(nfsd_file_evictions, i) = 0;
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
static struct nfsd_file *
|
|
|
|
nfsd_file_lookup_locked(const struct net *net, const struct cred *cred,
|
|
|
|
struct inode *inode, unsigned char need,
|
|
|
|
bool want_gc)
|
|
|
|
{
|
|
|
|
struct rhlist_head *tmp, *list;
|
|
|
|
struct nfsd_file *nf;
|
|
|
|
|
|
|
|
list = rhltable_lookup(&nfsd_file_rhltable, &inode,
|
|
|
|
nfsd_file_rhash_params);
|
|
|
|
rhl_for_each_entry_rcu(nf, tmp, list, nf_rlist) {
|
|
|
|
if (nf->nf_may != need)
|
|
|
|
continue;
|
|
|
|
if (nf->nf_net != net)
|
|
|
|
continue;
|
|
|
|
if (!nfsd_match_cred(nf->nf_cred, cred))
|
|
|
|
continue;
|
|
|
|
if (test_bit(NFSD_FILE_GC, &nf->nf_flags) != want_gc)
|
|
|
|
continue;
|
|
|
|
if (test_bit(NFSD_FILE_HASHED, &nf->nf_flags) == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!nfsd_file_get(nf))
|
|
|
|
continue;
|
|
|
|
return nf;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
/**
|
2022-07-09 02:26:30 +08:00
|
|
|
* nfsd_file_is_cached - are there any cached open files for this inode?
|
|
|
|
* @inode: inode to check
|
|
|
|
*
|
|
|
|
* The lookup matches inodes in all net namespaces and is atomic wrt
|
|
|
|
* nfsd_file_acquire().
|
2019-08-19 02:18:48 +08:00
|
|
|
*
|
2022-07-09 02:26:30 +08:00
|
|
|
* Return values:
|
|
|
|
* %true: filecache contains at least one file matching this inode
|
|
|
|
* %false: filecache contains no files matching this inode
|
2019-08-19 02:18:48 +08:00
|
|
|
*/
|
|
|
|
bool
|
|
|
|
nfsd_file_is_cached(struct inode *inode)
|
|
|
|
{
|
2022-11-25 04:09:04 +08:00
|
|
|
struct rhlist_head *tmp, *list;
|
|
|
|
struct nfsd_file *nf;
|
2022-07-09 02:26:30 +08:00
|
|
|
bool ret = false;
|
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
list = rhltable_lookup(&nfsd_file_rhltable, &inode,
|
|
|
|
nfsd_file_rhash_params);
|
|
|
|
rhl_for_each_entry_rcu(nf, tmp, list, nf_rlist)
|
|
|
|
if (test_bit(NFSD_FILE_GC, &nf->nf_flags)) {
|
|
|
|
ret = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
2022-07-09 02:25:37 +08:00
|
|
|
trace_nfsd_file_is_cached(inode, (int)ret);
|
2019-08-19 02:18:48 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-30 22:30:54 +08:00
|
|
|
static __be32
|
2022-07-09 02:26:43 +08:00
|
|
|
nfsd_file_do_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
|
2023-01-06 03:55:56 +08:00
|
|
|
unsigned int may_flags, struct file *file,
|
|
|
|
struct nfsd_file **pnf, bool want_gc)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-11-25 04:09:04 +08:00
|
|
|
unsigned char need = may_flags & NFSD_FILE_MAY_MASK;
|
|
|
|
struct net *net = SVC_NET(rqstp);
|
|
|
|
struct nfsd_file *new, *nf;
|
2023-09-12 02:30:27 +08:00
|
|
|
bool stale_retry = true;
|
2022-10-05 03:41:10 +08:00
|
|
|
bool open_retry = true;
|
2022-11-25 04:09:04 +08:00
|
|
|
struct inode *inode;
|
2022-07-09 02:26:30 +08:00
|
|
|
__be32 status;
|
2022-10-05 03:41:10 +08:00
|
|
|
int ret;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2023-09-12 02:30:27 +08:00
|
|
|
retry:
|
2019-08-19 02:18:48 +08:00
|
|
|
status = fh_verify(rqstp, fhp, S_IFREG,
|
|
|
|
may_flags|NFSD_MAY_OWNER_OVERRIDE);
|
|
|
|
if (status != nfs_ok)
|
|
|
|
return status;
|
2022-11-25 04:09:04 +08:00
|
|
|
inode = d_inode(fhp->fh_dentry);
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-10-05 03:41:10 +08:00
|
|
|
rcu_read_lock();
|
2023-09-12 02:30:27 +08:00
|
|
|
nf = nfsd_file_lookup_locked(net, current_cred(), inode, need, want_gc);
|
2022-10-05 03:41:10 +08:00
|
|
|
rcu_read_unlock();
|
2022-12-11 19:19:33 +08:00
|
|
|
|
|
|
|
if (nf) {
|
2023-01-05 20:15:12 +08:00
|
|
|
/*
|
|
|
|
* If the nf is on the LRU then it holds an extra reference
|
|
|
|
* that must be put if it's removed. It had better not be
|
|
|
|
* the last one however, since we should hold another.
|
|
|
|
*/
|
2022-12-11 19:19:33 +08:00
|
|
|
if (nfsd_file_lru_remove(nf))
|
|
|
|
WARN_ON_ONCE(refcount_dec_and_test(&nf->nf_ref));
|
2019-08-19 02:18:48 +08:00
|
|
|
goto wait_for_construction;
|
2022-12-11 19:19:33 +08:00
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
new = nfsd_file_alloc(net, inode, need, want_gc);
|
|
|
|
if (!new) {
|
2022-07-09 02:25:37 +08:00
|
|
|
status = nfserr_jukebox;
|
2023-01-05 20:15:11 +08:00
|
|
|
goto out;
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
spin_lock(&inode->i_lock);
|
2023-09-12 02:30:27 +08:00
|
|
|
nf = nfsd_file_lookup_locked(net, current_cred(), inode, need, want_gc);
|
2022-11-25 04:09:04 +08:00
|
|
|
if (unlikely(nf)) {
|
|
|
|
spin_unlock(&inode->i_lock);
|
|
|
|
rcu_read_unlock();
|
|
|
|
nfsd_file_slab_free(&new->nf_rcu);
|
|
|
|
goto wait_for_construction;
|
|
|
|
}
|
|
|
|
nf = new;
|
|
|
|
ret = rhltable_insert(&nfsd_file_rhltable, &nf->nf_rlist,
|
|
|
|
nfsd_file_rhash_params);
|
|
|
|
spin_unlock(&inode->i_lock);
|
|
|
|
rcu_read_unlock();
|
2022-10-05 03:41:10 +08:00
|
|
|
if (likely(ret == 0))
|
2019-08-19 02:18:48 +08:00
|
|
|
goto open_file;
|
2022-10-05 03:41:10 +08:00
|
|
|
|
|
|
|
if (ret == -EEXIST)
|
|
|
|
goto retry;
|
2022-11-25 04:09:04 +08:00
|
|
|
trace_nfsd_file_insert_err(rqstp, inode, may_flags, ret);
|
2022-10-05 03:41:10 +08:00
|
|
|
status = nfserr_jukebox;
|
2023-01-05 20:15:11 +08:00
|
|
|
goto construction_err;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
|
|
|
wait_for_construction:
|
|
|
|
wait_on_bit(&nf->nf_flags, NFSD_FILE_PENDING, TASK_UNINTERRUPTIBLE);
|
|
|
|
|
|
|
|
/* Did construction of this file fail? */
|
|
|
|
if (!test_bit(NFSD_FILE_HASHED, &nf->nf_flags)) {
|
2022-11-25 04:09:04 +08:00
|
|
|
trace_nfsd_file_cons_err(rqstp, inode, may_flags, nf);
|
2022-10-05 03:41:10 +08:00
|
|
|
if (!open_retry) {
|
2020-01-07 02:18:03 +08:00
|
|
|
status = nfserr_jukebox;
|
2023-01-05 20:15:11 +08:00
|
|
|
goto construction_err;
|
2020-01-07 02:18:03 +08:00
|
|
|
}
|
2022-10-05 03:41:10 +08:00
|
|
|
open_retry = false;
|
2023-09-12 02:30:27 +08:00
|
|
|
fh_put(fhp);
|
2019-08-19 02:18:48 +08:00
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
this_cpu_inc(nfsd_file_cache_hits);
|
|
|
|
|
2022-07-30 05:01:07 +08:00
|
|
|
status = nfserrno(nfsd_open_break_lease(file_inode(nf->nf_file), may_flags));
|
2023-01-05 20:15:11 +08:00
|
|
|
if (status != nfs_ok) {
|
|
|
|
nfsd_file_put(nf);
|
|
|
|
nf = NULL;
|
|
|
|
}
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
out:
|
|
|
|
if (status == nfs_ok) {
|
2023-01-06 03:55:56 +08:00
|
|
|
this_cpu_inc(nfsd_file_acquisitions);
|
2023-02-08 01:02:46 +08:00
|
|
|
nfsd_file_check_write_error(nf);
|
2019-08-19 02:18:48 +08:00
|
|
|
*pnf = nf;
|
|
|
|
}
|
2022-11-25 04:09:04 +08:00
|
|
|
trace_nfsd_file_acquire(rqstp, inode, may_flags, nf, status);
|
2019-08-19 02:18:48 +08:00
|
|
|
return status;
|
2022-07-09 02:25:37 +08:00
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
open_file:
|
2022-07-09 02:26:49 +08:00
|
|
|
trace_nfsd_file_alloc(nf);
|
2022-11-25 04:09:04 +08:00
|
|
|
nf->nf_mark = nfsd_file_mark_find_or_create(nf, inode);
|
2022-03-30 22:30:54 +08:00
|
|
|
if (nf->nf_mark) {
|
2023-01-06 03:55:56 +08:00
|
|
|
if (file) {
|
|
|
|
get_file(file);
|
|
|
|
nf->nf_file = file;
|
|
|
|
status = nfs_ok;
|
|
|
|
trace_nfsd_file_opened(nf, status);
|
|
|
|
} else {
|
2023-09-12 02:30:27 +08:00
|
|
|
ret = nfsd_open_verified(rqstp, fhp, may_flags,
|
|
|
|
&nf->nf_file);
|
|
|
|
if (ret == -EOPENSTALE && stale_retry) {
|
|
|
|
stale_retry = false;
|
|
|
|
nfsd_file_unhash(nf);
|
|
|
|
clear_and_wake_up_bit(NFSD_FILE_PENDING,
|
|
|
|
&nf->nf_flags);
|
|
|
|
if (refcount_dec_and_test(&nf->nf_ref))
|
|
|
|
nfsd_file_free(nf);
|
|
|
|
nf = NULL;
|
|
|
|
fh_put(fhp);
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
status = nfserrno(ret);
|
2022-03-28 04:42:20 +08:00
|
|
|
trace_nfsd_file_open(nf, status);
|
2023-01-06 03:55:56 +08:00
|
|
|
}
|
2022-03-30 22:30:54 +08:00
|
|
|
} else
|
2019-08-19 02:18:48 +08:00
|
|
|
status = nfserr_jukebox;
|
|
|
|
/*
|
|
|
|
* If construction failed, or we raced with a call to unlink()
|
|
|
|
* then unhash.
|
|
|
|
*/
|
2022-11-25 04:09:04 +08:00
|
|
|
if (status != nfs_ok || inode->i_nlink == 0)
|
2022-12-11 19:19:33 +08:00
|
|
|
nfsd_file_unhash(nf);
|
2023-01-05 20:15:09 +08:00
|
|
|
clear_and_wake_up_bit(NFSD_FILE_PENDING, &nf->nf_flags);
|
2023-01-05 20:15:11 +08:00
|
|
|
if (status == nfs_ok)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
construction_err:
|
|
|
|
if (refcount_dec_and_test(&nf->nf_ref))
|
|
|
|
nfsd_file_free(nf);
|
|
|
|
nf = NULL;
|
2019-08-19 02:18:48 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2022-10-28 22:46:51 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_acquire_gc - Get a struct nfsd_file with an open file
|
|
|
|
* @rqstp: the RPC transaction being executed
|
|
|
|
* @fhp: the NFS filehandle of the file to be opened
|
|
|
|
* @may_flags: NFSD_MAY_ settings for the file
|
|
|
|
* @pnf: OUT: new or found "struct nfsd_file" object
|
|
|
|
*
|
|
|
|
* The nfsd_file object returned by this API is reference-counted
|
|
|
|
* and garbage-collected. The object is retained for a few
|
|
|
|
* seconds after the final nfsd_file_put() in case the caller
|
|
|
|
* wants to re-use it.
|
|
|
|
*
|
2022-11-25 04:09:04 +08:00
|
|
|
* Return values:
|
|
|
|
* %nfs_ok - @pnf points to an nfsd_file with its reference
|
|
|
|
* count boosted.
|
|
|
|
*
|
|
|
|
* On error, an nfsstat value in network byte order is returned.
|
2022-10-28 22:46:51 +08:00
|
|
|
*/
|
|
|
|
__be32
|
|
|
|
nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp,
|
|
|
|
unsigned int may_flags, struct nfsd_file **pnf)
|
|
|
|
{
|
2023-01-06 03:55:56 +08:00
|
|
|
return nfsd_file_do_acquire(rqstp, fhp, may_flags, NULL, pnf, true);
|
2022-10-28 22:46:51 +08:00
|
|
|
}
|
|
|
|
|
2022-03-30 22:30:54 +08:00
|
|
|
/**
|
|
|
|
* nfsd_file_acquire - Get a struct nfsd_file with an open file
|
|
|
|
* @rqstp: the RPC transaction being executed
|
|
|
|
* @fhp: the NFS filehandle of the file to be opened
|
|
|
|
* @may_flags: NFSD_MAY_ settings for the file
|
|
|
|
* @pnf: OUT: new or found "struct nfsd_file" object
|
|
|
|
*
|
2022-10-28 22:46:51 +08:00
|
|
|
* The nfsd_file_object returned by this API is reference-counted
|
|
|
|
* but not garbage-collected. The object is unhashed after the
|
|
|
|
* final nfsd_file_put().
|
|
|
|
*
|
2022-11-25 04:09:04 +08:00
|
|
|
* Return values:
|
|
|
|
* %nfs_ok - @pnf points to an nfsd_file with its reference
|
|
|
|
* count boosted.
|
|
|
|
*
|
|
|
|
* On error, an nfsstat value in network byte order is returned.
|
2022-03-30 22:30:54 +08:00
|
|
|
*/
|
|
|
|
__be32
|
|
|
|
nfsd_file_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
|
|
|
|
unsigned int may_flags, struct nfsd_file **pnf)
|
|
|
|
{
|
2023-01-06 03:55:56 +08:00
|
|
|
return nfsd_file_do_acquire(rqstp, fhp, may_flags, NULL, pnf, false);
|
2022-03-30 22:30:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-01-06 03:55:56 +08:00
|
|
|
* nfsd_file_acquire_opened - Get a struct nfsd_file using existing open file
|
2022-03-30 22:30:54 +08:00
|
|
|
* @rqstp: the RPC transaction being executed
|
|
|
|
* @fhp: the NFS filehandle of the file just created
|
|
|
|
* @may_flags: NFSD_MAY_ settings for the file
|
2023-01-06 03:55:56 +08:00
|
|
|
* @file: cached, already-open file (may be NULL)
|
2022-03-30 22:30:54 +08:00
|
|
|
* @pnf: OUT: new or found "struct nfsd_file" object
|
|
|
|
*
|
2023-01-06 03:55:56 +08:00
|
|
|
* Acquire a nfsd_file object that is not GC'ed. If one doesn't already exist,
|
|
|
|
* and @file is non-NULL, use it to instantiate a new nfsd_file instead of
|
|
|
|
* opening a new one.
|
2022-10-28 22:46:51 +08:00
|
|
|
*
|
2022-11-25 04:09:04 +08:00
|
|
|
* Return values:
|
|
|
|
* %nfs_ok - @pnf points to an nfsd_file with its reference
|
|
|
|
* count boosted.
|
|
|
|
*
|
|
|
|
* On error, an nfsstat value in network byte order is returned.
|
2022-03-30 22:30:54 +08:00
|
|
|
*/
|
|
|
|
__be32
|
2023-01-06 03:55:56 +08:00
|
|
|
nfsd_file_acquire_opened(struct svc_rqst *rqstp, struct svc_fh *fhp,
|
|
|
|
unsigned int may_flags, struct file *file,
|
|
|
|
struct nfsd_file **pnf)
|
2022-03-30 22:30:54 +08:00
|
|
|
{
|
2023-01-06 03:55:56 +08:00
|
|
|
return nfsd_file_do_acquire(rqstp, fhp, may_flags, file, pnf, false);
|
2022-03-30 22:30:54 +08:00
|
|
|
}
|
|
|
|
|
2019-08-19 02:18:48 +08:00
|
|
|
/*
|
|
|
|
* Note that fields may be added, removed or reordered in the future. Programs
|
|
|
|
* scraping this file for info should test the labels to ensure they're
|
|
|
|
* getting the correct field.
|
|
|
|
*/
|
2022-09-23 00:31:56 +08:00
|
|
|
int nfsd_file_cache_stats_show(struct seq_file *m, void *v)
|
2019-08-19 02:18:48 +08:00
|
|
|
{
|
2022-11-03 02:44:47 +08:00
|
|
|
unsigned long releases = 0, evictions = 0;
|
2022-07-09 02:24:45 +08:00
|
|
|
unsigned long hits = 0, acquisitions = 0;
|
2022-07-09 02:26:30 +08:00
|
|
|
unsigned int i, count = 0, buckets = 0;
|
2022-07-09 02:24:12 +08:00
|
|
|
unsigned long lru = 0, total_age = 0;
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-07-09 02:26:30 +08:00
|
|
|
/* Serialize with server shutdown */
|
2019-08-19 02:18:48 +08:00
|
|
|
mutex_lock(&nfsd_mutex);
|
2022-07-09 02:26:16 +08:00
|
|
|
if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 1) {
|
2022-07-09 02:26:30 +08:00
|
|
|
struct bucket_table *tbl;
|
|
|
|
struct rhashtable *ht;
|
|
|
|
|
2022-07-09 02:23:52 +08:00
|
|
|
lru = list_lru_count(&nfsd_file_lru);
|
2022-07-09 02:26:30 +08:00
|
|
|
|
|
|
|
rcu_read_lock();
|
2022-11-25 04:09:04 +08:00
|
|
|
ht = &nfsd_file_rhltable.ht;
|
2022-07-09 02:26:30 +08:00
|
|
|
count = atomic_read(&ht->nelems);
|
|
|
|
tbl = rht_dereference_rcu(ht->tbl, ht);
|
|
|
|
buckets = tbl->size;
|
|
|
|
rcu_read_unlock();
|
2019-08-19 02:18:48 +08:00
|
|
|
}
|
|
|
|
mutex_unlock(&nfsd_mutex);
|
|
|
|
|
2022-07-09 02:23:59 +08:00
|
|
|
for_each_possible_cpu(i) {
|
2019-08-19 02:18:48 +08:00
|
|
|
hits += per_cpu(nfsd_file_cache_hits, i);
|
2022-07-09 02:23:59 +08:00
|
|
|
acquisitions += per_cpu(nfsd_file_acquisitions, i);
|
2022-07-09 02:24:05 +08:00
|
|
|
releases += per_cpu(nfsd_file_releases, i);
|
2022-07-09 02:24:12 +08:00
|
|
|
total_age += per_cpu(nfsd_file_total_age, i);
|
2022-07-09 02:24:38 +08:00
|
|
|
evictions += per_cpu(nfsd_file_evictions, i);
|
2022-07-09 02:23:59 +08:00
|
|
|
}
|
2019-08-19 02:18:48 +08:00
|
|
|
|
2022-11-25 04:09:04 +08:00
|
|
|
seq_printf(m, "total inodes: %u\n", count);
|
2022-07-09 02:26:30 +08:00
|
|
|
seq_printf(m, "hash buckets: %u\n", buckets);
|
2022-07-09 02:23:52 +08:00
|
|
|
seq_printf(m, "lru entries: %lu\n", lru);
|
2019-08-19 02:18:48 +08:00
|
|
|
seq_printf(m, "cache hits: %lu\n", hits);
|
2022-07-09 02:23:59 +08:00
|
|
|
seq_printf(m, "acquisitions: %lu\n", acquisitions);
|
2022-07-09 02:24:05 +08:00
|
|
|
seq_printf(m, "releases: %lu\n", releases);
|
2022-07-09 02:24:38 +08:00
|
|
|
seq_printf(m, "evictions: %lu\n", evictions);
|
2022-07-09 02:24:12 +08:00
|
|
|
if (releases)
|
|
|
|
seq_printf(m, "mean age (ms): %ld\n", total_age / releases);
|
|
|
|
else
|
|
|
|
seq_printf(m, "mean age (ms): -\n");
|
2019-08-19 02:18:48 +08:00
|
|
|
return 0;
|
|
|
|
}
|