← Documents Documentation/core-api/xarray.rst GitHub 원문 ↗

Linux 6.18.37 · Core API

XArray

XArray의 pointer·value·tagged pointer 저장 모델, 일반 API, mark와 ID 할당, RCU 및 xa_lock 규칙, xa_state 기반 고급 API, internal entry와 multi-index entry를 설명합니다.

Source pathDocumentation/core-api/xarray.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

xarray.rst:1-510

XArray는 큰 sparse pointer 배열을 radix-tree 계열 구조로 구현하여 cache 효율적인 순회, lockless RCU lookup, 동적 확장을 제공합니다. Index가 조밀할수록 효율적이며 page cache가 대표 사용자입니다.

일반 API는 내부 locking을 제공하며 `xa_store()`, `xa_load()`, `xa_erase()`, `xa_find()`와 mark 및 ID allocation helper를 사용합니다. Interrupt나 softirq와 공유할 때는 `XA_FLAGS_LOCK_IRQ` 또는 `XA_FLAGS_LOCK_BH`와 대응 helper를 선택해야 합니다.

고급 API는 `XA_STATE()`로 만든 `xa_state` cursor를 사용합니다. 호출자가 `xa_lock` 또는 RCU 보호를 책임지는 대신 반복 순회와 조합 operation에서 더 높은 성능과 유연성을 얻습니다.

