summaryrefslogtreecommitdiff
path: root/Documentation/filesystems/path-lookup.txt
blob: fbafc0f98d38de5feef09acc342d09fccab2762b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
Path walking and name lookup locking
====================================

Path resolution is finding a dentry or parent dentry corresponding to a path
name, by performing a path walk. Typically, for every open(), stat() etc., the
path name will be resolved. Paths are resolved by walking the tree starting
with the first component of the pathname, and using that dentry along with the
next component to look up the next level and so on.

Since it is a frequent operation for workloads like multiuser environments and
web servers, it is important to optimize this code.

Path walking synchronisation history:
Prior to 2.5.10, dcache_lock was acquired in d_lookup (dcache hash lookup) and
thus in every component during path look-up. Since 2.5.10 onwards, fast-walk
algorithm changed this by holding the dcache_lock at the beginning and walking
as many cached path component dentries as possible. This significantly
decreases the number of acquisition of dcache_lock. However it also increases
the lock hold time significantly and affects performance in large SMP machines.
Since 2.5.62 kernel, dcache has been using a new locking model that uses RCU to
make dcache look-up lock-free.

All the above algorithms required taking a lock and reference count on the
dentry that was looked up, so that may be used as the basis for walking the
next path element. This is inefficient and unscalable. It is inefficient
because of the locks and atomic operations required for every dentry element
slows things down. It is not scalable because many parallel applications that
are path-walk intensive tend to do path lookups starting from a common dentry
(usually, the root "/" or current working directory). So contention on these
common path elements causes lock and cacheline queueing.

Since 2.6.38, RCU is used to make a significant part of the entire path walk
(including dcache look-up) completely "store-free" (so, no locks, atomics, or
even stores into cachelines of common dentries). This is known as rcu-walk
path walking.

Path walking overview
=====================

A name string specifies a start (root directory, cwd, fd-relative) and a
sequence of elements (directory entry names), which together refer to a path in
the namespace. The elements are strings seperated by '/'.

Name lookups will want to find a particular path that a name string refers to
(usually the path of the final element, or parent of final element). This is
done by taking the path given by the name's starting point (which we know in
advance -- eg.  current->fs->cwd) as the first parent of the lookup. Then
iteratively for each name element, look up the child of the current parent with
the given name and if it is not the final entry, make it the parent for the
next lookup.

The parent must of course be a directory, and we must have appropriate
permissions on the parent inode to be able to walk into it.

Making the child a parent for the next lookup requires more checks and
procedures. Symlinks essentially substitute the symlink name for the target
name in the name string, and require some recursive path walking.  Mount points
must be followed into, switching from the mount point path to the root of the
particular mounted vfsmount.

Path walking then must, broadly, do several particular things:
- perform dcache name lookups on (parent, name element) tuples;
- find the start point of the walk;
- perform permissions and validity checks on inodes;
- traverse mount points;
- traverse symlinks;
- lookup and create missing parts of the path on demand.

Safe store-free look-up of dcache hash table
============================================

Dcache name lookup
------------------
In order to lookup a dcache (parent, name) tuple, we take a hash on the tuple
and use that to select a bucket in the dcache-hash table. The list of entries
in that bucket is then walked, and we do a full comparison of each entry
against our (parent, name) tuple.

The hash lists are RCU protected, so list walking is not serialised with
concurrent updates (insertion, deletion from the hash). This is a standard RCU
list application with the exception of renames, which will be covered below.

Parent and name members of a dentry, as well as its membership in the dcache
hash, and its inode are protected by the per-dentry d_lock spinlock. A
reference is taken on the dentry (while the fields are verified under d_lock),
and this stabilises its d_inode pointer and the inode it points to. This gives
a stable point to perform the next step of our path walk against.

These members are also protected by d_seq seqlock, although this offers
read-only protection and no durability of results so care must be taken when
using d_seq for synchronisation (see below).

Renames
-------
Back to the rename case. In usual RCU protected lists, the only operations that
will happen to an object is insertion, and then eventually removal from the
list. The object will not be reused until an RCU grace period is complete.
This ensures the RCU list traversal primitives can run over the object without
problems (see RCU documentation for how this works).

