History

access: avoid the RCU grace period for the temporary subjective credentials

It turns out that 'access()' (and 'faccessat()') can cause a lot of RCU work because it installs a temporary credential that gets allocated and freed for each system call. The allocation and freeing overhead is mostly benign, but because credentials can be accessed under the RCU read lock, the freeing involves a RCU grace period. Which is not a huge deal normally, but if you have a lot of access() calls, this causes a fair amount of seconday damage: instead of having a nice alloc/free patterns that hits in hot per-CPU slab caches, you have all those delayed free's, and on big machines with hundreds of cores, the RCU overhead can end up being enormous. But it turns out that all of this is entirely unnecessary. Exactly because access() only installs the credential as the thread-local subjective credential, the temporary cred pointer doesn't actually need to be RCU free'd at all. Once we're done using it, we can just free it synchronously and avoid all the RCU overhead. So add a 'non_rcu' flag to 'struct cred', which can be set by users that know they only use it in non-RCU context (there are other potential users for this). We can make it a union with the rcu freeing list head that we need for the RCU case, so this doesn't need any extra storage. Note that this also makes 'get_current_cred()' clear the new non_rcu flag, in case we have filesystems that take a long-term reference to the cred and then expect the RCU delayed freeing afterwards. It's not entirely clear that this is required, but it makes for clear semantics: the subjective cred remains non-RCU as long as you only access it synchronously using the thread-local accessors, but you _can_ use it as a generic cred if you want to. It is possible that we should just remove the whole RCU markings for ->cred entirely. Only ->real_cred is really supposed to be accessed through RCU, and the long-term cred copies that nfs uses might want to explicitly re-enable RCU freeing if required, rather than have get_current_cred() do it implicitly. But this is a "minimal semantic changes" change for the immediate problem.

Linus Torvalds committed 7 years ago

Commit d7852fb1 parent 0ecfebd3 files · +45 −3Browse files

Unified · Split

3 files
  1. fs/open.c +19 −0
  2. include/linux/cred.h +7 −1
  3. kernel/cred.c +19 −2
fs/open.c+19−0
@@ -374,6 +374,25 @@ long do_faccessat(int dfd, const char __user *filename, int mode) · 373 unmodified lines
374374 override_cred->cap_permitted;
375375 }
376376
377+ /*
378+ * The new set of credentials can *only* be used in
379+ * task-synchronous circumstances, and does not need
380+ * RCU freeing, unless somebody then takes a separate
381+ * reference to it.
382+ *
383+ * NOTE! This is _only_ true because this credential
384+ * is used purely for override_creds() that installs
385+ * it as the subjective cred. Other threads will be
386+ * accessing ->real_cred, not the subjective cred.
387+ *
388+ * If somebody _does_ make a copy of this (using the
389+ * 'get_current_cred()' function), that will clear the
390+ * non_rcu field, because now that other user may be
391+ * expecting RCU freeing. But normal thread-synchronous
392+ * cred accesses will keep things non-RCY.
393+ */
394+ override_cred->non_rcu = 1;
395+
377396 old_cred = override_creds(override_cred);
378397 retry:
379398 res = user_path_at(dfd, filename, lookup_flags, &path);
include/linux/cred.h+7−1
@@ -145,7 +145,11 @@ struct cred { · 144 unmodified lines
145145 struct user_struct *user; /* real user ID subscription */
146146 struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
147147 struct group_info *group_info; /* supplementary groups for euid/fsgid */
148− struct rcu_head rcu; /* RCU deletion hook */
148+ /* RCU deletion */
149+ union {
150+ int non_rcu; /* Can we skip RCU deletion? */
151+ struct rcu_head rcu; /* RCU deletion hook */
152+ };
149153 } __randomize_layout;
150154
151155 extern void __put_cred(struct cred *);
@@ -246,6 +250,7 @@ static inline const struct cred *get_cred(const struct cred *cred) · 94 unmodified lines
246250 if (!cred)
247251 return cred;
248252 validate_creds(cred);
253+ nonconst_cred->non_rcu = 0;
249254 return get_new_cred(nonconst_cred);
250255 }
251256
@@ -257,6 +262,7 @@ static inline const struct cred *get_cred_rcu(const struct cred *cred) · 5 unmodified lines
257262 if (!atomic_inc_not_zero(&nonconst_cred->usage))
258263 return NULL;
259264 validate_creds(cred);
265+ nonconst_cred->non_rcu = 0;
260266 return cred;
261267 }
262268
kernel/cred.c+19−2
@@ -144,7 +144,10 @@ void __put_cred(struct cred *cred) · 143 unmodified lines
144144 BUG_ON(cred == current->cred);
145145 BUG_ON(cred == current->real_cred);
146146
147− call_rcu(&cred->rcu, put_cred_rcu);
147+ if (cred->non_rcu)
148+ put_cred_rcu(&cred->rcu);
149+ else
150+ call_rcu(&cred->rcu, put_cred_rcu);
148151 }
149152 EXPORT_SYMBOL(__put_cred);
150153
@@ -256,6 +259,7 @@ struct cred *prepare_creds(void) · 105 unmodified lines
256259 old = task->cred;
257260 memcpy(new, old, sizeof(struct cred));
258261
262+ new->non_rcu = 0;
259263 atomic_set(&new->usage, 1);
260264 set_cred_subscribers(new, 0);
261265 get_group_info(new->group_info);
@@ -535,7 +539,19 @@ const struct cred *override_creds(const struct cred *new) · 273 unmodified lines
535539
536540 validate_creds(old);
537541 validate_creds(new);
538− get_cred(new);
542+
543+ /*
544+ * NOTE! This uses 'get_new_cred()' rather than 'get_cred()'.
545+ *
546+ * That means that we do not clear the 'non_rcu' flag, since
547+ * we are only installing the cred into the thread-synchronous
548+ * '->cred' pointer, not the '->real_cred' pointer that is
549+ * visible to other threads under RCU.
550+ *
551+ * Also note that we did validate_creds() manually, not depending
552+ * on the validation in 'get_cred()'.
553+ */
554+ get_new_cred((struct cred *)new);
539555 alter_cred_subscribers(new, 1);
540556 rcu_assign_pointer(current->cred, new);
541557 alter_cred_subscribers(old, -1);
@@ -672,6 +688,7 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) · 130 unmodified lines
672688 validate_creds(old);
673689
674690 *new = *old;
691+ new->non_rcu = 0;
675692 atomic_set(&new->usage, 1);
676693 set_cred_subscribers(new, 0);
677694 get_uid(new->user);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t