Multi-index entry는 alignment가 맞는 power-of-two index 범위를 하나의 entry로 묶어 memory를 절약합니다. Split, mark, lookup의 범위 semantics와 internal entry 처리에서 `xas_retry()` 규칙을 지키는 것이 중요합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0+
2
3 ======
4 XArray
5 ======
6
7 :Author: Matthew Wilcox
8
9 Overview
10 ========
11
12 The XArray is an abstract data type which behaves like a very large array
13 of pointers. It meets many of the same needs as a hash or a conventional
14 resizable array. Unlike a hash, it allows you to sensibly go to the
15 next or previous entry in a cache-efficient manner. In contrast to a
16 resizable array, there is no need to copy data or change MMU mappings in
17 order to grow the array. It is more memory-efficient, parallelisable
18 and cache friendly than a doubly-linked list. It takes advantage of
19 RCU to perform lookups without locking.
20
21 The XArray implementation is efficient when the indices used are densely
22 clustered; hashing the object and using the hash as the index will not
23 perform well. The XArray is optimised for small indices, but still has
24 good performance with large indices. If your index can be larger than
25 ``ULONG_MAX`` then the XArray is not the data type for you. The most
26 important user of the XArray is the page cache.
27
28 Normal pointers may be stored in the XArray directly. They must be 4-byte
29 aligned, which is true for any pointer returned from kmalloc() and
30 alloc_page(). It isn't true for arbitrary user-space pointers,
31 nor for function pointers. You can store pointers to statically allocated
32 objects, as long as those objects have an alignment of at least 4.
33
34 You can also store integers between 0 and ``LONG_MAX`` in the XArray.
35 You must first convert it into an entry using xa_mk_value().
36 When you retrieve an entry from the XArray, you can check whether it is
37 a value entry by calling xa_is_value(), and convert it back to
38 an integer by calling xa_to_value().
39
40 Some users want to tag the pointers they store in the XArray. You can
41 call xa_tag_pointer() to create an entry with a tag, xa_untag_pointer()
42 to turn a tagged entry back into an untagged pointer and xa_pointer_tag()
43 to retrieve the tag of an entry. Tagged pointers use the same bits that
44 are used to distinguish value entries from normal pointers, so you must
45 decide whether you want to store value entries or tagged pointers in any
46 particular XArray.
47
48 The XArray does not support storing IS_ERR() pointers as some
49 conflict with value entries or internal entries.
50
51 An unusual feature of the XArray is the ability to create entries which
52 occupy a range of indices. Once stored to, looking up any index in
53 the range will return the same entry as looking up any other index in
54 the range. Storing to any index will store to all of them. Multi-index
55 entries can be explicitly split into smaller entries. Unsetting (using
56 xa_erase() or xa_store() with ``NULL``) any entry will cause the XArray
57 to forget about the range.
58
59 Normal API
60 ==========
61
62 Start by initialising an XArray, either with DEFINE_XARRAY()
63 for statically allocated XArrays or xa_init() for dynamically
64 allocated ones. A freshly-initialised XArray contains a ``NULL``
65 pointer at every index.
66
67 You can then set entries using xa_store() and get entries using
68 xa_load(). xa_store() will overwrite any entry with the new entry and
69 return the previous entry stored at that index. You can unset entries
70 using xa_erase() or by setting the entry to ``NULL`` using xa_store().
71 There is no difference between an entry that has never been stored to
72 and one that has been erased with xa_erase(); an entry that has most
73 recently had ``NULL`` stored to it is also equivalent except if the
74 XArray was initialized with ``XA_FLAGS_ALLOC``.
75
76 You can conditionally replace an entry at an index by using
77 xa_cmpxchg(). Like cmpxchg(), it will only succeed if
78 the entry at that index has the 'old' value. It also returns the entry
79 which was at that index; if it returns the same entry which was passed as
80 'old', then xa_cmpxchg() succeeded.
81
82 If you want to only store a new entry to an index if the current entry
83 at that index is ``NULL``, you can use xa_insert() which
84 returns ``-EBUSY`` if the entry is not empty.
85
86 You can copy entries out of the XArray into a plain array by calling
87 xa_extract(). Or you can iterate over the present entries in the XArray
88 by calling xa_for_each(), xa_for_each_start() or xa_for_each_range().
89 You may prefer to use xa_find() or xa_find_after() to move to the next
90 present entry in the XArray.
91
92 Calling xa_store_range() stores the same entry in a range
93 of indices. If you do this, some of the other operations will behave
94 in a slightly odd way. For example, marking the entry at one index
95 may result in the entry being marked at some, but not all of the other
96 indices. Storing into one index may result in the entry retrieved by
97 some, but not all of the other indices changing.
98
99 Sometimes you need to ensure that a subsequent call to xa_store()
100 will not need to allocate memory. The xa_reserve() function
101 will store a reserved entry at the indicated index. Users of the
102 normal API will see this entry as containing ``NULL``. If you do
103 not need to use the reserved entry, you can call xa_release()
104 to remove the unused entry. If another user has stored to the entry
105 in the meantime, xa_release() will do nothing; if instead you
106 want the entry to become ``NULL``, you should use xa_erase().
107 Using xa_insert() on a reserved entry will fail.
108
109 If all entries in the array are ``NULL``, the xa_empty() function
110 will return ``true``.
111
112 Finally, you can remove all entries from an XArray by calling
113 xa_destroy(). If the XArray entries are pointers, you may wish
114 to free the entries first. You can do this by iterating over all present
115 entries in the XArray using the xa_for_each() iterator.
116
117 Search Marks
118 ------------
119
120 Each entry in the array has three bits associated with it called marks.
121 Each mark may be set or cleared independently of the others. You can
122 iterate over marked entries by using the xa_for_each_marked() iterator.
123
124 You can enquire whether a mark is set on an entry by using
125 xa_get_mark(). If the entry is not ``NULL``, you can set a mark on it
126 by using xa_set_mark() and remove the mark from an entry by calling
127 xa_clear_mark(). You can ask whether any entry in the XArray has a
128 particular mark set by calling xa_marked(). Erasing an entry from the
129 XArray causes all marks associated with that entry to be cleared.
130
131 Setting or clearing a mark on any index of a multi-index entry will
132 affect all indices covered by that entry. Querying the mark on any
133 index will return the same result.
134
135 There is no way to iterate over entries which are not marked; the data
136 structure does not allow this to be implemented efficiently. There are
137 not currently iterators to search for logical combinations of bits (eg
138 iterate over all entries which have both ``XA_MARK_1`` and ``XA_MARK_2``
139 set, or iterate over all entries which have ``XA_MARK_0`` or ``XA_MARK_2``
140 set). It would be possible to add these if a user arises.
141
142 Allocating XArrays
143 ------------------
144
145 If you use DEFINE_XARRAY_ALLOC() to define the XArray, or
146 initialise it by passing ``XA_FLAGS_ALLOC`` to xa_init_flags(),
147 the XArray changes to track whether entries are in use or not.
148
149 You can call xa_alloc() to store the entry at an unused index
150 in the XArray. If you need to modify the array from interrupt context,
151 you can use xa_alloc_bh() or xa_alloc_irq() to disable
152 interrupts while allocating the ID.
153
154 Using xa_store(), xa_cmpxchg() or xa_insert() will
155 also mark the entry as being allocated. Unlike a normal XArray, storing
156 ``NULL`` will mark the entry as being in use, like xa_reserve().
157 To free an entry, use xa_erase() (or xa_release() if
158 you only want to free the entry if it's ``NULL``).
159
160 By default, the lowest free entry is allocated starting from 0. If you
161 want to allocate entries starting at 1, it is more efficient to use
162 DEFINE_XARRAY_ALLOC1() or ``XA_FLAGS_ALLOC1``. If you want to
163 allocate IDs up to a maximum, then wrap back around to the lowest free
164 ID, you can use xa_alloc_cyclic().
165
166 You cannot use ``XA_MARK_0`` with an allocating XArray as this mark
167 is used to track whether an entry is free or not. The other marks are
168 available for your use.
169
170 Memory allocation
171 -----------------
172
173 The xa_store(), xa_cmpxchg(), xa_alloc(),
174 xa_reserve() and xa_insert() functions take a gfp_t
175 parameter in case the XArray needs to allocate memory to store this entry.
176 If the entry is being deleted, no memory allocation needs to be performed,
177 and the GFP flags specified will be ignored.
178
179 It is possible for no memory to be allocatable, particularly if you pass
180 a restrictive set of GFP flags. In that case, the functions return a
181 special value which can be turned into an errno using xa_err().
182 If you don't need to know exactly which error occurred, using
183 xa_is_err() is slightly more efficient.
184
185 Locking
186 -------
187
188 When using the Normal API, you do not have to worry about locking.
189 The XArray uses RCU and an internal spinlock to synchronise access:
190
191 No lock needed:
192 * xa_empty()
193 * xa_marked()
194
195 Takes RCU read lock:
196 * xa_load()
197 * xa_for_each()
198 * xa_for_each_start()
199 * xa_for_each_range()
200 * xa_find()
201 * xa_find_after()
202 * xa_extract()
203 * xa_get_mark()
204
205 Takes xa_lock internally:
206 * xa_store()
207 * xa_store_bh()
208 * xa_store_irq()
209 * xa_insert()
210 * xa_insert_bh()
211 * xa_insert_irq()
212 * xa_erase()
213 * xa_erase_bh()
214 * xa_erase_irq()
215 * xa_cmpxchg()
216 * xa_cmpxchg_bh()
217 * xa_cmpxchg_irq()
218 * xa_store_range()
219 * xa_alloc()
220 * xa_alloc_bh()
221 * xa_alloc_irq()
222 * xa_reserve()
223 * xa_reserve_bh()
224 * xa_reserve_irq()
225 * xa_destroy()
226 * xa_set_mark()
227 * xa_clear_mark()
228
229 Assumes xa_lock held on entry:
230 * __xa_store()
231 * __xa_insert()
232 * __xa_erase()
233 * __xa_cmpxchg()
234 * __xa_alloc()
235 * __xa_set_mark()
236 * __xa_clear_mark()
237
238 If you want to take advantage of the lock to protect the data structures
239 that you are storing in the XArray, you can call xa_lock()
240 before calling xa_load(), then take a reference count on the
241 object you have found before calling xa_unlock(). This will
242 prevent stores from removing the object from the array between looking
243 up the object and incrementing the refcount. You can also use RCU to
244 avoid dereferencing freed memory, but an explanation of that is beyond
245 the scope of this document.
246
247 The XArray does not disable interrupts or softirqs while modifying
248 the array. It is safe to read the XArray from interrupt or softirq
249 context as the RCU lock provides enough protection.
250
251 If, for example, you want to store entries in the XArray in process
252 context and then erase them in softirq context, you can do that this way::
253
254 void foo_init(struct foo *foo)
255 {
256 xa_init_flags(&foo->array, XA_FLAGS_LOCK_BH);
257 }
258
259 int foo_store(struct foo *foo, unsigned long index, void *entry)
260 {
261 int err;
262
263 xa_lock_bh(&foo->array);
264 err = xa_err(__xa_store(&foo->array, index, entry, GFP_KERNEL));
265 if (!err)
266 foo->count++;
267 xa_unlock_bh(&foo->array);
268 return err;
269 }
270
271 /* foo_erase() is only called from softirq context */
272 void foo_erase(struct foo *foo, unsigned long index)
273 {
274 xa_lock(&foo->array);
275 __xa_erase(&foo->array, index);
276 foo->count--;
277 xa_unlock(&foo->array);
278 }
279
280 If you are going to modify the XArray from interrupt or softirq context,
281 you need to initialise the array using xa_init_flags(), passing
282 ``XA_FLAGS_LOCK_IRQ`` or ``XA_FLAGS_LOCK_BH``.
283
284 The above example also shows a common pattern of wanting to extend the
285 coverage of the xa_lock on the store side to protect some statistics
286 associated with the array.
287
288 Sharing the XArray with interrupt context is also possible, either
289 using xa_lock_irqsave() in both the interrupt handler and process
290 context, or xa_lock_irq() in process context and xa_lock()
291 in the interrupt handler. Some of the more common patterns have helper
292 functions such as xa_store_bh(), xa_store_irq(),
293 xa_erase_bh(), xa_erase_irq(), xa_cmpxchg_bh()
294 and xa_cmpxchg_irq().
295
296 Sometimes you need to protect access to the XArray with a mutex because
297 that lock sits above another mutex in the locking hierarchy. That does
298 not entitle you to use functions like __xa_erase() without taking
299 the xa_lock; the xa_lock is used for lockdep validation and will be used
300 for other purposes in the future.
301
302 The __xa_set_mark() and __xa_clear_mark() functions are also
303 available for situations where you look up an entry and want to atomically
304 set or clear a mark. It may be more efficient to use the advanced API
305 in this case, as it will save you from walking the tree twice.
306
307 Advanced API
308 ============
309
310 The advanced API offers more flexibility and better performance at the
311 cost of an interface which can be harder to use and has fewer safeguards.
312 No locking is done for you by the advanced API, and you are required
313 to use the xa_lock while modifying the array. You can choose whether
314 to use the xa_lock or the RCU lock while doing read-only operations on
315 the array. You can mix advanced and normal operations on the same array;
316 indeed the normal API is implemented in terms of the advanced API. The
317 advanced API is only available to modules with a GPL-compatible license.
318
319 The advanced API is based around the xa_state. This is an opaque data
320 structure which you declare on the stack using the XA_STATE() macro.
321 This macro initialises the xa_state ready to start walking around the
322 XArray. It is used as a cursor to maintain the position in the XArray
323 and let you compose various operations together without having to restart
324 from the top every time. The contents of the xa_state are protected by
325 the rcu_read_lock() or the xas_lock(). If you need to drop whichever of
326 those locks is protecting your state and tree, you must call xas_pause()
327 so that future calls do not rely on the parts of the state which were
328 left unprotected.
329
330 The xa_state is also used to store errors. You can call
331 xas_error() to retrieve the error. All operations check whether
332 the xa_state is in an error state before proceeding, so there's no need
333 for you to check for an error after each call; you can make multiple
334 calls in succession and only check at a convenient point. The only
335 errors currently generated by the XArray code itself are ``ENOMEM`` and
336 ``EINVAL``, but it supports arbitrary errors in case you want to call
337 xas_set_err() yourself.
338
339 If the xa_state is holding an ``ENOMEM`` error, calling xas_nomem()
340 will attempt to allocate more memory using the specified gfp flags and
341 cache it in the xa_state for the next attempt. The idea is that you take
342 the xa_lock, attempt the operation and drop the lock. The operation
343 attempts to allocate memory while holding the lock, but it is more
344 likely to fail. Once you have dropped the lock, xas_nomem()
345 can try harder to allocate more memory. It will return ``true`` if it
346 is worth retrying the operation (i.e. that there was a memory error *and*
347 more memory was allocated). If it has previously allocated memory, and
348 that memory wasn't used, and there is no error (or some error that isn't
349 ``ENOMEM``), then it will free the memory previously allocated.
350
351 Internal Entries
352 ----------------
353
354 The XArray reserves some entries for its own purposes. These are never
355 exposed through the normal API, but when using the advanced API, it's
356 possible to see them. Usually the best way to handle them is to pass them
357 to xas_retry(), and retry the operation if it returns ``true``.
358
359 .. flat-table::
360 :widths: 1 1 6
361
362 * - Name
363 - Test
364 - Usage
365
366 * - Node
367 - xa_is_node()
368 - An XArray node. May be visible when using a multi-index xa_state.
369
370 * - Sibling
371 - xa_is_sibling()
372 - A non-canonical entry for a multi-index entry. The value indicates
373 which slot in this node has the canonical entry.
374
375 * - Retry
376 - xa_is_retry()
377 - This entry is currently being modified by a thread which has the
378 xa_lock. The node containing this entry may be freed at the end
379 of this RCU period. You should restart the lookup from the head
380 of the array.
381
382 * - Zero
383 - xa_is_zero()
384 - Zero entries appear as ``NULL`` through the Normal API, but occupy
385 an entry in the XArray which can be used to reserve the index for
386 future use. This is used by allocating XArrays for allocated entries
387 which are ``NULL``.
388
389 Other internal entries may be added in the future. As far as possible, they
390 will be handled by xas_retry().
391
392 Additional functionality
393 ------------------------
394
395 The xas_create_range() function allocates all the necessary memory
396 to store every entry in a range. It will set ENOMEM in the xa_state if
397 it cannot allocate memory.
398
399 You can use xas_init_marks() to reset the marks on an entry
400 to their default state. This is usually all marks clear, unless the
401 XArray is marked with ``XA_FLAGS_TRACK_FREE``, in which case mark 0 is set
402 and all other marks are clear. Replacing one entry with another using
403 xas_store() will not reset the marks on that entry; if you want
404 the marks reset, you should do that explicitly.
405
406 The xas_load() will walk the xa_state as close to the entry
407 as it can. If you know the xa_state has already been walked to the
408 entry and need to check that the entry hasn't changed, you can use
409 xas_reload() to save a function call.
410
411 If you need to move to a different index in the XArray, call
412 xas_set(). This resets the cursor to the top of the tree, which
413 will generally make the next operation walk the cursor to the desired
414 spot in the tree. If you want to move to the next or previous index,
415 call xas_next() or xas_prev(). Setting the index does
416 not walk the cursor around the array so does not require a lock to be
417 held, while moving to the next or previous index does.
418
419 You can search for the next present entry using xas_find(). This
420 is the equivalent of both xa_find() and xa_find_after();
421 if the cursor has been walked to an entry, then it will find the next
422 entry after the one currently referenced. If not, it will return the
423 entry at the index of the xa_state. Using xas_next_entry() to
424 move to the next present entry instead of xas_find() will save
425 a function call in the majority of cases at the expense of emitting more
426 inline code.
427
428 The xas_find_marked() function is similar. If the xa_state has
429 not been walked, it will return the entry at the index of the xa_state,
430 if it is marked. Otherwise, it will return the first marked entry after
431 the entry referenced by the xa_state. The xas_next_marked()
432 function is the equivalent of xas_next_entry().
433
434 When iterating over a range of the XArray using xas_for_each()
435 or xas_for_each_marked(), it may be necessary to temporarily stop
436 the iteration. The xas_pause() function exists for this purpose.
437 After you have done the necessary work and wish to resume, the xa_state
438 is in an appropriate state to continue the iteration after the entry
439 you last processed. If you have interrupts disabled while iterating,
440 then it is good manners to pause the iteration and reenable interrupts
441 every ``XA_CHECK_SCHED`` entries.
442
443 The xas_get_mark(), xas_set_mark() and xas_clear_mark() functions require
444 the xa_state cursor to have been moved to the appropriate location in the
445 XArray; they will do nothing if you have called xas_pause() or xas_set()
446 immediately before.
447
448 You can call xas_set_update() to have a callback function
449 called each time the XArray updates a node. This is used by the page
450 cache workingset code to maintain its list of nodes which contain only
451 shadow entries.
452
453 Multi-Index Entries
454 -------------------
455
456 The XArray has the ability to tie multiple indices together so that
457 operations on one index affect all indices. For example, storing into
458 any index will change the value of the entry retrieved from any index.
459 Setting or clearing a mark on any index will set or clear the mark
460 on every index that is tied together. The current implementation
461 only allows tying ranges which are aligned powers of two together;
462 eg indices 64-127 may be tied together, but 2-6 may not be. This may
463 save substantial quantities of memory; for example tying 512 entries
464 together will save over 4kB.
465
466 You can create a multi-index entry by using XA_STATE_ORDER()
467 or xas_set_order() followed by a call to xas_store().
468 Calling xas_load() with a multi-index xa_state will walk the
469 xa_state to the right location in the tree, but the return value is not
470 meaningful, potentially being an internal entry or ``NULL`` even when there
471 is an entry stored within the range. Calling xas_find_conflict()
472 will return the first entry within the range or ``NULL`` if there are no
473 entries in the range. The xas_for_each_conflict() iterator will
474 iterate over every entry which overlaps the specified range.
475
476 If xas_load() encounters a multi-index entry, the xa_index
477 in the xa_state will not be changed. When iterating over an XArray
478 or calling xas_find(), if the initial index is in the middle
479 of a multi-index entry, it will not be altered. Subsequent calls
480 or iterations will move the index to the first index in the range.
481 Each entry will only be returned once, no matter how many indices it
482 occupies.
483
484 Using xas_next() or xas_prev() with a multi-index xa_state is not
485 supported. Using either of these functions on a multi-index entry will
486 reveal sibling entries; these should be skipped over by the caller.
487
488 Storing ``NULL`` into any index of a multi-index entry will set the
489 entry at every index to ``NULL`` and dissolve the tie. A multi-index
490 entry can be split into entries occupying smaller ranges by calling
491 xas_split_alloc() without the xa_lock held, followed by taking the lock
492 and calling xas_split() or calling xas_try_split() with xa_lock. The
493 difference between xas_split_alloc()+xas_split() and xas_try_alloc() is
494 that xas_split_alloc() + xas_split() split the entry from the original
495 order to the new order in one shot uniformly, whereas xas_try_split()
496 iteratively splits the entry containing the index non-uniformly.
497 For example, to split an order-9 entry, which takes 2^(9-6)=8 slots,
498 assuming ``XA_CHUNK_SHIFT`` is 6, xas_split_alloc() + xas_split() need
499 8 xa_node. xas_try_split() splits the order-9 entry into
500 2 order-8 entries, then split one order-8 entry, based on the given index,
501 to 2 order-7 entries, ..., and split one order-1 entry to 2 order-0 entries.
502 When splitting the order-6 entry and a new xa_node is needed, xas_try_split()
503 will try to allocate one if possible. As a result, xas_try_split() would only
504 need 1 xa_node instead of 8.
505
506 Functions and structures
507 ========================
508
509 .. kernel-doc:: include/linux/xarray.h
510 .. kernel-doc:: lib/xarray.c
511

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

