/* * Copyright 2019, 2020 Amazon.com, Inc. or its affiliates. All rights reserved. * * User extended attribute client side cache functions. * * Author: Frank van der Linden <fllinden@amazon.com>
*/ #include <linux/errno.h> #include <linux/nfs_fs.h> #include <linux/hashtable.h> #include <linux/refcount.h> #include <uapi/linux/xattr.h>
#include"nfs4_fs.h" #include"internal.h"
/* * User extended attributes client side caching is implemented by having * a cache structure attached to NFS inodes. This structure is allocated * when needed, and freed when the cache is zapped. * * The cache structure contains as hash table of entries, and a pointer * to a special-cased entry for the listxattr cache. * * Accessing and allocating / freeing the caches is done via reference * counting. The cache entries use a similar refcounting scheme. * * This makes freeing a cache, both from the shrinker and from the * zap cache path, easy. It also means that, in current use cases, * the large majority of inodes will not waste any memory, as they * will never have any user extended attributes assigned to them. * * Attribute entries are hashed in to a simple hash table. They are * also part of an LRU. * * There are three shrinkers. * * Two shrinkers deal with the cache entries themselves: one for * large entries (> PAGE_SIZE), and one for smaller entries. The * shrinker for the larger entries works more aggressively than * those for the smaller entries. * * The other shrinker frees the cache structures themselves.
*/
/* * 64 buckets is a good default. There is likely no reasonable * workload that uses more than even 64 user extended attributes. * You can certainly add a lot more - but you get what you ask for * in those circumstances.
*/ #define NFS4_XATTR_HASH_SIZE 64
for (i = 0; i < NFS4_XATTR_HASH_SIZE; i++) {
INIT_HLIST_HEAD(&cache->buckets[i].hlist);
spin_lock_init(&cache->buckets[i].lock);
cache->buckets[i].cache = cache;
cache->buckets[i].draining = false;
}
}
/* * Locking order: * 1. inode i_lock or bucket lock * 2. list_lru lock (taken by list_lru_* functions)
*/
/* * Wrapper functions to add a cache entry to the right LRU.
*/ staticbool
nfs4_xattr_entry_lru_add(struct nfs4_xattr_entry *entry)
{ struct list_lru *lru;
/* * This function allocates cache entries. They are the normal * extended attribute name/value pairs, but may also be a listxattr * cache. Those allocations use the same entry so that they can be * treated as one by the memory shrinker. * * xattr cache entries are allocated together with names. If the * value fits in to one page with the entry structure and the name, * it will also be part of the same allocation (kmalloc). This is * expected to be the vast majority of cases. Larger allocations * have a value pointer that is allocated separately by kvmalloc. * * Parameters: * * @name: Name of the extended attribute. NULL for listxattr cache * entry. * @value: Value of attribute, or listxattr cache. NULL if the * value is to be copied from pages instead. * @pages: Pages to copy the value from, if not NULL. Passed in to * make it easier to copy the value after an RPC, even if * the value will not be passed up to application (e.g. * for a 'query' getxattr with NULL buffer). * @len: Length of the value. Can be 0 for zero-length attributes. * @value and @pages will be NULL if @len is 0.
*/ staticstruct nfs4_xattr_entry *
nfs4_xattr_alloc_entry(constchar *name, constvoid *value, struct page **pages, size_t len)
{ struct nfs4_xattr_entry *entry; void *valp; char *namep;
size_t alloclen, slen; char *buf;
uint32_t flags;
/* * Set the listxattr cache, which is a special-cased cache entry. * The special value ERR_PTR(-ESTALE) is used to indicate that * the cache is being drained - this prevents a new listxattr * cache from being added to what is now a stale cache.
*/ staticint
nfs4_xattr_set_listcache(struct nfs4_xattr_cache *cache, struct nfs4_xattr_entry *new)
{ struct nfs4_xattr_entry *old; int ret = 1;
spin_lock(&cache->listxattr_lock);
old = cache->listxattr;
if (old == ERR_PTR(-ESTALE)) {
ret = 0; goto out;
}
cache->listxattr = new; if (new != NULL && new != ERR_PTR(-ESTALE))
nfs4_xattr_entry_lru_add(new);
/* * Unlink a cache from its parent inode, clearing out an invalid * cache. Must be called with i_lock held.
*/ staticstruct nfs4_xattr_cache *
nfs4_xattr_cache_unlink(struct inode *inode)
{ struct nfs_inode *nfsi; struct nfs4_xattr_cache *oldcache;
/* * Discard a cache. Called by get_cache() if there was an old, * invalid cache. Can also be called from a shrinker callback. * * The cache is dead, it has already been unlinked from its inode, * and no longer appears on the cache LRU list. * * Mark all buckets as draining, so that no new entries are added. This * could still happen in the unlikely, but possible case that another * thread had grabbed a reference before it was unlinked from the inode, * and is still holding it for an add operation. * * Remove all entries from the LRU lists, so that there is no longer * any way to 'find' this cache. Then, remove the entries from the hash * table. * * At that point, the cache will remain empty and can be freed when the final * reference drops, which is very likely the kref_put at the end of * this function, or the one called immediately afterwards in the * shrinker callback.
*/ staticvoid
nfs4_xattr_discard_cache(struct nfs4_xattr_cache *cache)
{ unsignedint i; struct nfs4_xattr_entry *entry; struct nfs4_xattr_bucket *bucket; struct hlist_node *n;
/* * Get a referenced copy of the cache structure. Avoid doing allocs * while holding i_lock. Which means that we do some optimistic allocation, * and might have to free the result in rare cases. * * This function only checks the NFS_INO_INVALID_XATTR cache validity bit * and acts accordingly, replacing the cache when needed. For the read case * (!add), this means that the caller must make sure that the cache * is valid before caling this function. getxattr and listxattr call * revalidate_inode to do this. The attribute cache timeout (for the * non-delegated case) is expected to be dealt with in the revalidate * call.
*/
cache = nfs4_xattr_alloc_cache(); if (cache == NULL) goto out;
spin_lock(&inode->i_lock); if (nfsi->cache_validity & NFS_INO_INVALID_XATTR) { /* * The cache was invalidated again. Give up, * since what we want to enter is now likely * outdated anyway.
*/
spin_unlock(&inode->i_lock);
kref_put(&cache->ref, nfs4_xattr_free_cache_cb);
cache = NULL; goto out;
}
/* * Check if someone beat us to it.
*/ if (nfsi->xattr_cache != NULL) {
newcache = nfsi->xattr_cache;
kref_get(&newcache->ref);
} else {
kref_get(&cache->ref);
nfsi->xattr_cache = cache;
cache->inode = inode;
list_lru_add_obj(&nfs4_xattr_cache_lru, &cache->lru);
}
spin_unlock(&inode->i_lock);
/* * If there was a race, throw away the cache we just * allocated, and use the new one allocated by someone * else.
*/ if (newcache != NULL) {
kref_put(&cache->ref, nfs4_xattr_free_cache_cb);
cache = newcache;
}
}
out: /* * Discard the now orphaned old cache.
*/ if (oldcache != NULL)
nfs4_xattr_discard_cache(oldcache);
/* * Remove an xattr from the cache. * * This also invalidates the xattr list cache.
*/ void nfs4_xattr_cache_remove(struct inode *inode, constchar *name)
{ struct nfs4_xattr_cache *cache;
dprintk("%s: remove '%s'\n", __func__, name);
cache = nfs4_xattr_get_cache(inode, 0); if (cache == NULL) return;
/* * This is just there to be able to get to bucket->cache, * which is obviously the same for all buckets, so just * use bucket 0.
*/
entry->bucket = &cache->buckets[0];
if (!nfs4_xattr_set_listcache(cache, entry))
kref_put(&entry->ref, nfs4_xattr_free_entry_cb);
if (oldcache)
nfs4_xattr_discard_cache(oldcache);
}
/* * The entry LRU is shrunk more aggressively than the cache LRU, * by settings @seeks to 1. * * Cache structures are freed only when they've become empty, after * pruning all but one entry.
*/
if (atomic_long_read(&cache->nent) > 1) return LRU_SKIP;
/* * If a cache structure is on the LRU list, we know that * its inode is valid. Try to lock it to break the link. * Since we're inverting the lock order here, only try.
*/
inode = cache->inode;
if (!spin_trylock(&inode->i_lock)) return LRU_SKIP;
/* * Unhook the entry from its parent (either a cache bucket * or a cache structure if it's a listxattr buf), so that * it's no longer found. Then add it to the isolate list, * to be freed later. * * In both cases, we're reverting lock order, so use * trylock and skip the entry if we can't get the lock.
*/ if (entry->xattr_name != NULL) { /* Regular cache entry */ if (!spin_trylock(&bucket->lock)) return LRU_SKIP;
while (!list_empty(&dispose)) {
entry = list_first_entry(&dispose, struct nfs4_xattr_entry,
dispose);
list_del_init(&entry->dispose);
/* * Drop two references: the one that we just grabbed * in entry_lru_isolate, and the one that was set * when the entry was first allocated.
*/
kref_put(&entry->ref, nfs4_xattr_free_entry_cb);
kref_put(&entry->ref, nfs4_xattr_free_entry_cb);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.