However when a dentry is renamed, its hash value can change, requiring it to be
moved to a new hash list. Allocating and inserting a new alias would be
expensive and also problematic for directory dentries. Latency would be far to
high to wait for a grace period after removing the dentry and before inserting
it in the new hash bucket. So what is done is to insert the dentry into the
new list immediately.

However, when the dentry's list pointers are updated to point to objects in the
new list before waiting for a grace period, this can result in a concurrent RCU
lookup of the old list veering off into the new (incorrect) list and missing
the remaining dentries on the list.

There is no fundamental problem with walking down the wrong list, because the
dentry comparisons will never match. However it is fatal to miss a matching
dentry. So a seqlock is used to detect when a rename has occurred, and so the
lookup can be retried.

         1      2      3
        +---+  +---+  +---+
hlist-->| N-+->| N-+->| N-+->
head <--+-P |<-+-P |<-+-P |
        +---+  +---+  +---+

Rename of dentry 2 may require it deleted from the above list, and inserted
into a new list. Deleting 2 gives the following list.

         1             3
        +---+         +---+     (don't worry, the longer pointers do not
hlist-->| N-+-------->| N-+->    impose a measurable performance overhead
head <--+-P |<--------+-P |      on modern CPUs)
        +---+         +---+
          ^      2      ^
          |    +---+    |
          |    | N-+----+
          +----+-P |
               +---+

This is a standard RCU-list deletion, which leaves the deleted object's
pointers intact, so a concurrent list walker that is currently looking at
object 2 will correctly continue to object 3 when it is time to traverse the
next object.

However, when inserting object 2 onto a new list, we end up with this:

         1             3
        +---+         +---+
hlist-->| N-+-------->| N-+->
head <--+-P |<--------+-P |
        +---+         +---+
                 2
               +---+
               | N-+---->
          <----+-P |
               +---+

Because we didn't wait for a grace period, there may be a concurrent lookup
still at 2. Now when it follows 2's 'next' pointer, it will walk off into
another list without ever having checked object 3.

A related, but distinctly different, issue is that of rename atomicity versus
lookup operations. If a file is renamed from 'A' to 'B', a lookup must only
find either 'A' or 'B'. So if a lookup of 'A' returns NULL, a subsequent lookup
of 'B' must succeed (note the reverse is not true).

Between deleting the dentry from the old hash list, and inserting it on the new
hash list, a lookup may find neither 'A' nor 'B' matching the dentry. The same
rename seqlock is also used to cover this race in much the same way, by
retrying a negative lookup result if a rename was in progress.

Seqcount based lookups
----------------------
In refcount based dcache lookups, d_lock is used to serialise access to
the dentry, stabilising it while comparing its name and parent and then
taking a reference count (the reference count then gives a stable place to
start the next part of the path walk from).