XArray

1-8
.. SPDX-License-Identifier: GPL-2.0+

XArray

저자: Matthew Wilcox

개요

9-58

개요

XArray는 매우 큰 pointer 배열처럼 동작하는 추상 자료형입니다. Hash나 일반적인 크기 조절 가능 배열과 같은 요구를 많이 충족합니다. Hash와 달리 cache 효율적인 방식으로 다음 또는 이전 entry로 이동할 수 있습니다. 크기 조절 가능 배열과 달리 배열을 확장하기 위해 data를 복사하거나 MMU mapping을 변경할 필요가 없습니다. Doubly-linked list보다 memory 효율적이고 병렬화하기 쉬우며 cache 친화적입니다. RCU를 활용하여 lock 없이 lookup을 수행합니다.

XArray 구현은 사용하는 index가 조밀하게 모여 있을 때 효율적입니다. Object를 hashing하고 그 hash를 index로 사용하면 성능이 좋지 않습니다. XArray는 작은 index에 최적화되어 있지만 큰 index에서도 좋은 성능을 냅니다. Index가 `ULONG_MAX`보다 클 수 있다면 XArray는 적합한 자료형이 아닙니다. XArray의 가장 중요한 사용자는 page cache입니다.

일반 pointer는 XArray에 직접 저장할 수 있습니다. Pointer는 4-byte alignment를 만족해야 하며, `kmalloc()`과 `alloc_page()`가 반환하는 모든 pointer가 이에 해당합니다. 임의의 user-space pointer나 function pointer에는 이 조건이 성립하지 않습니다. 정적으로 할당한 object도 alignment가 최소 4라면 pointer를 저장할 수 있습니다.

