Extend RCU-walk support to NFS.
Subsystems (3)
cover Extend RCU-walk support to NFS.
Linux 2.6.38 introduced RCU-walk (https://lwn.net/Articles/419811/)
to improve path-name lookup by avoiding locking in many cases, particularly when everything is in cache.
NFS doesn't benefit from this, returning -ECHILD whenever RCU-walk is detected.
For workloads that can be served entirely from the cache and are metadata-heavy (e.g. 'make') this can be a performance problem particularly if multiple threads are using multiple cores.
The following series extends RCU-walk into NFS allowing a synthetic stat-heavy workload to avoid locking and refcounting most of the time. It provides about 20% performance improvement with 4 threads on 2 core, and will probably have a much more significant effect with 4 or 8 cores (though I haven't tested yet).
The key to providing RCU-walk support is to avoid sleeping and, where possible, avoid locking or refcounting.
So in nfs_permission and nfs{,4}_lookup_revalidate we should return -ECHILD only if a memory allocation or RPC request are required. In other cases we can simply complete as normal.
There are two data-structures which need to be searched fornfs_permission: the credential cache (in sunrpc) and the nfs access_cache.
The credential cache is already rcu-aware so it is easy to return a credential without locking if LOOKUP_RCU is set.
The access_cache uses an rbtree which is not rcu-aware and cannot easily be made so. There are two approaches we can take here. 1/ take the required locks and perform the access check with locks
held (as nfs_access_get_cached already does). This is easy but requires taking locks which doesn't scale as well as not taking them.
2/ Use rcu to free cache entries and use a simpler rcu-safe search
mechanism when performing an RCU-walk. This search doesn't have to be perfect. If it finds the right value most of the time that will still help. When it doesn't find the required value we fall back on taking the lock.
The early patches assume the first approach. The final patch attempts the second. The rcu-safe search method used is simple to examine the final (most recent) entry on the lru.
This final patch provides about 2% performance increase over not having the patch in my 4-thread tests on 2-core CPU.
It is the most "RFC" part of the series. The earlier patches I am comfortable with (though review is always appreciated of course).
---
NeilBrown (8):
NFS: nfs4_lookup_revalidate: only evaluate parent if it will be used.
NFS: prepare for RCU-walk support but pushing tests later in code. sunrpc/auth: allow lockless (rcu) lookup of credential cache. sunrpc/auth: add 'rcu_walk' arg to rpc_lookup_cred.
NFS: teach nfs_do_access to understand MAY_NOT_BLOCK
NFS: teach nfs_neg_need_reval to understand LOOKUP_RCU
NFS: teach nfs_lookup_verify_inode to handle LOOKUP_RCU
NFS: allow lockless access to access_cache
fs/nfs/dir.c | 134 +++++++++++++++++++++++++++++++++---------- fs/nfs/inode.c | 11 +++- fs/nfs/nfs4proc.c | 2 - fs/nfs/unlink.c | 4 + include/linux/nfs_fs.h | 2 + include/linux/sunrpc/auth.h | 3 + net/sunrpc/auth.c | 17 +++++ net/sunrpc/auth_generic.c | 5 +- net/sunrpc/auth_null.c | 2 + 9 files changed, 139 insertions(+), 41 deletions(-)
Signature · 1 line
Signature
PATCH 1/8 NFS: nfs4_lookup_revalidate: only evaluate parent if it will be used.
nfs4_lookup_revalidate only uses 'parent' to get 'dir', and only uses 'dir' if 'inode == NULL'.
So we don't need to find out what 'parent' or 'dir' is until we know that 'inode' is NULL.
By moving 'dget_parent' inside the 'if', we can reduce the number of call sites for 'dput(parent)'.
--- fs/nfs/dir.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-)
fs/nfs/dir.c +8 -9
@@ -1496,9 +1496,7 @@ EXPORT_SYMBOL_GPL(nfs_atomic_open);static int nfs4_lookup_revalidate (struct dentry *dentry,unsigned int flags) { -struct dentry *parent =NULL ;struct inode *inode; -struct inode *dir;int ret =0 ;if (flags &LOOKUP_RCU )
@@ -1512,34 +1510,35 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags) goto no_open; inode = dentry->d_inode; - parent =dget_parent (dentry); - dir = parent->d_inode;/* We can't create new files in nfs_open_revalidate(), so we * optimize away revalidation of negative dentries. */ if (inode ==NULL ) { +struct dentry *parent; +struct inode *dir; + + parent =dget_parent (dentry); + dir = parent->d_inode;if (!nfs_neg_need_reval (dir, dentry, flags)) ret =1 ; +dput (parent); goto out; }/* NFS only supports OPEN on regular files */ if (!S_ISREG (inode->i_mode)) - goto no_open_dput; + goto no_open;/* We cannot do exclusive creation on a positive dentry */ if (flags &LOOKUP_EXCL ) - goto no_open_dput; + goto no_open;/* Let f_op->open() actually open (and revalidate) the file */ ret =1 ; out: -dput (parent);return ret; -no_open_dput: -dput (parent); no_open:return nfs_lookup_revalidate (dentry, flags); }
PATCH 2/8 NFS: prepare for RCU-walk support but pushing tests later in code.
In reply to NeilBrownnfs_lookup_revalidate, nfs4_lookup_revalidate, and nfs_permission all need to understand and handle RCU-walk for NFS to gain the benefits of RCU-walk for cached information.
Currently these functions all immediately return -ECHILD if the relevant flag (LOOKUP_RCU or MAY_NOT_BLOCK) is set.
This patch pushes those tests later in the code so that we only abort immediately before we enter rcu-unsafe code. As subsequent patches make that rcu-unsafe code rcu-safe, several of these new tests will disappear.
With this patch there are several paths through the code which will no longer return -ECHILD during an RCU-walk. However these are mostly error paths or other uninteresting cases.
A noteworthy change in nfs_lookup_revalidate is that we don't take (or put) the reference to ->d_parent when LOOKUP_RCU is set.
In nfs4_lookup_revalidate we simple avoid testing LOOKUP_RCU on the path that simply calls nfs_lookup_revalidate() as that function already performs the required test.
--- fs/nfs/dir.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-)
fs/nfs/dir.c +24 -12
@@ -1056,14 +1056,17 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)int error;if (flags &LOOKUP_RCU ) -return -ECHILD ; - - parent =dget_parent (dentry); + parent =rcu_dereference (dentry->d_parent); +else + parent =dget_parent (dentry); dir = parent->d_inode;nfs_inc_stats (dir,NFSIOS_DENTRYREVALIDATE ); inode = dentry->d_inode;if (!inode) { +if (flags &LOOKUP_RCU ) +return -ECHILD ; +if (nfs_neg_need_reval (dir, dentry, flags)) goto out_bad; goto out_valid_noent;
@@ -1078,6 +1081,9 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)if (NFS_PROTO (dir)->have_delegation (inode,FMODE_READ )) goto out_set_verifier; +if (flags &LOOKUP_RCU ) +return -ECHILD ; +/* Force a full look up iff the parent directory has changed */ if (!nfs_is_exclusive_create (dir, flags) &&nfs_check_verifier (dir, dentry)) {if (nfs_lookup_verify_inode (inode, flags))
@@ -1120,7 +1126,8 @@ out_set_verifier:/* Success: notify readdir to use READDIRPLUS */ nfs_advise_use_readdirplus (dir); out_valid_noent: -dput (parent); +if (!(flags &LOOKUP_RCU )) +dput (parent);dfprintk (LOOKUPCACHE ,"NFS: %s(%pd2) is valid\n" , __func__, dentry);return 1 ;
@@ -1147,7 +1154,8 @@ out_zap_parent:if (check_submounts_and_drop (dentry) !=0 ) goto out_valid; -dput (parent); +if (!(flags &LOOKUP_RCU )) +dput (parent);dfprintk (LOOKUPCACHE ,"NFS: %s(%pd2) is invalid\n" , __func__, dentry);return 0 ;
@@ -1155,7 +1163,8 @@ out_error:nfs_free_fattr (fattr);nfs_free_fhandle (fhandle);nfs4_label_free (label); -dput (parent); +if (!(flags &LOOKUP_RCU )) +dput (parent);dfprintk (LOOKUPCACHE ,"NFS: %s(%pd2) lookup returned error %d\n" , __func__, dentry, error);return error;
@@ -1499,9 +1508,6 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct inode *inode;int ret =0 ; -if (flags &LOOKUP_RCU ) -return -ECHILD ; -if (!(flags &LOOKUP_OPEN ) || (flags &LOOKUP_DIRECTORY )) goto no_open;if (d_mountpoint (dentry))
@@ -1518,6 +1524,9 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct dentry *parent;struct inode *dir; +if (flags &LOOKUP_RCU ) +return -ECHILD ; + parent =dget_parent (dentry); dir = parent->d_inode;if (!nfs_neg_need_reval (dir, dentry, flags))
@@ -2273,9 +2282,6 @@ int nfs_permission(struct inode *inode, int mask)struct rpc_cred *cred;int res =0 ; -if (mask &MAY_NOT_BLOCK ) -return -ECHILD ; -nfs_inc_stats (inode,NFSIOS_VFSACCESS );if ((mask & (MAY_READ |MAY_WRITE |MAY_EXEC )) ==0 )
@@ -2302,6 +2308,9 @@ force_lookup:if (!NFS_PROTO (inode)->access) goto out_notsup; +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; + cred =rpc_lookup_cred ();if (!IS_ERR (cred)) { res =nfs_do_access (inode, cred, mask);
@@ -2316,6 +2325,9 @@ out: inode->i_sb->s_id, inode->i_ino, mask, res);return res; out_notsup: +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; + res =nfs_revalidate_inode (NFS_SERVER (inode), inode);if (res ==0 ) res =generic_permission (inode, mask);
Al Viro
NAKRe: [PATCH 2/8] NFS: prepare for RCU-walk support but pushing tests later in code.
fs/nfs/dir.c static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags) · line 1063
Quoted 6 lines · NeilBrown
fs/nfs/dir.c +3
-1- parent =dget_parent (dentry); + parent =rcu_dereference (dentry->d_parent); + else + parent =dget_parent (dentry); dir = parent->d_inode;nfs_inc_stats (dir,NFSIOS_DENTRYREVALIDATE );
... and in RCU mode there's nothing to stop parent from
a) not being dentry->d_parent anymore b) having already become negative.
NAK.
Re: [PATCH 2/8] NFS: prepare for RCU-walk support but pushing tests later in code.
Quoted 8 lines · On Wed, 5 Mar 2014 05:34:41 +0000 Al Viro <[email protected]> wrote:
fs/nfs/dir.c static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags) · line 1063
Quoted 6 lines · NeilBrown
fs/nfs/dir.c +3
-1- parent =dget_parent (dentry); + parent =rcu_dereference (dentry->d_parent); + else + parent =dget_parent (dentry); dir = parent->d_inode;nfs_inc_stats (dir,NFSIOS_DENTRYREVALIDATE );
Hi Al,
thanks for the review.
Quoted 2 lines
... and in RCU mode there's nothing to stop parent from
a) not being dentry->d_parent anymore
Does that matter? Surely dentry->d_parent exists, is still a dentry, and was recently the parent to dentry (i.e. since the last grace period). If it has changed (which it must if it is no longer our parent), then the cache validity checks are bound to fail and we will fall back to refcnt-walk as we should.
Quoted 1 line
b) having already become negative.
I didn't think dentries ever became negative. When a file is deleted the old positive dentry is unlinked and a new negative dentry is created in it's place.
Or has that changed since last I looked?
If they can become negative, then I could
dir = ACCESS_ONCE(parent->d_inode);
if (!dir)
return -ECHILD;
Do you think that would be safe?
Thanks,
NeilBrown
Quoted 2 lines
NAK.
Re: [PATCH 2/8] NFS: prepare for RCU-walk support but pushing tests later in code.
Quoted 6 lines · NeilBrown
Quoted 1 line
b) having already become negative.
I didn't think dentries ever became negative. When a file is deleted the old positive dentry is unlinked and a new negative dentry is created in it's place.
Or has that changed since last I looked?
It has never been true. See what d_delete() is doing. If there was only one reference to dentry, it *does* become negative.
Quoted 6 lines
If they can become negative, then I could
dir = ACCESS_ONCE(parent->d_inode); if (!dir) return -ECHILD;Do you think that would be safe?
Depends on what you do with it afterwards...
Re: [PATCH 2/8] NFS: prepare for RCU-walk support but pushing tests later in code.
Quoted 10 lines · On Wed, 5 Mar 2014 06:49:46 +0000 Al Viro <[email protected]> wrote:
Quoted 6 lines · NeilBrown
Quoted 1 line
b) having already become negative.
I didn't think dentries ever became negative. When a file is deleted the old positive dentry is unlinked and a new negative dentry is created in it's place.
Or has that changed since last I looked?It has never been true. See what
d_delete()is doing. If there was only one reference to dentry, it *does* become negative.
Seems I missed that - thanks.
Quoted 9 lines
Quoted 7 lines
If they can become negative, then I could
dir = ACCESS_ONCE(parent->d_inode); if (!dir) return -ECHILD;Do you think that would be safe?
Depends on what you do with it afterwards...
It deferences i_sb, reads a couple of integer fields (never writes) and
rcu_dereference(NFS_I(inode)->delegation);
which will currently always be NULL on a directory and if we ever did have directory delegation, should clearly be safe to reference under rcu.
Here is my current version of that patch.
Thanks again,
NeilBrown
From 8b85de80ccc7be4e9a31a547737e091fba2ae81f Mon Sep 17 00:00:00 2001
From: NeilBrown <[email protected]>
Date: Tue, 4 Mar 2014 15:06:58 +1100
Subject: [PATCH] NFS: prepare for RCU-walk support but pushing tests later in
code.
nfs_lookup_revalidate, nfs4_lookup_revalidate, and nfs_permission all need to understand and handle RCU-walk for NFS to gain the benefits of RCU-walk for cached information.
Currently these functions all immediately return -ECHILD if the relevant flag (LOOKUP_RCU or MAY_NOT_BLOCK) is set.
This patch pushes those tests later in the code so that we only abort immediately before we enter rcu-unsafe code. As subsequent patches make that rcu-unsafe code rcu-safe, several of these new tests will disappear.
With this patch there are several paths through the code which will no longer return -ECHILD during an RCU-walk. However these are mostly error paths or other uninteresting cases.
A noteworthy change in nfs_lookup_revalidate is that we don't take (or put) the reference to ->d_parent when LOOKUP_RCU is set. Rather we rcu_dereference ->d_parent, and check that ->d_inode is not NULL. We also check that ->d_parent hasn't changed after all the tests.
In nfs4_lookup_revalidate we simple avoid testing LOOKUP_RCU on the path that simply calls nfs_lookup_revalidate() as that function already performs the required test.
fs/nfs/dir.c +33 -12
@@ -1055,21 +1055,30 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct nfs4_label *label =NULL ;int error; -if (flags &LOOKUP_RCU ) -return -ECHILD ; - - parent =dget_parent (dentry); - dir = parent->d_inode; +if (flags &LOOKUP_RCU ) { + parent =rcu_dereference (dentry->d_parent); + dir =ACCESS_ONCE (parent->d_inode); +if (!dir) +return -ECHILD ; + }else { + parent =dget_parent (dentry); + dir = parent->d_inode; + }nfs_inc_stats (dir,NFSIOS_DENTRYREVALIDATE ); inode = dentry->d_inode;if (!inode) { +if (flags &LOOKUP_RCU ) +return -ECHILD ; +if (nfs_neg_need_reval (dir, dentry, flags)) goto out_bad; goto out_valid_noent; }if (is_bad_inode (inode)) { +if (flags &LOOKUP_RCU ) +return -ECHILD ;dfprintk (LOOKUPCACHE ,"%s: %pd2 has dud inode\n" , __func__, dentry); goto out_bad;
@@ -1078,6 +1087,9 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)if (NFS_PROTO (dir)->have_delegation (inode,FMODE_READ )) goto out_set_verifier; +if (flags &LOOKUP_RCU ) +return -ECHILD ; +/* Force a full look up iff the parent directory has changed */ if (!nfs_is_exclusive_create (dir, flags) &&nfs_check_verifier (dir, dentry)) {if (nfs_lookup_verify_inode (inode, flags))
@@ -1120,13 +1132,18 @@ out_set_verifier:/* Success: notify readdir to use READDIRPLUS */ nfs_advise_use_readdirplus (dir); out_valid_noent: -dput (parent); +if (flags &LOOKUP_RCU ) { +if (parent !=rcu_dereference (dentry->d_parent)) +return -ECHILD ; + }else +dput (parent);dfprintk (LOOKUPCACHE ,"NFS: %s(%pd2) is valid\n" , __func__, dentry);return 1 ; out_zap_parent:nfs_zap_caches (dir); out_bad: +BUG_ON (flags &LOOKUP_RCU );nfs_free_fattr (fattr);nfs_free_fhandle (fhandle);nfs4_label_free (label);
@@ -1152,6 +1169,7 @@ out_zap_parent: __func__, dentry);return 0 ; out_error: +BUG_ON (flags &LOOKUP_RCU );nfs_free_fattr (fattr);nfs_free_fhandle (fhandle);nfs4_label_free (label);
@@ -1499,9 +1517,6 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct inode *inode;int ret =0 ; -if (flags &LOOKUP_RCU ) -return -ECHILD ; -if (!(flags &LOOKUP_OPEN ) || (flags &LOOKUP_DIRECTORY )) goto no_open;if (d_mountpoint (dentry))
@@ -1518,6 +1533,9 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct dentry *parent;struct inode *dir; +if (flags &LOOKUP_RCU ) +return -ECHILD ; + parent =dget_parent (dentry); dir = parent->d_inode;if (!nfs_neg_need_reval (dir, dentry, flags))
@@ -2273,9 +2291,6 @@ int nfs_permission(struct inode *inode, int mask)struct rpc_cred *cred;int res =0 ; -if (mask &MAY_NOT_BLOCK ) -return -ECHILD ; -nfs_inc_stats (inode,NFSIOS_VFSACCESS );if ((mask & (MAY_READ |MAY_WRITE |MAY_EXEC )) ==0 )
@@ -2302,6 +2317,9 @@ force_lookup:if (!NFS_PROTO (inode)->access) goto out_notsup; +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; + cred =rpc_lookup_cred ();if (!IS_ERR (cred)) { res =nfs_do_access (inode, cred, mask);
@@ -2316,6 +2334,9 @@ out: inode->i_sb->s_id, inode->i_ino, mask, res);return res; out_notsup: +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; + res =nfs_revalidate_inode (NFS_SERVER (inode), inode);if (res ==0 ) res =generic_permission (inode, mask);
PATCH 3/8 sunrpc/auth: allow lockless (rcu) lookup of credential cache.
In reply to NeilBrownThe new flag RPCAUTH_LOOKUP_RCU to credential lookup avoids locking, does not take a reference on the returned credential, and returns -ECHILD if a simple lookup was not possible.
The returned value can only be used within an rcu_read_lock protected region.
--- include/linux/sunrpc/auth.h | 1 + net/sunrpc/auth.c | 17 +++++++++++++++-- net/sunrpc/auth_null.c | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-)
include/linux/sunrpc/auth.h +1 -0
@@ -103,6 +103,7 @@ struct rpc_auth_create_args {/* Flags for rpcauth_lookupcred() */ #define RPCAUTH_LOOKUP_NEW 0x01/* Accept an uninitialised cred */ +#define RPCAUTH_LOOKUP_RCU 0x02/* lock-less lookup */ /* * Client authentication ops
net/sunrpc/auth.c +15 -2
@@ -523,6 +523,12 @@ rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred,hlist_for_each_entry_rcu (entry, &cache->hashtable[nr], cr_hash) {if (!entry->cr_ops->crmatch (acred, entry, flags))continue ; +if (flags &RPCAUTH_LOOKUP_RCU ) { +if (test_bit (RPCAUTH_CRED_HASHED , &entry->cr_flags) && + !test_bit (RPCAUTH_CRED_NEW , &entry->cr_flags)) + cred = entry; +break ; + }spin_lock (&cache->lock);if (test_bit (RPCAUTH_CRED_HASHED , &entry->cr_flags) ==0 ) {spin_unlock (&cache->lock);
@@ -537,6 +543,9 @@ rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred,if (cred !=NULL ) goto found; +if (flags &RPCAUTH_LOOKUP_RCU ) +return ERR_PTR (-ECHILD ); + new = auth->au_ops->crcreate (auth, acred, flags);if (IS_ERR (new)) { cred = new;
@@ -586,10 +595,14 @@ rpcauth_lookupcred(struct rpc_auth *auth, int flags)memset (&acred,0 ,sizeof (acred)); acred.uid = cred->fsuid; acred.gid = cred->fsgid; - acred.group_info =get_group_info (((struct cred *)cred)->group_info); +if (flags &RPCAUTH_LOOKUP_RCU ) + acred.group_info =rcu_dereference (cred->group_info); +else + acred.group_info =get_group_info (((struct cred *)cred)->group_info); ret = auth->au_ops->lookup_cred (auth, &acred, flags); -put_group_info (acred.group_info); +if (!(flags &RPCAUTH_LOOKUP_RCU )) +put_group_info (acred.group_info);return ret; }
net/sunrpc/auth_null.c +2 -0
@@ -35,6 +35,8 @@ nul_destroy(struct rpc_auth *auth)static struct rpc_cred *nul_lookup_cred (struct rpc_auth *auth,struct auth_cred *acred,int flags) { +if (flags &RPCAUTH_LOOKUP_RCU ) +return &null_cred;return get_rpccred (&null_cred); }
PATCH 4/8 sunrpc/auth: add 'rcu_walk' arg to rpc_lookup_cred.
In reply to NeilBrownThis arg causes rpc_lookup_cred to set RPCAUTH_LOOKUP_RCU when performing the credential lookup. Most callers pass '0', except nfs_permission() which passes (mask & MAY_NOT_BLOCK).
This doesn't speed up nfs_permission yet as it is not yet safe to call nfs_do_access with the returned cred.
--- fs/nfs/dir.c | 8 ++++---- fs/nfs/inode.c | 2 +- fs/nfs/nfs4proc.c | 2 +- fs/nfs/unlink.c | 4 ++-- include/linux/sunrpc/auth.h | 2 +- net/sunrpc/auth_generic.c | 5 +++-- 6 files changed, 12 insertions(+), 11 deletions(-)
fs/nfs/dir.c +4 -4
@@ -102,7 +102,7 @@ nfs_opendir(struct inode *inode, struct file *filp)nfs_inc_stats (inode,NFSIOS_VFSOPEN ); - cred =rpc_lookup_cred (); + cred =rpc_lookup_cred (0 );if (IS_ERR (cred))return PTR_ERR (cred); ctx =alloc_nfs_open_dir_context (inode, cred);
@@ -2308,11 +2308,11 @@ force_lookup:if (!NFS_PROTO (inode)->access) goto out_notsup; -if (mask &MAY_NOT_BLOCK ) -return -ECHILD ; - cred =rpc_lookup_cred (); + cred =rpc_lookup_cred (mask &MAY_NOT_BLOCK );if (!IS_ERR (cred)) { +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; res =nfs_do_access (inode, cred, mask);put_rpccred (cred); }else
fs/nfs/inode.c +1 -1
@@ -717,7 +717,7 @@ EXPORT_SYMBOL_GPL(nfs_close_context);struct nfs_open_context *alloc_nfs_open_context (struct dentry *dentry,fmode_t f_mode) {struct nfs_open_context *ctx; -struct rpc_cred *cred =rpc_lookup_cred (); +struct rpc_cred *cred =rpc_lookup_cred (0 );if (IS_ERR (cred))return ERR_CAST (cred);
fs/nfs/nfs4proc.c +1 -1
@@ -4733,7 +4733,7 @@ nfs4_set_security_label(struct dentry *dentry, const void *buf, size_t buflen) ilabel.label = (char *)buf; ilabel.len = buflen; - cred =rpc_lookup_cred (); + cred =rpc_lookup_cred (0 );if (IS_ERR (cred))return PTR_ERR (cred);
fs/nfs/unlink.c +2 -2
@@ -258,7 +258,7 @@ nfs_async_unlink(struct inode *dir, struct dentry *dentry)if (data ==NULL ) goto out; - data->cred =rpc_lookup_cred (); + data->cred =rpc_lookup_cred (0 );if (IS_ERR (data->cred)) { status =PTR_ERR (data->cred); goto out_free;
@@ -418,7 +418,7 @@ nfs_async_rename(struct inode *old_dir, struct inode *new_dir,return ERR_PTR (-ENOMEM ); task_setup_data.callback_data = data; - data->cred =rpc_lookup_cred (); + data->cred =rpc_lookup_cred (0 );if (IS_ERR (data->cred)) {struct rpc_task *task =ERR_CAST (data->cred);kfree (data);
include/linux/sunrpc/auth.h +1 -1
@@ -153,7 +153,7 @@ void rpcauth_remove_module(void);void rpc_destroy_generic_auth (void );void rpc_destroy_authunix (void ); -struct rpc_cred *rpc_lookup_cred (void ); +struct rpc_cred *rpc_lookup_cred (int rcu_walk);struct rpc_cred *rpc_lookup_machine_cred (const char *service_name);int rpcauth_register (const struct rpc_authops *);int rpcauth_unregister (const struct rpc_authops *);
net/sunrpc/auth_generic.c +3 -2
@@ -32,9 +32,10 @@ static const struct rpc_credops generic_credops;/* * Public call interface */ -struct rpc_cred *rpc_lookup_cred (void ) +struct rpc_cred *rpc_lookup_cred (int rcu_walk) { -return rpcauth_lookupcred (&generic_auth,0 ); +return rpcauth_lookupcred (&generic_auth, + rcu_walk ?RPCAUTH_LOOKUP_RCU :0 ); }EXPORT_SYMBOL_GPL (rpc_lookup_cred);
Re: [PATCH 4/8] sunrpc/auth: add 'rcu_walk' arg to rpc_lookup_cred.
Quoted 4 lines · NeilBrown
This arg causes
rpc_lookup_credto setRPCAUTH_LOOKUP_RCUwhen performing the credential lookup. Most callers pass '0', exceptnfs_permission()which passes (mask &MAY_NOT_BLOCK).
Why not just add a function rpc_lookup_cred_rcu() or rpc_lookup_cred_noblock() to cater for that one exception?
_________________________________
Trond Myklebust
Linux NFS client maintainer, PrimaryData
[email protected]
Re: [PATCH 4/8] sunrpc/auth: add 'rcu_walk' arg to rpc_lookup_cred.
Quoted 9 lines · On Wed, 5 Mar 2014 09:43:03 -0500 Trond Myklebust…
Quoted 4 lines · NeilBrown
This arg causes
rpc_lookup_credto setRPCAUTH_LOOKUP_RCUwhen performing the credential lookup. Most callers pass '0', exceptnfs_permission()which passes (mask &MAY_NOT_BLOCK).Why not just add a function
rpc_lookup_cred_rcu()orrpc_lookup_cred_noblock()to cater for that one exception?
That certainly makes the patch smaller, and is probably a net win.
Thanks.
NeilBrown
From b805a8f79a5527676b96b9654dd26d1b5bc03691 Mon Sep 17 00:00:00 2001
From: NeilBrown <[email protected]>
Date: Tue, 4 Mar 2014 15:28:43 +1100
Subject: [PATCH] sunrpc/auth: add rpc_lookup_cred_nonblock
This version of rpc_lookup_cred sets RPCAUTH_LOOKUP_RCU when performing the credential lookup.nfs_permission() uses it in RCU-walk mode.
This doesn't speed up nfs_permission yet as it is not yet safe to call nfs_do_access with the returned cred.
fs/nfs/dir.c +5 -3
@@ -2318,10 +2318,12 @@ force_lookup: goto out_notsup;if (mask &MAY_NOT_BLOCK ) -return -ECHILD ; - - cred =rpc_lookup_cred (); + cred =rpc_lookup_cred_nonblock (); +else + cred =rpc_lookup_cred ();if (!IS_ERR (cred)) { +if (mask &MAY_NOT_BLOCK ) +return -ECHILD ; res =nfs_do_access (inode, cred, mask);put_rpccred (cred); }else
include/linux/sunrpc/auth.h +1 -0
@@ -154,6 +154,7 @@ void rpc_destroy_generic_auth(void);void rpc_destroy_authunix (void );struct rpc_cred *rpc_lookup_cred (void ); +struct rpc_cred *rpc_lookup_cred_nonblock (void );struct rpc_cred *rpc_lookup_machine_cred (const char *service_name);int rpcauth_register (const struct rpc_authops *);int rpcauth_unregister (const struct rpc_authops *);
net/sunrpc/auth_generic.c +5 -0
@@ -38,6 +38,11 @@ struct rpc_cred *rpc_lookup_cred(void) }EXPORT_SYMBOL_GPL (rpc_lookup_cred); +struct rpc_cred *rpc_lookup_cred_nonblock (void ) +{ +return rpcauth_lookupcred (&generic_auth,1 ); +} +/* * Public call interface for looking up machine creds. */
PATCH 5/8 NFS: teach nfs_do_access to understand MAY_NOT_BLOCK
In reply to NeilBrownIf MAY_NOT_BLOCK is set we return -ECHILD in preference to making a network call.
This allows nfs_permission to call it in RCU-walk mode.
Now nfs_permission() can often complete in RCU-walk mode with no error.
--- fs/nfs/dir.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
fs/nfs/dir.c +6 -3
@@ -2232,6 +2232,10 @@ static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask)if (status ==0 ) goto out_cached; + status = -ECHILD ; +if (mask &MAY_NOT_BLOCK ) + goto out; +/* Be clever: ask server to check for all possible rights */ cache.mask =MAY_EXEC |MAY_WRITE |MAY_READ ; cache.cred = cred;
@@ -2311,10 +2315,9 @@ force_lookup: cred =rpc_lookup_cred (mask &MAY_NOT_BLOCK );if (!IS_ERR (cred)) { -if (mask &MAY_NOT_BLOCK ) -return -ECHILD ; res =nfs_do_access (inode, cred, mask); -put_rpccred (cred); +if (!(mask &MAY_NOT_BLOCK )) +put_rpccred (cred); }else res =PTR_ERR (cred); out:
PATCH 6/8 NFS: teach nfs_neg_need_reval to understand LOOKUP_RCU
In reply to NeilBrownThis requires nfs_check_verifier to take an rcu_walk flag, and requires an rcu version of nfs_revalidate_inode which returns -ECHILD rather than making an RPC call.
With this, nfs_lookup_revalidate can call nfs_neg_need_reval in RCU-walk mode.
We can also move the LOOKUP_RCU check past the nfs_check_verifier() call in nfs_lookup_revalidate.
--- fs/nfs/dir.c | 47 ++++++++++++++++++++++++++++++++--------------- fs/nfs/inode.c | 9 +++++++++ include/linux/nfs_fs.h | 1 + 3 files changed, 42 insertions(+), 15 deletions(-)
fs/nfs/dir.c +32 -15
@@ -956,8 +956,11 @@ EXPORT_SYMBOL_GPL(nfs_force_lookup_revalidate);* In the case it has, we assume that the dentries are untrustworthy * and may need to be looked up again. */ -static int nfs_check_verifier (struct inode *dir,struct dentry *dentry) +static int nfs_check_verifier (struct inode *dir,struct dentry *dentry, +int rcu_walk) { +int ret; +if (IS_ROOT (dentry))return 1 ;if (NFS_SERVER (dir)->flags &NFS_MOUNT_LOOKUP_CACHE_NONE )
@@ -965,7 +968,13 @@ static int nfs_check_verifier(struct inode *dir, struct dentry *dentry)if (!nfs_verify_change_attribute (dir, dentry->d_time))return 0 ;/* Revalidate nfsi->cache_change_attribute before we declare a match */ -if (nfs_revalidate_inode (NFS_SERVER (dir), dir) <0 ) +if (rcu_walk) + ret =nfs_revalidate_inode_rcu (NFS_SERVER (dir), dir); +else + ret =nfs_revalidate_inode (NFS_SERVER (dir), dir); +if (ret == -ECHILD ) +return -ECHILD ; +if (ret <0 )return 0 ;if (!nfs_verify_change_attribute (dir, dentry->d_time))return 0 ;
@@ -1031,7 +1040,7 @@ int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry,return 0 ;if (NFS_SERVER (dir)->flags &NFS_MOUNT_LOOKUP_CACHE_NONEG )return 1 ; -return !nfs_check_verifier (dir, dentry); +return !nfs_check_verifier (dir, dentry, flags &LOOKUP_RCU ); }/*
@@ -1064,11 +1073,11 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags) inode = dentry->d_inode;if (!inode) { -if (flags &LOOKUP_RCU ) -return -ECHILD ; - -if (nfs_neg_need_reval (dir, dentry, flags)) +if (nfs_neg_need_reval (dir, dentry, flags)) { +if (flags &LOOKUP_RCU ) +return -ECHILD ; goto out_bad; + } goto out_valid_noent; }
@@ -1081,11 +1090,13 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)if (NFS_PROTO (dir)->have_delegation (inode,FMODE_READ )) goto out_set_verifier; -if (flags &LOOKUP_RCU ) -return -ECHILD ; -/* Force a full look up iff the parent directory has changed */ -if (!nfs_is_exclusive_create (dir, flags) &&nfs_check_verifier (dir, dentry)) { +if (!nfs_is_exclusive_create (dir, flags) && +nfs_check_verifier (dir, dentry, flags &LOOKUP_RCU )) { + +if (flags &LOOKUP_RCU ) +return -ECHILD ; +if (nfs_lookup_verify_inode (inode, flags)) goto out_zap_parent; goto out_valid;
@@ -1094,6 +1105,9 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)if (NFS_STALE (inode)) goto out_bad; +if (flags &LOOKUP_RCU ) +return -ECHILD ; + error = -ENOMEM ; fhandle =nfs_alloc_fhandle (); fattr =nfs_alloc_fattr ();
@@ -1525,13 +1539,16 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)struct inode *dir;if (flags &LOOKUP_RCU ) -return -ECHILD ; - - parent =dget_parent (dentry); + parent =rcu_dereference (dentry); +else + parent =dget_parent (dentry); dir = parent->d_inode;if (!nfs_neg_need_reval (dir, dentry, flags)) ret =1 ; -dput (parent); +else if (flags &LOOKUP_RCU ) + ret = -ECHILD ; +if (!(flags &LOOKUP_RCU )) +dput (parent); goto out; }
fs/nfs/inode.c +9 -0
@@ -962,6 +962,15 @@ int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode) }EXPORT_SYMBOL_GPL (nfs_revalidate_inode); +int nfs_revalidate_inode_rcu (struct nfs_server *server,struct inode *inode) +{ +if (!(NFS_I (inode)->cache_validity & + (NFS_INO_INVALID_ATTR |NFS_INO_INVALID_LABEL )) + && !nfs_attribute_cache_expired (inode)) +return NFS_STALE (inode) ? -ESTALE :0 ; +return -ECHILD ; +} +static int nfs_invalidate_mapping (struct inode *inode,struct address_space *mapping) {struct nfs_inode *nfsi =NFS_I (inode);
include/linux/nfs_fs.h +1 -0
@@ -351,6 +351,7 @@ extern int nfs_release(struct inode *, struct file *);extern int nfs_attribute_timeout (struct inode *inode);extern int nfs_attribute_cache_expired (struct inode *inode);extern int nfs_revalidate_inode (struct nfs_server *server,struct inode *inode); +extern int nfs_revalidate_inode_rcu (struct nfs_server *server,struct inode *inode);extern int __nfs_revalidate_inode (struct nfs_server *,struct inode *);extern int nfs_revalidate_mapping (struct inode *inode,struct address_space *mapping);extern int nfs_setattr (struct dentry *,struct iattr *);
PATCH 7/8 NFS: teach nfs_lookup_verify_inode to handle LOOKUP_RCU
In reply to NeilBrownIt fails with -ECHILD rather than make an RPC call.
This allows nfs_lookup_revalidate to call it in RCU-walk mode.
--- fs/nfs/dir.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
fs/nfs/dir.c +6 -4
@@ -1018,6 +1018,8 @@ int nfs_lookup_verify_inode(struct inode *inode, unsigned int flags) out:return (inode->i_nlink ==0 ) ? -ENOENT :0 ; out_force: +if (flags &LOOKUP_RCU ) +return -ECHILD ; ret =__nfs_revalidate_inode (server, inode);if (ret !=0 )return ret;
@@ -1094,11 +1096,11 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)if (!nfs_is_exclusive_create (dir, flags) &&nfs_check_verifier (dir, dentry, flags &LOOKUP_RCU )) { -if (flags &LOOKUP_RCU ) -return -ECHILD ; - -if (nfs_lookup_verify_inode (inode, flags)) +if (nfs_lookup_verify_inode (inode, flags)) { +if (flags &LOOKUP_RCU ) +return -ECHILD ; goto out_zap_parent; + } goto out_valid; }
PATCH 8/8 NFS: allow lockless access to access_cache
In reply to NeilBrownThe access cache is used during RCU-walk path lookups, so it is best to avoid locking if possible as taking a lock kills concurrency.
The rbtree is not rcu-safe and cannot easily be made so. Instead we simply check the last (i.e. most recent) entry on the LRU list. If this doesn't match, then we return -ECHILD and retry in lock/refcount mode.
This requires freeing the nfs_access_entry struct with rcu, and requires using rcu access primates when adding entries to the lru, and when examining the last entry.
Calling put_rpccred before kfree_rcu looks a bit odd, but as put_rpccred already provide rcu protection, we know that the cred will not actually be freed until the next grace period, so any concurrent access will be safe.
This patch provides about 5% performance improvement on a stat-heavy synthetic work load with 4 threads on a 2-core CPU.
--- fs/nfs/dir.c | 41 +++++++++++++++++++++++++++++++++++++++-- include/linux/nfs_fs.h | 1 + 2 files changed, 40 insertions(+), 2 deletions(-)
fs/nfs/dir.c +39 -2
@@ -2011,7 +2011,7 @@ static atomic_long_t nfs_access_nr_entries;static void nfs_access_free_entry (struct nfs_access_entry *entry) {put_rpccred (entry->cred); -kfree (entry); +kfree_rcu (entry, rcu_head);smp_mb__before_atomic_dec ();atomic_long_dec (&nfs_access_nr_entries);smp_mb__after_atomic_dec ();
@@ -2166,6 +2166,36 @@ out_zap:return -ENOENT ; } +static int nfs_access_get_cached_rcu (struct inode *inode,struct rpc_cred *cred,struct nfs_access_entry *res) +{ +/* Only check the most recently returned cache entry, +* but do it without locking. +*/ +struct nfs_inode *nfsi =NFS_I (inode); +struct nfs_access_entry *cache; +int err = -ECHILD ; +struct list_head *lh; + +if (nfsi->cache_validity &NFS_INO_INVALID_ACCESS ) + goto out; + lh =rcu_dereference (nfsi->access_cache_entry_lru.prev); + cache = list_entry(lh, struct nfs_access_entry, lru); +if (lh == &nfsi->access_cache_entry_lru || + cred != cache->cred) + cache =NULL ; +if (cache ==NULL ) + goto out; +if (!nfs_have_delegated_attributes (inode) && + !time_in_range_open (jiffies, cache->jiffies, cache->jiffies + nfsi->attrtimeo)) + goto out; + res->jiffies = cache->jiffies; + res->cred = cache->cred; + res->mask = cache->mask; + err =0 ; +out: +return err; +} +static void nfs_access_add_rbtree (struct inode *inode,struct nfs_access_entry *set) {struct nfs_inode *nfsi =NFS_I (inode);
@@ -2209,6 +2239,11 @@ void nfs_access_add_cache(struct inode *inode, struct nfs_access_entry *set) cache->cred =get_rpccred (set->cred); cache->mask = set->mask; +/* The above fields assignments must be visible +* before this item appears on the lru. We cannot easily +* use rcu_assign_pointer, so just force the memory barrier. +*/ +smp_wmb ();nfs_access_add_rbtree (inode, cache);/* Update accounting */
@@ -2247,7 +2282,9 @@ static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask)trace_nfs_access_enter (inode); - status =nfs_access_get_cached (inode, cred, &cache); +if (!(mask &MAY_NOT_BLOCK ) || + (status =nfs_access_get_cached_rcu (inode, cred, &cache)) !=0 ) + status =nfs_access_get_cached (inode, cred, &cache);if (status ==0 ) goto out_cached;
include/linux/nfs_fs.h +1 -0
@@ -52,6 +52,7 @@ struct nfs_access_entry {unsigned long jiffies;struct rpc_cred * cred;int mask; +struct rcu_head rcu_head; };struct nfs_lockowner {
Re: [PATCH 8/8] NFS: allow lockless access to access_cache
Quoted 12 lines · NeilBrown
The access cache is used during RCU-walk path lookups, so it is best to avoid locking if possible as taking a lock kills concurrency.
The rbtree is not rcu-safe and cannot easily be made so. Instead we simply check the last (i.e. most recent) entry on the LRU list. If this doesn't match, then we return
-ECHILDand retry in lock/refcount mode.This requires freeing the
nfs_access_entrystruct with rcu, and requires using rcu access primates when adding entries to the lru, and when examining the last entry.
Quoted 8 lines
Calling
put_rpccredbeforekfree_rculooks a bit odd, but asput_rpccredalready provide rcu protection, we know that the cred will not actually be freed until the next grace period, so any concurrent access will be safe.This patch provides about 5% performance improvement on a stat-heavy synthetic work load with 4 threads on a 2-core CPU.
Have you looked at converting the rb-tree into something a little more RCU-friendly? Since our lookup key is just a pointer value, couldn’t we use a radix-tree?
_________________________________
Trond Myklebust
Linux NFS client maintainer, PrimaryData
[email protected]
Re: [PATCH 8/8] NFS: allow lockless access to access_cache
Quoted 20 lines · On Wed, 5 Mar 2014 10:11:02 -0500 Trond Myklebust…
Quoted 17 lines · NeilBrown
The access cache is used during RCU-walk path lookups, so it is best to avoid locking if possible as taking a lock kills concurrency.
The rbtree is not rcu-safe and cannot easily be made so. Instead we simply check the last (i.e. most recent) entry on the LRU list. If this doesn't match, then we return
-ECHILDand retry in lock/refcount mode.This requires freeing the
nfs_access_entrystruct with rcu, and requires using rcu access primates when adding entries to the lru, and when examining the last entry.Calling
put_rpccredbeforekfree_rculooks a bit odd, but asput_rpccredalready provide rcu protection, we know that the cred will not actually be freed until the next grace period, so any concurrent access will be safe.
Quoted 6 lines
Quoted 3 lines
This patch provides about 5% performance improvement on a stat-heavy synthetic work load with 4 threads on a 2-core CPU.
Have you looked at converting the rb-tree into something a little more RCU-friendly? Since our lookup key is just a pointer value, couldn’t we use a radix-tree?
Interesting idea. The radix tree would be fairly sparse unless we divided the address by the size of the structure ... would that hit alignment issues? But we don't know real size of the structure (it can depend on auth scheme) so I doubt that would work. So I think a radix tree would waste a lot of space.
Skip lists might work https://lwn.net/Articles/551896/ but they don't appear to be in the kernel yet.
I think my current approach is no worse the existing code and while there is room for further improvement, that should probably wait until a suitable data structure is available.
Thanks,
NeilBrown