So in rcu-walk mode, we want to do path walking without taking locks or
reference counts. So d_seq is used to take a coherent snapshot of what the
dentry looks like (its name, parent, and inode). That snapshot is then used
to start the next part of the path walk. When loading our coherent snapshot
under d_seq, care must be taken to load the members up-front, and use those
loaded pointers rather than reloading from the dentry later on (otherwise
we'd have interesting things like d_inode going NULL underneath us).

Also important is to avoid performing any destructive operations (pretty much:
no non-atomic stores to shared data), and to recheck the seqcount when we are
"done" with the operation. Retry or abort if the seqcount does not match.
Avoiding destructive or changing operations means we can easily unwind from
failure.

What this means is that a caller, provided they are holding RCU lock to
protect the dentry object from disappearing, can perform a seqcount based
lookup which does not increment the refcount on the dentry or write to
it in any way. This returned dentry can be used for subsequent operations,
provided that d_seq is rechecked after that operation is complete.

RCU-walk path walking design
============================

Path walking code has two distinct modes, ref-walk and rcu-walk. ref-walk
is the traditional[*] way of performing dcache lookups using d_lock to
serialise concurrent modifications to the dentry and take a reference count
on it. ref-walk is simple and obvious, and may sleep, take locks, etc while
path walking is operating on each dentry. rcu-walk uses seqcount based
dentry lookups and can perform lookup of intermediate elements without
performing any stores to shared data in the dentry or inode. rcu-walk can
not be applied to all cases, eg. if the filesystem must sleep or perform
non trivial operations, rcu-walk must be switched to ref-walk.

[*] RCU is still used for the dentry hash lookup, but not the full path walk.

Where ref-walk uses a stable, refcounted ``parent'' to walk the remaining
path string, rcu-walk uses a d_seq protected snapshot. When looking up a
child from its parent snapshot, we open d_seq critical section on the child
before closing d_seq critical section on the parent. This gives an interlocking
ladder of snapshots to walk down.

It is, in some sense, a bit of a house of cards. If the seqcount check of the
parent snapshot fails, the house comes down, because we had closed the
d_seq section on the grandparent, so we have nothing to stand on. In that case,
the path walk must be restarted (which we do in ref-walk mode, to avoid live
locks). It is costly to have a failed walk like this, but fortunately, the
further toward the root we go, the less frequently changes tend to happen.

When we reach a point where sleeping is required, or a filesystem callout is
required, then instead of starting the walk again, we attempt to drop out of
rcu-walk mode into ref-walk mode at the point we have reached. That is, we
take a lock on the dentry, then recheck its d_seq count under lock (to ensure
we got the right thing), and then its refcount is elevated. This way, we have
a stable, referenced dentry as-in ref-walk.

The overall design for rcu-walk is like this:
* LOOKUP_RCU is set in nd->flags, which distinguishes rcu-walk from ref-walk.
* Take the RCU lock for the entire path walk, starting with the acquiring
  of the starting path (eg. root/cwd/fd-path). So now dentry refcounts are
  not required for dentry persistence.
* synchronize_rcu is called when unregistering a filesystem, so we can
  access d_ops and i_ops during rcu-walk.
* Similarly take the vfsmount lock for the entire path walk. So now mnt
  refcounts are not required for persistence. Also we are free to perform mount
  lookups, and to assume dentry mount points and mount roots are stable up and
  down the path.
* Have a per-dentry seqlock to protect the dentry name, parent, and inode,
  so we can load this tuple atomically, and also check whether any of its
  members have changed.
* Dentry lookups (based on parent, candidate string tuple) recheck the parent
  sequence after the child is found in case anything changed in the parent
  during the path walk.
* inode is also RCU protected so we can load d_inode and use the inode for
  limited things.
* i_mode, i_uid, i_gid can be tested for exec permissions during path walk.
* i_op can be loaded.

When we reach the destination dentry, we lock it, recheck lookup sequence,
and increment its refcount and mountpoint refcount. RCU and vfsmount locks
are dropped. This is termed "dropping rcu-walk" (see nameidata_drop_rcu group
of functions). If the dentry seqcount does not match, we can not drop rcu-walk
gracefully at the current point in the lokup, so instead return -ECHILD (for
want of a better errno). This signals the path walking code to re-do the entire
lookup with a ref-walk.

Aside from the final dentry, there are other situations that may be encountered
where we cannot continue rcu-walk. In that case, we drop rcu-walk (ie. take a
reference on the last good dentry) and continue with a ref-walk. Again, if we
can drop rcu-walk gracefully, we return -ECHILD and do the whole lookup using
ref-walk. But it is very important that we can continue with ref-walk for most
cases, particularly to avoid the overhead of double lookups, and to gain the
scalability advantages on common path elements (like cwd and root).

The cases where rcu-walk cannot continue are:
* NULL dentry (ie. any uncached path element)
* Following links

It may be possible eventually to make following links rcu-walk aware.

Uncached path elements will always require dropping to ref-walk mode, at the
very least because i_mutex needs to be grabbed, and objects allocated.

Final note:
"store-free" path walking is not strictly store free. We take vfsmount lock
and refcounts (both of which can be made per-cpu), and we also store to the
stack (which is essentially CPU-local), and we also have to take locks and
refcount on final dentry.

But the point is that shared data, where practically possible, is not locked
or stored into.


Papers and other documentation on dcache locking
================================================

1. Scaling dcache with RCU (http://linuxjournal.com/article.php?sid=7124).

2. http://lse.sourceforge.net/locking/dcache/dcache.html