0부터 `LONG_MAX` 사이의 integer도 XArray에 저장할 수 있습니다. 먼저 `xa_mk_value()`를 사용해 entry로 변환해야 합니다. XArray에서 entry를 가져온 뒤 `xa_is_value()`로 value entry인지 검사하고 `xa_to_value()`로 다시 integer로 변환할 수 있습니다.

XArray에 저장하는 pointer에 tag를 붙여야 하는 사용자도 있습니다. `xa_tag_pointer()`는 tag가 있는 entry를 만들고, `xa_untag_pointer()`는 tagged entry를 tag가 없는 pointer로 되돌리며, `xa_pointer_tag()`는 entry의 tag를 가져옵니다. Tagged pointer는 value entry와 일반 pointer를 구분하는 bit를 함께 사용하므로, 특정 XArray에는 value entry와 tagged pointer 중 어느 것을 저장할지 결정해야 합니다.

일부 `IS_ERR()` pointer는 value entry 또는 internal entry와 충돌하므로 XArray는 `IS_ERR()` pointer 저장을 지원하지 않습니다.

XArray의 특이한 기능 중 하나는 일정한 index 범위를 차지하는 entry를 만들 수 있다는 점입니다. 저장 후에는 범위 안의 어느 index를 lookup해도 다른 index와 같은 entry를 반환하며, 어느 index에 저장하더라도 전체 범위에 저장됩니다. Multi-index entry는 명시적으로 더 작은 entry로 분할할 수 있습니다. `xa_erase()` 또는 `NULL`을 전달한 `xa_store()`로 어느 entry든 해제하면 XArray는 그 범위 정보를 잊습니다.

일반 API

59-116

일반 API

정적으로 할당한 XArray는 `DEFINE_XARRAY()`로, 동적으로 할당한 XArray는 `xa_init()`으로 초기화합니다. 새로 초기화한 XArray의 모든 index에는 `NULL` pointer가 들어 있습니다.

`xa_store()`로 entry를 설정하고 `xa_load()`로 가져옵니다. `xa_store()`는 index의 기존 entry를 새 entry로 덮어쓰고 이전 entry를 반환합니다. `xa_erase()`를 사용하거나 `xa_store()`로 `NULL`을 저장하여 entry를 해제할 수 있습니다. 한 번도 저장하지 않은 entry, `xa_erase()`로 지운 entry, 가장 최근에 `NULL`을 저장한 entry는 동일합니다. 단, XArray를 `XA_FLAGS_ALLOC`으로 초기화한 경우는 예외입니다.

`xa_cmpxchg()`를 사용하면 index의 entry를 조건부로 교체할 수 있습니다. `cmpxchg()`와 마찬가지로 해당 entry가 `old` 값일 때만 성공합니다. 이 함수는 그 index에 있던 entry도 반환하며, 전달한 `old`와 같은 entry가 반환되면 `xa_cmpxchg()`가 성공한 것입니다.

현재 index의 entry가 `NULL`일 때만 새 entry를 저장하려면 `xa_insert()`를 사용합니다. Entry가 비어 있지 않으면 `-EBUSY`를 반환합니다.

`xa_extract()`를 호출하면 XArray의 entry를 일반 배열로 복사할 수 있습니다. `xa_for_each()`, `xa_for_each_start()`, `xa_for_each_range()`로 현재 존재하는 entry를 순회할 수도 있습니다. 다음 present entry로 이동할 때는 `xa_find()` 또는 `xa_find_after()`가 더 알맞을 수 있습니다.

`xa_store_range()`는 일정한 index 범위에 같은 entry를 저장합니다. 이때 일부 다른 operation은 다소 특이하게 동작합니다. 예를 들어 한 index의 entry에 mark를 설정하면 다른 index 중 일부에만 mark가 설정될 수 있고, 한 index에 저장하면 다른 index 중 일부에서 가져오는 entry만 바뀔 수 있습니다.

이후의 `xa_store()`가 memory를 할당하지 않도록 보장해야 할 때가 있습니다. `xa_reserve()`는 지정한 index에 reserved entry를 저장하며 일반 API 사용자는 이를 `NULL`로 봅니다. Reserved entry가 필요 없어지면 `xa_release()`로 사용하지 않은 entry를 제거합니다. 그 사이 다른 사용자가 entry에 저장했다면 `xa_release()`는 아무것도 하지 않습니다. Entry를 반드시 `NULL`로 만들려면 `xa_erase()`를 사용해야 합니다. Reserved entry에 대한 `xa_insert()`는 실패합니다.

배열의 모든 entry가 `NULL`이면 `xa_empty()`가 `true`를 반환합니다.

마지막으로 `xa_destroy()`를 호출하면 XArray의 모든 entry를 제거할 수 있습니다. Entry가 pointer라면 먼저 해제해야 할 수 있으며, `xa_for_each()` iterator로 모든 present entry를 순회하여 처리할 수 있습니다.

검색 mark

117-141

검색 mark

배열의 각 entry에는 mark라고 부르는 bit 세 개가 연결됩니다. 각 mark는 서로 독립적으로 설정하거나 지울 수 있습니다. `xa_for_each_marked()` iterator로 mark가 설정된 entry를 순회할 수 있습니다.

`xa_get_mark()`로 entry에 mark가 설정되었는지 확인합니다. Entry가 `NULL`이 아니면 `xa_set_mark()`로 mark를 설정하고 `xa_clear_mark()`로 제거할 수 있습니다. `xa_marked()`는 특정 mark가 설정된 entry가 XArray에 하나라도 있는지 확인합니다. XArray에서 entry를 지우면 그 entry와 연결된 모든 mark도 지워집니다.

Multi-index entry의 어느 index에서 mark를 설정하거나 지우더라도 그 entry가 포함하는 모든 index에 영향을 줍니다. 어느 index에서 mark를 조회해도 같은 결과가 반환됩니다.

Mark가 없는 entry를 순회하는 방법은 없습니다. 이 자료 구조로는 이를 효율적으로 구현할 수 없기 때문입니다. 현재는 `XA_MARK_1`과 `XA_MARK_2`가 모두 설정된 entry 또는 `XA_MARK_0`이나 `XA_MARK_2`가 설정된 entry처럼 bit의 논리 조합을 검색하는 iterator도 없습니다. 사용 사례가 생기면 추가할 수 있습니다.

할당형 XArray

142-169

할당형 XArray

`DEFINE_XARRAY_ALLOC()`으로 XArray를 정의하거나 `XA_FLAGS_ALLOC`을 `xa_init_flags()`에 전달해 초기화하면, XArray는 entry가 사용 중인지 추적하는 방식으로 바뀝니다.

`xa_alloc()`은 XArray의 사용하지 않는 index에 entry를 저장합니다. Interrupt context에서 배열을 수정해야 한다면 ID를 할당하는 동안 interrupt를 비활성화하는 `xa_alloc_bh()` 또는 `xa_alloc_irq()`를 사용할 수 있습니다.

`xa_store()`, `xa_cmpxchg()`, `xa_insert()`도 entry를 allocated 상태로 표시합니다. 일반 XArray와 달리 `NULL` 저장도 `xa_reserve()`처럼 entry를 사용 중으로 표시합니다. Entry를 해제하려면 `xa_erase()`를 사용하고, entry가 `NULL`인 경우에만 해제하려면 `xa_release()`를 사용합니다.

기본적으로 가장 낮은 free entry를 0부터 할당합니다. 1부터 할당하려면 `DEFINE_XARRAY_ALLOC1()` 또는 `XA_FLAGS_ALLOC1`을 사용하는 편이 효율적입니다. 최대값까지 ID를 할당한 뒤 가장 낮은 free ID로 되돌아가려면 `xa_alloc_cyclic()`을 사용합니다.

할당형 XArray에서는 `XA_MARK_0`이 entry의 free 여부를 추적하는 데 사용되므로 사용자가 쓸 수 없습니다. 나머지 mark는 사용할 수 있습니다.

Memory 할당

170-184

Memory 할당

`xa_store()`, `xa_cmpxchg()`, `xa_alloc()`, `xa_reserve()`, `xa_insert()`는 entry 저장에 memory가 필요할 때를 위해 `gfp_t` parameter를 받습니다. Entry를 삭제하는 경우에는 memory를 할당할 필요가 없으므로 지정한 GFP flag를 무시합니다.

특히 제한적인 GFP flag를 전달하면 memory를 전혀 할당할 수 없을 수 있습니다. 이때 함수들은 `xa_err()`로 errno로 변환할 수 있는 특별한 값을 반환합니다. 정확히 어떤 error인지 알 필요가 없다면 `xa_is_err()`가 조금 더 효율적입니다.

Locking 분류

185-237

Locking

일반 API를 사용할 때는 locking을 직접 처리할 필요가 없습니다. XArray는 RCU와 내부 spinlock으로 access를 동기화합니다.

Lock이 필요 없는 함수:

  • xa_empty()
  • xa_marked()

RCU read lock을 획득하는 함수와 iterator:

  • xa_load()
  • xa_for_each()
  • xa_for_each_start()
  • xa_for_each_range()
  • xa_find()
  • xa_find_after()
  • xa_extract()
  • xa_get_mark()

내부에서 `xa_lock`을 획득하는 함수:

  • xa_store()
  • xa_store_bh()
  • xa_store_irq()
  • xa_insert()
  • xa_insert_bh()
  • xa_insert_irq()
  • xa_erase()
  • xa_erase_bh()
  • xa_erase_irq()
  • xa_cmpxchg()
  • xa_cmpxchg_bh()
  • xa_cmpxchg_irq()
  • xa_store_range()
  • xa_alloc()
  • xa_alloc_bh()
  • xa_alloc_irq()
  • xa_reserve()
  • xa_reserve_bh()
  • xa_reserve_irq()
  • xa_destroy()
  • xa_set_mark()
  • xa_clear_mark()

진입 시 `xa_lock`이 이미 잡혀 있다고 가정하는 함수:

  • __xa_store()
  • __xa_insert()
  • __xa_erase()
  • __xa_cmpxchg()
  • __xa_alloc()
  • __xa_set_mark()
  • __xa_clear_mark()

Locking 사용 방법

238-306

XArray에 저장한 자료 구조까지 lock으로 보호하려면 `xa_load()` 전에 `xa_lock()`을 호출하고, 찾은 object의 reference count를 획득한 뒤 `xa_unlock()`을 호출할 수 있습니다. 그러면 object lookup과 refcount 증가 사이에 store가 배열에서 object를 제거하지 못합니다. RCU로 해제된 memory의 dereference를 피할 수도 있지만 이는 이 문서의 범위를 벗어납니다.

XArray는 배열을 수정하는 동안 interrupt나 softirq를 비활성화하지 않습니다. RCU lock이 충분한 보호를 제공하므로 interrupt 또는 softirq context에서 XArray를 읽어도 안전합니다.

예를 들어 process context에서 XArray에 entry를 저장하고 softirq context에서 지우려면 다음과 같이 구현할 수 있습니다.

void foo_init(struct foo *foo)
{
    xa_init_flags(&foo->array, XA_FLAGS_LOCK_BH);
}

int foo_store(struct foo *foo, unsigned long index, void *entry)
{
    int err;

    xa_lock_bh(&foo->array);
    err = xa_err(__xa_store(&foo->array, index, entry, GFP_KERNEL));
    if (!err)
        foo->count++;
    xa_unlock_bh(&foo->array);
    return err;
}

/* foo_erase() is only called from softirq context */
void foo_erase(struct foo *foo, unsigned long index)
{
    xa_lock(&foo->array);
    __xa_erase(&foo->array, index);
    foo->count--;
    xa_unlock(&foo->array);
}

Interrupt 또는 softirq context에서 XArray를 수정한다면 `xa_init_flags()`에 `XA_FLAGS_LOCK_IRQ` 또는 `XA_FLAGS_LOCK_BH`를 전달하여 배열을 초기화해야 합니다.

위 예제는 배열과 연결된 통계를 보호하기 위해 store 쪽에서 `xa_lock`의 보호 범위를 확장하는 일반적인 pattern도 보여 줍니다.

XArray를 interrupt context와 공유할 수도 있습니다. Interrupt handler와 process context 양쪽에서 `xa_lock_irqsave()`를 사용하거나, process context에서 `xa_lock_irq()`를 사용하고 interrupt handler에서 `xa_lock()`을 사용합니다. 흔한 pattern에는 `xa_store_bh()`, `xa_store_irq()`, `xa_erase_bh()`, `xa_erase_irq()`, `xa_cmpxchg_bh()`, `xa_cmpxchg_irq()` 같은 helper가 제공됩니다.

Locking hierarchy에서 다른 mutex 위에 있는 lock 때문에 mutex로 XArray access를 보호해야 할 때도 있습니다. 그렇더라도 `xa_lock` 없이 `__xa_erase()` 같은 함수를 사용할 수 있는 것은 아닙니다. `xa_lock`은 lockdep validation에 쓰이며 앞으로 다른 목적으로도 사용될 예정입니다.

`__xa_set_mark()`와 `__xa_clear_mark()`는 entry를 lookup한 뒤 mark를 원자적으로 설정하거나 지울 때도 사용할 수 있습니다. 이 경우 advanced API를 사용하면 tree를 두 번 순회하지 않아도 되어 더 효율적일 수 있습니다.

고급 API

307-350

고급 API

고급 API는 사용하기 더 어렵고 보호 장치가 적은 대신 더 높은 유연성과 성능을 제공합니다. 고급 API는 locking을 대신 수행하지 않으므로 배열을 수정할 때 `xa_lock`을 사용해야 합니다. Read-only operation에는 `xa_lock` 또는 RCU lock 중 하나를 선택할 수 있습니다. 같은 배열에서 고급 operation과 일반 operation을 섞어 쓸 수 있으며 실제로 일반 API는 고급 API로 구현됩니다. 고급 API는 GPL-compatible license를 가진 module에만 제공됩니다.

고급 API는 `xa_state`를 중심으로 구성됩니다. 이는 `XA_STATE()` macro를 사용해 stack에 선언하는 opaque 자료 구조입니다. Macro는 XArray 순회를 시작할 준비가 된 `xa_state`를 초기화합니다. `xa_state`는 XArray 안의 위치를 유지하는 cursor로 사용되므로 매번 root부터 다시 시작하지 않고 여러 operation을 조합할 수 있습니다. 내용은 `rcu_read_lock()` 또는 `xas_lock()`으로 보호합니다. State와 tree를 보호하는 lock을 내려놓아야 한다면, 이후 호출이 보호되지 않은 state 부분에 의존하지 않도록 `xas_pause()`를 호출해야 합니다.

`xa_state`는 error 저장에도 사용됩니다. `xas_error()`로 error를 가져옵니다. 모든 operation은 진행 전에 `xa_state`의 error 상태를 검사하므로 호출마다 error를 확인할 필요 없이 여러 호출을 연속 수행한 뒤 편리한 지점에서 한 번 검사할 수 있습니다. 현재 XArray code가 직접 생성하는 error는 `ENOMEM`과 `EINVAL`뿐이지만, 사용자가 `xas_set_err()`를 호출하는 경우를 위해 임의의 error를 지원합니다.

`xa_state`가 `ENOMEM` error를 보유하고 있을 때 `xas_nomem()`을 호출하면 지정한 GFP flag로 memory를 더 할당하여 다음 시도를 위해 `xa_state`에 cache합니다. 의도한 흐름은 `xa_lock`을 잡고 operation을 시도한 뒤 lock을 내려놓는 것입니다. Lock을 잡은 채 memory 할당을 시도하면 실패하기 쉽지만, lock을 내려놓은 뒤 `xas_nomem()`은 더 적극적으로 할당할 수 있습니다. Memory error가 있었고 추가 memory를 할당하여 재시도할 가치가 있으면 `true`를 반환합니다. 이전에 할당한 memory가 사용되지 않았고 error가 없거나 `ENOMEM`이 아닌 error라면 그 memory를 해제합니다.

내부 entry

351-391

내부 entry

XArray는 자체 용도를 위해 일부 entry를 예약합니다. 일반 API에는 노출되지 않지만 고급 API를 사용하면 볼 수 있습니다. 보통은 이를 `xas_retry()`에 전달하고, 함수가 `true`를 반환하면 operation을 재시도하는 방식이 가장 좋습니다.

이름검사용도
Nodexa_is_node()XArray node입니다. Multi-index xa_state를 사용할 때 보일 수 있습니다.
Siblingxa_is_sibling()Multi-index entry의 non-canonical entry입니다. 값은 이 node에서 canonical entry가 있는 slot을 나타냅니다.
Retryxa_is_retry()`xa_lock`을 보유한 thread가 현재 수정 중인 entry입니다. 이 entry를 포함한 node는 현재 RCU period가 끝날 때 해제될 수 있으므로 array head부터 lookup을 다시 시작해야 합니다.
Zeroxa_is_zero()일반 API에서는 `NULL`로 보이지만 XArray 안의 entry를 차지하여 이후 사용을 위해 index를 예약합니다. 할당형 XArray에서 값이 `NULL`인 allocated entry에 사용됩니다.

앞으로 다른 internal entry가 추가될 수 있습니다. 가능한 한 `xas_retry()`가 이를 처리할 것입니다.

추가 기능

392-452

추가 기능

`xas_create_range()`는 범위의 모든 entry를 저장하는 데 필요한 memory를 할당합니다. Memory를 할당할 수 없으면 `xa_state`에 `ENOMEM`을 설정합니다.

`xas_init_marks()`는 entry의 mark를 기본 상태로 reset합니다. 보통 모든 mark가 clear된 상태이지만 XArray에 `XA_FLAGS_TRACK_FREE`가 설정되어 있으면 mark 0은 set되고 나머지는 clear됩니다. `xas_store()`로 entry를 교체해도 mark가 reset되지 않으므로 필요하다면 명시적으로 reset해야 합니다.

`xas_load()`는 가능한 한 entry에 가까운 위치까지 `xa_state`를 순회합니다. 이미 해당 entry까지 순회했고 entry가 바뀌지 않았는지 확인해야 한다면 `xas_reload()`로 function call 하나를 줄일 수 있습니다.

XArray의 다른 index로 이동하려면 `xas_set()`을 호출합니다. 이 함수는 cursor를 tree root로 reset하며 보통 다음 operation이 원하는 위치까지 cursor를 순회하게 합니다. 다음 또는 이전 index로 이동하려면 `xas_next()` 또는 `xas_prev()`를 호출합니다. Index 설정 자체는 cursor를 순회하지 않아 lock이 필요 없지만 다음이나 이전 index로 이동할 때는 lock이 필요합니다.

`xas_find()`로 다음 present entry를 검색할 수 있습니다. 이는 `xa_find()`와 `xa_find_after()`의 역할을 모두 수행합니다. Cursor가 이미 entry까지 순회했다면 현재 참조하는 entry 다음의 entry를 찾고, 그렇지 않으면 `xa_state`의 index에 있는 entry를 반환합니다. `xas_find()` 대신 `xas_next_entry()`로 다음 present entry로 이동하면 inline code가 늘어나는 대신 대부분의 경우 function call 하나를 줄일 수 있습니다.

`xas_find_marked()`도 비슷합니다. `xa_state`가 아직 순회되지 않았고 해당 index의 entry에 mark가 있으면 그 entry를 반환합니다. 이미 순회했다면 현재 참조하는 entry 다음의 첫 marked entry를 반환합니다. `xas_next_marked()`는 `xas_next_entry()`에 대응합니다.

`xas_for_each()` 또는 `xas_for_each_marked()`로 XArray 범위를 순회할 때 일시적으로 순회를 멈춰야 할 수 있습니다. 이를 위해 `xas_pause()`를 사용합니다. 필요한 작업을 마치고 재개하면 `xa_state`는 마지막으로 처리한 entry 다음부터 계속할 수 있는 상태입니다. Interrupt를 비활성화한 채 순회한다면 `XA_CHECK_SCHED`개 entry마다 순회를 멈추고 interrupt를 다시 활성화하는 것이 좋습니다.

`xas_get_mark()`, `xas_set_mark()`, `xas_clear_mark()`는 `xa_state` cursor가 XArray의 적절한 위치로 이동해 있어야 합니다. 바로 전에 `xas_pause()` 또는 `xas_set()`을 호출했다면 아무 작업도 하지 않습니다.

`xas_set_update()`를 호출하면 XArray가 node를 갱신할 때마다 callback function을 호출하게 할 수 있습니다. Page cache workingset code는 이를 사용해 shadow entry만 포함한 node 목록을 유지합니다.

Multi-index entry

453-505

Multi-index entry

XArray는 여러 index를 묶어 한 index의 operation이 모든 index에 영향을 주게 할 수 있습니다. 어느 index에 저장해도 모든 index에서 가져오는 entry 값이 바뀌며, 어느 index에서 mark를 설정하거나 지워도 묶인 모든 index의 mark가 함께 바뀝니다. 현재 구현은 alignment가 맞는 power-of-two 범위만 묶을 수 있습니다. 예를 들어 64-127은 묶을 수 있지만 2-6은 묶을 수 없습니다. 이렇게 하면 상당한 memory를 절약할 수 있으며 512개 entry를 묶으면 4kB 넘게 줄일 수 있습니다.

`XA_STATE_ORDER()`를 사용하거나 `xas_set_order()` 뒤에 `xas_store()`를 호출하여 multi-index entry를 만듭니다. Multi-index `xa_state`로 `xas_load()`를 호출하면 tree의 올바른 위치까지 이동하지만 반환값은 의미가 없으며, 범위 안에 entry가 있어도 internal entry 또는 `NULL`일 수 있습니다. `xas_find_conflict()`는 범위 안의 첫 entry를 반환하고 entry가 없으면 `NULL`을 반환합니다. `xas_for_each_conflict()` iterator는 지정 범위와 겹치는 모든 entry를 순회합니다.

`xas_load()`가 multi-index entry를 만나도 `xa_state`의 `xa_index`는 바뀌지 않습니다. XArray를 순회하거나 `xas_find()`를 호출할 때 초기 index가 multi-index entry 중간에 있으면 그대로 유지됩니다. 이후 호출이나 순회에서는 index가 범위의 첫 index로 이동합니다. Entry가 차지하는 index 수와 관계없이 각 entry는 한 번만 반환됩니다.

Multi-index `xa_state`에 `xas_next()` 또는 `xas_prev()`를 사용하는 것은 지원되지 않습니다. Multi-index entry에 이 함수를 사용하면 sibling entry가 노출되므로 호출자가 건너뛰어야 합니다.

Multi-index entry의 어느 index에든 `NULL`을 저장하면 모든 index의 entry가 `NULL`이 되고 묶음이 해제됩니다. 더 작은 범위의 entry로 나누려면 `xa_lock` 없이 `xas_split_alloc()`을 호출한 뒤 lock을 잡고 `xas_split()`을 호출하거나, `xa_lock`을 잡은 채 `xas_try_split()`을 호출합니다. 원문의 비교 대상인 `xas_split_alloc()+xas_split()`과 `xas_try_alloc()` 가운데 전자는 원래 order에서 새 order로 한 번에 균일하게 분할하고, `xas_try_split()`은 지정 index를 포함한 entry를 반복적으로 비균일 분할합니다. `XA_CHUNK_SHIFT`가 6일 때 order-9 entry는 2^(9-6)=8 slot을 차지하므로 전자는 xa_node 8개가 필요합니다. `xas_try_split()`은 order-9 entry를 order-8 두 개로 나눈 다음 지정 index에 따라 그중 하나를 order-7 두 개로 나누는 과정을 order-0까지 반복합니다. Order-6 entry 분할에 새 xa_node가 필요하면 가능한 경우 하나를 할당하므로, 결과적으로 xa_node 8개 대신 1개만 필요합니다.

함수와 구조체

506-510

함수와 구조체

XArray API와 구현의 kernel-doc reference는 다음 source에 있습니다.

.. kernel-doc:: include/linux/xarray.h
.. kernel-doc:: lib/xarray.c