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

Linux 6.18.37 · Core API

Workqueue

Concurrency-managed workqueue의 worker-pool 설계, allocation flag와 max_active, unbound affinity scope, 성능 tradeoff, monitoring 및 debugging을 설명합니다.

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

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

1. 요약·해설

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

요약과 해설

workqueue.rst:1-789

Cmwq는 모든 workqueue가 per-CPU worker pool을 공유하게 하여 기존 MT 및 ST workqueue의 thread 낭비와 제한된 concurrency 문제를 해결합니다. Scheduler feedback으로 필요한 만큼의 worker만 runnable 상태로 유지합니다.

`WQ_MEM_RECLAIM`은 memory pressure에서도 forward progress를 보장하고, `WQ_UNBOUND`와 affinity scope는 locality와 system utilization 사이의 균형을 조절합니다. Strict scope는 isolation을 높이지만 work conservation을 크게 잃을 수 있습니다.

일반적으로 `@max_active`는 0을 사용하고 strict ordering에는 `alloc_ordered_workqueue()`를 사용합니다. CPU를 많이 소비하는 workqueue는 `apply_workqueue_attrs()` 또는 `WQ_SYSFS`로 실제 workload에 맞춰야 합니다.

`wq_dump.py`와 `wq_monitor.py`, workqueue tracepoint 및 worker stack으로 configuration, saturation, rapid requeue, CPU-hogging item을 진단할 수 있습니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 =========
2 Workqueue
3 =========
4
5 :Date: September, 2010
6 :Author: Tejun Heo <[email protected]>
7 :Author: Florian Mickler <[email protected]>
8
9
10 Introduction
11 ============
12
13 There are many cases where an asynchronous process execution context
14 is needed and the workqueue (wq) API is the most commonly used
15 mechanism for such cases.
16
17 When such an asynchronous execution context is needed, a work item
18 describing which function to execute is put on a queue. An
19 independent thread serves as the asynchronous execution context. The
20 queue is called workqueue and the thread is called worker.
21
22 While there are work items on the workqueue the worker executes the
23 functions associated with the work items one after the other. When
24 there is no work item left on the workqueue the worker becomes idle.
25 When a new work item gets queued, the worker begins executing again.
26
27
28 Why Concurrency Managed Workqueue?
29 ==================================
30
31 In the original wq implementation, a multi threaded (MT) wq had one
32 worker thread per CPU and a single threaded (ST) wq had one worker
33 thread system-wide. A single MT wq needed to keep around the same
34 number of workers as the number of CPUs. The kernel grew a lot of MT
35 wq users over the years and with the number of CPU cores continuously
36 rising, some systems saturated the default 32k PID space just booting
37 up.
38
39 Although MT wq wasted a lot of resource, the level of concurrency
40 provided was unsatisfactory. The limitation was common to both ST and
41 MT wq albeit less severe on MT. Each wq maintained its own separate
42 worker pool. An MT wq could provide only one execution context per CPU
43 while an ST wq one for the whole system. Work items had to compete for
44 those very limited execution contexts leading to various problems
45 including proneness to deadlocks around the single execution context.
46
47 The tension between the provided level of concurrency and resource
48 usage also forced its users to make unnecessary tradeoffs like libata
49 choosing to use ST wq for polling PIOs and accepting an unnecessary
50 limitation that no two polling PIOs can progress at the same time. As
51 MT wq don't provide much better concurrency, users which require
52 higher level of concurrency, like async or fscache, had to implement
53 their own thread pool.
54
55 Concurrency Managed Workqueue (cmwq) is a reimplementation of wq with
56 focus on the following goals.
57
58 * Maintain compatibility with the original workqueue API.
59
60 * Use per-CPU unified worker pools shared by all wq to provide
61 flexible level of concurrency on demand without wasting a lot of
62 resource.
63
64 * Automatically regulate worker pool and level of concurrency so that
65 the API users don't need to worry about such details.
66
67
68 The Design
69 ==========
70
71 In order to ease the asynchronous execution of functions a new
72 abstraction, the work item, is introduced.
73
74 A work item is a simple struct that holds a pointer to the function
75 that is to be executed asynchronously. Whenever a driver or subsystem
76 wants a function to be executed asynchronously it has to set up a work
77 item pointing to that function and queue that work item on a
78 workqueue.
79
80 A work item can be executed in either a thread or the BH (softirq) context.
81
82 For threaded workqueues, special purpose threads, called [k]workers, execute
83 the functions off of the queue, one after the other. If no work is queued,
84 the worker threads become idle. These worker threads are managed in
85 worker-pools.
86
87 The cmwq design differentiates between the user-facing workqueues that
88 subsystems and drivers queue work items on and the backend mechanism
89 which manages worker-pools and processes the queued work items.
90
91 There are two worker-pools, one for normal work items and the other
92 for high priority ones, for each possible CPU and some extra
93 worker-pools to serve work items queued on unbound workqueues - the
94 number of these backing pools is dynamic.
95
96 BH workqueues use the same framework. However, as there can only be one
97 concurrent execution context, there's no need to worry about concurrency.
98 Each per-CPU BH worker pool contains only one pseudo worker which represents
99 the BH execution context. A BH workqueue can be considered a convenience
100 interface to softirq.
101
102 Subsystems and drivers can create and queue work items through special
103 workqueue API functions as they see fit. They can influence some
104 aspects of the way the work items are executed by setting flags on the
105 workqueue they are putting the work item on. These flags include
106 things like CPU locality, concurrency limits, priority and more. To
107 get a detailed overview refer to the API description of
108 ``alloc_workqueue()`` below.
109
110 When a work item is queued to a workqueue, the target worker-pool is
111 determined according to the queue parameters and workqueue attributes
112 and appended on the shared worklist of the worker-pool. For example,
113 unless specifically overridden, a work item of a bound workqueue will
114 be queued on the worklist of either normal or highpri worker-pool that
115 is associated to the CPU the issuer is running on.
116
117 For any thread pool implementation, managing the concurrency level
118 (how many execution contexts are active) is an important issue. cmwq
119 tries to keep the concurrency at a minimal but sufficient level.
120 Minimal to save resources and sufficient in that the system is used at
121 its full capacity.
122
123 Each worker-pool bound to an actual CPU implements concurrency
124 management by hooking into the scheduler. The worker-pool is notified
125 whenever an active worker wakes up or sleeps and keeps track of the
126 number of the currently runnable workers. Generally, work items are
127 not expected to hog a CPU and consume many cycles. That means
128 maintaining just enough concurrency to prevent work processing from
129 stalling should be optimal. As long as there are one or more runnable
130 workers on the CPU, the worker-pool doesn't start execution of a new
131 work, but, when the last running worker goes to sleep, it immediately
132 schedules a new worker so that the CPU doesn't sit idle while there
133 are pending work items. This allows using a minimal number of workers
134 without losing execution bandwidth.
135
136 Keeping idle workers around doesn't cost other than the memory space
137 for kthreads, so cmwq holds onto idle ones for a while before killing
138 them.
139
140 For unbound workqueues, the number of backing pools is dynamic.
141 Unbound workqueue can be assigned custom attributes using
142 ``apply_workqueue_attrs()`` and workqueue will automatically create
143 backing worker pools matching the attributes. The responsibility of
144 regulating concurrency level is on the users. There is also a flag to
145 mark a bound wq to ignore the concurrency management. Please refer to
146 the API section for details.
147
148 Forward progress guarantee relies on that workers can be created when
149 more execution contexts are necessary, which in turn is guaranteed
150 through the use of rescue workers. All work items which might be used
151 on code paths that handle memory reclaim are required to be queued on
152 wq's that have a rescue-worker reserved for execution under memory
153 pressure. Else it is possible that the worker-pool deadlocks waiting
154 for execution contexts to free up.
155
156
157 Application Programming Interface (API)
158 =======================================
159
160 ``alloc_workqueue()`` allocates a wq. The original
161 ``create_*workqueue()`` functions are deprecated and scheduled for
162 removal. ``alloc_workqueue()`` takes three arguments - ``@name``,
163 ``@flags`` and ``@max_active``. ``@name`` is the name of the wq and
164 also used as the name of the rescuer thread if there is one.
165
166 A wq no longer manages execution resources but serves as a domain for
167 forward progress guarantee, flush and work item attributes. ``@flags``
168 and ``@max_active`` control how work items are assigned execution
169 resources, scheduled and executed.
170
171
172 ``flags``
173 ---------
174
175 ``WQ_BH``
176 BH workqueues can be considered a convenience interface to softirq. BH
177 workqueues are always per-CPU and all BH work items are executed in the
178 queueing CPU's softirq context in the queueing order.
179
180 All BH workqueues must have 0 ``max_active`` and ``WQ_HIGHPRI`` is the
181 only allowed additional flag.
182
183 BH work items cannot sleep. All other features such as delayed queueing,
184 flushing and canceling are supported.
185
186 ``WQ_PERCPU``
187 Work items queued to a per-cpu wq are bound to a specific CPU.
188 This flag is the right choice when cpu locality is important.
189
190 This flag is the complement of ``WQ_UNBOUND``.
191
192 ``WQ_UNBOUND``
193 Work items queued to an unbound wq are served by the special
194 worker-pools which host workers which are not bound to any
195 specific CPU. This makes the wq behave as a simple execution
196 context provider without concurrency management. The unbound
197 worker-pools try to start execution of work items as soon as
198 possible. Unbound wq sacrifices locality but is useful for
199 the following cases.
200
201 * Wide fluctuation in the concurrency level requirement is
202 expected and using bound wq may end up creating large number
203 of mostly unused workers across different CPUs as the issuer
204 hops through different CPUs.
205
206 * Long running CPU intensive workloads which can be better
207 managed by the system scheduler.
208
209 ``WQ_FREEZABLE``
210 A freezable wq participates in the freeze phase of the system
211 suspend operations. Work items on the wq are drained and no
212 new work item starts execution until thawed.
213
214 ``WQ_MEM_RECLAIM``
215 All wq which might be used in the memory reclaim paths **MUST**
216 have this flag set. The wq is guaranteed to have at least one
217 execution context regardless of memory pressure.
218
219 ``WQ_HIGHPRI``
220 Work items of a highpri wq are queued to the highpri
221 worker-pool of the target cpu. Highpri worker-pools are
222 served by worker threads with elevated nice level.
223
224 Note that normal and highpri worker-pools don't interact with
225 each other. Each maintains its separate pool of workers and
226 implements concurrency management among its workers.
227
228 ``WQ_CPU_INTENSIVE``
229 Work items of a CPU intensive wq do not contribute to the
230 concurrency level. In other words, runnable CPU intensive
231 work items will not prevent other work items in the same
232 worker-pool from starting execution. This is useful for bound
233 work items which are expected to hog CPU cycles so that their
234 execution is regulated by the system scheduler.
235
236 Although CPU intensive work items don't contribute to the
237 concurrency level, start of their executions is still
238 regulated by the concurrency management and runnable
239 non-CPU-intensive work items can delay execution of CPU
240 intensive work items.
241
242 This flag is meaningless for unbound wq.
243
244
245 ``max_active``
246 --------------
247
248 ``@max_active`` determines the maximum number of execution contexts per
249 CPU which can be assigned to the work items of a wq. For example, with
250 ``@max_active`` of 16, at most 16 work items of the wq can be executing
251 at the same time per CPU. This is always a per-CPU attribute, even for
252 unbound workqueues.
253
254 The maximum limit for ``@max_active`` is 2048 and the default value used
255 when 0 is specified is 1024. These values are chosen sufficiently high
256 such that they are not the limiting factor while providing protection in
257 runaway cases.
258
259 The number of active work items of a wq is usually regulated by the
260 users of the wq, more specifically, by how many work items the users
261 may queue at the same time. Unless there is a specific need for
262 throttling the number of active work items, specifying '0' is
263 recommended.
264
265 Some users depend on strict execution ordering where only one work item
266 is in flight at any given time and the work items are processed in
267 queueing order. While the combination of ``@max_active`` of 1 and
268 ``WQ_UNBOUND`` used to achieve this behavior, this is no longer the
269 case. Use alloc_ordered_workqueue() instead.
270
271
272 Example Execution Scenarios
273 ===========================
274
275 The following example execution scenarios try to illustrate how cmwq
276 behave under different configurations.
277
278 Work items w0, w1, w2 are queued to a bound wq q0 on the same CPU.
279 w0 burns CPU for 5ms then sleeps for 10ms then burns CPU for 5ms
280 again before finishing. w1 and w2 burn CPU for 5ms then sleep for
281 10ms.
282
283 Ignoring all other tasks, works and processing overhead, and assuming
284 simple FIFO scheduling, the following is one highly simplified version
285 of possible sequences of events with the original wq. ::
286
287 TIME IN MSECS EVENT
288 0 w0 starts and burns CPU
289 5 w0 sleeps
290 15 w0 wakes up and burns CPU
291 20 w0 finishes
292 20 w1 starts and burns CPU
293 25 w1 sleeps
294 35 w1 wakes up and finishes
295 35 w2 starts and burns CPU
296 40 w2 sleeps
297 50 w2 wakes up and finishes
298
299 And with cmwq with ``@max_active`` >= 3, ::
300
301 TIME IN MSECS EVENT
302 0 w0 starts and burns CPU
303 5 w0 sleeps
304 5 w1 starts and burns CPU
305 10 w1 sleeps
306 10 w2 starts and burns CPU
307 15 w2 sleeps
308 15 w0 wakes up and burns CPU
309 20 w0 finishes
310 20 w1 wakes up and finishes
311 25 w2 wakes up and finishes
312
313 If ``@max_active`` == 2, ::
314
315 TIME IN MSECS EVENT
316 0 w0 starts and burns CPU
317 5 w0 sleeps
318 5 w1 starts and burns CPU
319 10 w1 sleeps
320 15 w0 wakes up and burns CPU
321 20 w0 finishes
322 20 w1 wakes up and finishes
323 20 w2 starts and burns CPU
324 25 w2 sleeps
325 35 w2 wakes up and finishes
326
327 Now, let's assume w1 and w2 are queued to a different wq q1 which has
328 ``WQ_CPU_INTENSIVE`` set, ::
329
330 TIME IN MSECS EVENT
331 0 w0 starts and burns CPU
332 5 w0 sleeps
333 5 w1 and w2 start and burn CPU
334 10 w1 sleeps
335 15 w2 sleeps
336 15 w0 wakes up and burns CPU
337 20 w0 finishes
338 20 w1 wakes up and finishes
339 25 w2 wakes up and finishes
340
341
342 Guidelines
343 ==========
344
345 * Do not forget to use ``WQ_MEM_RECLAIM`` if a wq may process work
346 items which are used during memory reclaim. Each wq with
347 ``WQ_MEM_RECLAIM`` set has an execution context reserved for it. If
348 there is dependency among multiple work items used during memory
349 reclaim, they should be queued to separate wq each with
350 ``WQ_MEM_RECLAIM``.
351
352 * Unless strict ordering is required, there is no need to use ST wq.
353
354 * Unless there is a specific need, using 0 for @max_active is
355 recommended. In most use cases, concurrency level usually stays
356 well under the default limit.
357
358 * A wq serves as a domain for forward progress guarantee
359 (``WQ_MEM_RECLAIM``, flush and work item attributes. Work items
360 which are not involved in memory reclaim and don't need to be
361 flushed as a part of a group of work items, and don't require any
362 special attribute, can use one of the system wq. There is no
363 difference in execution characteristics between using a dedicated wq
364 and a system wq.
365
366 Note: If something may generate more than @max_active outstanding
367 work items (do stress test your producers), it may saturate a system
368 wq and potentially lead to deadlock. It should utilize its own
369 dedicated workqueue rather than the system wq.
370
371 * Unless work items are expected to consume a huge amount of CPU
372 cycles, using a bound wq is usually beneficial due to the increased
373 level of locality in wq operations and work item execution.
374
375
376 Affinity Scopes
377 ===============
378
379 An unbound workqueue groups CPUs according to its affinity scope to improve
380 cache locality. For example, if a workqueue is using the default affinity
381 scope of "cache", it will group CPUs according to last level cache
382 boundaries. A work item queued on the workqueue will be assigned to a worker
383 on one of the CPUs which share the last level cache with the issuing CPU.
384 Once started, the worker may or may not be allowed to move outside the scope
385 depending on the ``affinity_strict`` setting of the scope.
386
387 Workqueue currently supports the following affinity scopes.
388
389 ``default``
390 Use the scope in module parameter ``workqueue.default_affinity_scope``
391 which is always set to one of the scopes below.
392
393 ``cpu``
394 CPUs are not grouped. A work item issued on one CPU is processed by a
395 worker on the same CPU. This makes unbound workqueues behave as per-cpu
396 workqueues without concurrency management.
397
398 ``smt``
399 CPUs are grouped according to SMT boundaries. This usually means that the
400 logical threads of each physical CPU core are grouped together.
401
402 ``cache``
403 CPUs are grouped according to cache boundaries. Which specific cache
404 boundary is used is determined by the arch code. L3 is used in a lot of
405 cases. This is the default affinity scope.
406
407 ``numa``
408 CPUs are grouped according to NUMA boundaries.
409
410 ``system``
411 All CPUs are put in the same group. Workqueue makes no effort to process a
412 work item on a CPU close to the issuing CPU.
413
414 The default affinity scope can be changed with the module parameter
415 ``workqueue.default_affinity_scope`` and a specific workqueue's affinity
416 scope can be changed using ``apply_workqueue_attrs()``.
417
418 If ``WQ_SYSFS`` is set, the workqueue will have the following affinity scope
419 related interface files under its ``/sys/devices/virtual/workqueue/WQ_NAME/``
420 directory.
421
422 ``affinity_scope``
423 Read to see the current affinity scope. Write to change.
424
425 When default is the current scope, reading this file will also show the
426 current effective scope in parentheses, for example, ``default (cache)``.
427
428 ``affinity_strict``
429 0 by default indicating that affinity scopes are not strict. When a work
430 item starts execution, workqueue makes a best-effort attempt to ensure
431 that the worker is inside its affinity scope, which is called
432 repatriation. Once started, the scheduler is free to move the worker
433 anywhere in the system as it sees fit. This enables benefiting from scope
434 locality while still being able to utilize other CPUs if necessary and
435 available.
436
437 If set to 1, all workers of the scope are guaranteed always to be in the
438 scope. This may be useful when crossing affinity scopes has other
439 implications, for example, in terms of power consumption or workload
440 isolation. Strict NUMA scope can also be used to match the workqueue
441 behavior of older kernels.
442
443
444 Affinity Scopes and Performance
445 ===============================
446
447 It'd be ideal if an unbound workqueue's behavior is optimal for vast
448 majority of use cases without further tuning. Unfortunately, in the current
449 kernel, there exists a pronounced trade-off between locality and utilization
450 necessitating explicit configurations when workqueues are heavily used.
451
452 Higher locality leads to higher efficiency where more work is performed for
453 the same number of consumed CPU cycles. However, higher locality may also
454 cause lower overall system utilization if the work items are not spread
455 enough across the affinity scopes by the issuers. The following performance
456 testing with dm-crypt clearly illustrates this trade-off.
457
458 The tests are run on a CPU with 12-cores/24-threads split across four L3
459 caches (AMD Ryzen 9 3900x). CPU clock boost is turned off for consistency.
460 ``/dev/dm-0`` is a dm-crypt device created on NVME SSD (Samsung 990 PRO) and
461 opened with ``cryptsetup`` with default settings.
462
463
464 Scenario 1: Enough issuers and work spread across the machine
465 -------------------------------------------------------------
466
467 The command used: ::
468
469 $ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k --ioengine=libaio \
470 --iodepth=64 --runtime=60 --numjobs=24 --time_based --group_reporting \
471 --name=iops-test-job --verify=sha512
472
473 There are 24 issuers, each issuing 64 IOs concurrently. ``--verify=sha512``
474 makes ``fio`` generate and read back the content each time which makes
475 execution locality matter between the issuer and ``kcryptd``. The following
476 are the read bandwidths and CPU utilizations depending on different affinity
477 scope settings on ``kcryptd`` measured over five runs. Bandwidths are in
478 MiBps, and CPU util in percents.
479
480 .. list-table::
481 :widths: 16 20 20
482 :header-rows: 1
483
484 * - Affinity
485 - Bandwidth (MiBps)
486 - CPU util (%)
487
488 * - system
489 - 1159.40 ±1.34
490 - 99.31 ±0.02
491
492 * - cache
493 - 1166.40 ±0.89
494 - 99.34 ±0.01
495
496 * - cache (strict)
497 - 1166.00 ±0.71
498 - 99.35 ±0.01
499
500 With enough issuers spread across the system, there is no downside to
501 "cache", strict or otherwise. All three configurations saturate the whole
502 machine but the cache-affine ones outperform by 0.6% thanks to improved
503 locality.
504
505
506 Scenario 2: Fewer issuers, enough work for saturation
507 -----------------------------------------------------
508
509 The command used: ::
510
511 $ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
512 --ioengine=libaio --iodepth=64 --runtime=60 --numjobs=8 \
513 --time_based --group_reporting --name=iops-test-job --verify=sha512
514
515 The only difference from the previous scenario is ``--numjobs=8``. There are
516 a third of the issuers but is still enough total work to saturate the
517 system.
518
519 .. list-table::
520 :widths: 16 20 20
521 :header-rows: 1
522
523 * - Affinity
524 - Bandwidth (MiBps)
525 - CPU util (%)
526
527 * - system
528 - 1155.40 ±0.89
529 - 97.41 ±0.05
530
531 * - cache
532 - 1154.40 ±1.14
533 - 96.15 ±0.09
534
535 * - cache (strict)
536 - 1112.00 ±4.64
537 - 93.26 ±0.35
538
539 This is more than enough work to saturate the system. Both "system" and
540 "cache" are nearly saturating the machine but not fully. "cache" is using
541 less CPU but the better efficiency puts it at the same bandwidth as
542 "system".
543
544 Eight issuers moving around over four L3 cache scope still allow "cache
545 (strict)" to mostly saturate the machine but the loss of work conservation
546 is now starting to hurt with 3.7% bandwidth loss.
547
548
549 Scenario 3: Even fewer issuers, not enough work to saturate
550 -----------------------------------------------------------
551
552 The command used: ::
553
554 $ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
555 --ioengine=libaio --iodepth=64 --runtime=60 --numjobs=4 \
556 --time_based --group_reporting --name=iops-test-job --verify=sha512
557
558 Again, the only difference is ``--numjobs=4``. With the number of issuers
559 reduced to four, there now isn't enough work to saturate the whole system
560 and the bandwidth becomes dependent on completion latencies.
561
562 .. list-table::
563 :widths: 16 20 20
564 :header-rows: 1
565
566 * - Affinity
567 - Bandwidth (MiBps)
568 - CPU util (%)
569
570 * - system
571 - 993.60 ±1.82
572 - 75.49 ±0.06
573
574 * - cache
575 - 973.40 ±1.52
576 - 74.90 ±0.07
577
578 * - cache (strict)
579 - 828.20 ±4.49
580 - 66.84 ±0.29
581
582 Now, the tradeoff between locality and utilization is clearer. "cache" shows
583 2% bandwidth loss compared to "system" and "cache (struct)" whopping 20%.
584
585
586 Conclusion and Recommendations
587 ------------------------------
588
589 In the above experiments, the efficiency advantage of the "cache" affinity
590 scope over "system" is, while consistent and noticeable, small. However, the
591 impact is dependent on the distances between the scopes and may be more
592 pronounced in processors with more complex topologies.
593
594 While the loss of work-conservation in certain scenarios hurts, it is a lot
595 better than "cache (strict)" and maximizing workqueue utilization is
596 unlikely to be the common case anyway. As such, "cache" is the default
597 affinity scope for unbound pools.
598
599 * As there is no one option which is great for most cases, workqueue usages
600 that may consume a significant amount of CPU are recommended to configure
601 the workqueues using ``apply_workqueue_attrs()`` and/or enable
602 ``WQ_SYSFS``.
603
604 * An unbound workqueue with strict "cpu" affinity scope behaves the same as
605 ``WQ_CPU_INTENSIVE`` per-cpu workqueue. There is no real advanage to the
606 latter and an unbound workqueue provides a lot more flexibility.
607
608 * Affinity scopes are introduced in Linux v6.5. To emulate the previous
609 behavior, use strict "numa" affinity scope.
610
611 * The loss of work-conservation in non-strict affinity scopes is likely
612 originating from the scheduler. There is no theoretical reason why the
613 kernel wouldn't be able to do the right thing and maintain
614 work-conservation in most cases. As such, it is possible that future
615 scheduler improvements may make most of these tunables unnecessary.
616
617
618 Examining Configuration
619 =======================
620
621 Use tools/workqueue/wq_dump.py to examine unbound CPU affinity
622 configuration, worker pools and how workqueues map to the pools: ::
623
624 $ tools/workqueue/wq_dump.py
625 Affinity Scopes
626 ===============
627 wq_unbound_cpumask=0000000f
628
629 CPU
630 nr_pods 4
631 pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
632 pod_node [0]=0 [1]=0 [2]=1 [3]=1
633 cpu_pod [0]=0 [1]=1 [2]=2 [3]=3
634
635 SMT
636 nr_pods 4
637 pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
638 pod_node [0]=0 [1]=0 [2]=1 [3]=1
639 cpu_pod [0]=0 [1]=1 [2]=2 [3]=3
640
641 CACHE (default)
642 nr_pods 2
643 pod_cpus [0]=00000003 [1]=0000000c
644 pod_node [0]=0 [1]=1
645 cpu_pod [0]=0 [1]=0 [2]=1 [3]=1
646
647 NUMA
648 nr_pods 2
649 pod_cpus [0]=00000003 [1]=0000000c
650 pod_node [0]=0 [1]=1
651 cpu_pod [0]=0 [1]=0 [2]=1 [3]=1
652
653 SYSTEM
654 nr_pods 1
655 pod_cpus [0]=0000000f
656 pod_node [0]=-1
657 cpu_pod [0]=0 [1]=0 [2]=0 [3]=0
658
659 Worker Pools
660 ============
661 pool[00] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 0
662 pool[01] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 0
663 pool[02] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 1
664 pool[03] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 1
665 pool[04] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 2
666 pool[05] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 2
667 pool[06] ref= 1 nice= 0 idle/workers= 3/ 3 cpu= 3
668 pool[07] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 3
669 pool[08] ref=42 nice= 0 idle/workers= 6/ 6 cpus=0000000f
670 pool[09] ref=28 nice= 0 idle/workers= 3/ 3 cpus=00000003
671 pool[10] ref=28 nice= 0 idle/workers= 17/ 17 cpus=0000000c
672 pool[11] ref= 1 nice=-20 idle/workers= 1/ 1 cpus=0000000f
673 pool[12] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=00000003
674 pool[13] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=0000000c
675
676 Workqueue CPU -> pool
677 =====================
678 [ workqueue \ CPU 0 1 2 3 dfl]
679 events percpu 0 2 4 6
680 events_highpri percpu 1 3 5 7
681 events_long percpu 0 2 4 6
682 events_unbound unbound 9 9 10 10 8
683 events_freezable percpu 0 2 4 6
684 events_power_efficient percpu 0 2 4 6
685 events_freezable_pwr_ef percpu 0 2 4 6
686 rcu_gp percpu 0 2 4 6
687 rcu_par_gp percpu 0 2 4 6
688 slub_flushwq percpu 0 2 4 6
689 netns ordered 8 8 8 8 8
690 ...
691
692 See the command's help message for more info.
693
694
695 Monitoring
696 ==========
697
698 Use tools/workqueue/wq_monitor.py to monitor workqueue operations: ::
699
700 $ tools/workqueue/wq_monitor.py events
701 total infl CPUtime CPUhog CMW/RPR mayday rescued
702 events 18545 0 6.1 0 5 - -
703 events_highpri 8 0 0.0 0 0 - -
704 events_long 3 0 0.0 0 0 - -
705 events_unbound 38306 0 0.1 - 7 - -
706 events_freezable 0 0 0.0 0 0 - -
707 events_power_efficient 29598 0 0.2 0 0 - -
708 events_freezable_pwr_ef 10 0 0.0 0 0 - -
709 sock_diag_events 0 0 0.0 0 0 - -
710
711 total infl CPUtime CPUhog CMW/RPR mayday rescued
712 events 18548 0 6.1 0 5 - -
713 events_highpri 8 0 0.0 0 0 - -
714 events_long 3 0 0.0 0 0 - -
715 events_unbound 38322 0 0.1 - 7 - -
716 events_freezable 0 0 0.0 0 0 - -
717 events_power_efficient 29603 0 0.2 0 0 - -
718 events_freezable_pwr_ef 10 0 0.0 0 0 - -
719 sock_diag_events 0 0 0.0 0 0 - -
720
721 ...
722
723 See the command's help message for more info.
724
725
726 Debugging
727 =========
728
729 Because the work functions are executed by generic worker threads
730 there are a few tricks needed to shed some light on misbehaving
731 workqueue users.
732
733 Worker threads show up in the process list as: ::
734
735 root 5671 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/0:1]
736 root 5672 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/1:2]
737 root 5673 0.0 0.0 0 0 ? S 12:12 0:00 [kworker/0:0]
738 root 5674 0.0 0.0 0 0 ? S 12:13 0:00 [kworker/1:0]
739
740 If kworkers are going crazy (using too much cpu), there are two types
741 of possible problems:
742
743 1. Something being scheduled in rapid succession
744 2. A single work item that consumes lots of cpu cycles
745
746 The first one can be tracked using tracing: ::
747
748 $ echo workqueue:workqueue_queue_work > /sys/kernel/tracing/set_event
749 $ cat /sys/kernel/tracing/trace_pipe > out.txt
750 (wait a few secs)
751 ^C
752
753 If something is busy looping on work queueing, it would be dominating
754 the output and the offender can be determined with the work item
755 function.
756
757 For the second type of problems it should be possible to just check
758 the stack trace of the offending worker thread. ::
759
760 $ cat /proc/THE_OFFENDING_KWORKER/stack
761
762 The work item's function should be trivially visible in the stack
763 trace.
764
765
766 Non-reentrance Conditions
767 =========================
768
769 Workqueue guarantees that a work item cannot be re-entrant if the following
770 conditions hold after a work item gets queued:
771
772 1. The work function hasn't been changed.
773 2. No one queues the work item to another workqueue.
774 3. The work item hasn't been reinitiated.
775
776 In other words, if the above conditions hold, the work item is guaranteed to be
777 executed by at most one worker system-wide at any given time.
778
779 Note that requeuing the work item (to the same queue) in the self function
780 doesn't break these conditions, so it's safe to do. Otherwise, caution is
781 required when breaking the conditions inside a work function.
782
783
784 Kernel Inline Documentations Reference
785 ======================================
786
787 .. kernel-doc:: include/linux/workqueue.h
788
789 .. kernel-doc:: kernel/workqueue.c
790

3. 한국어 전문 번역

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

Workqueue

1-9

Workqueue

날짜: 2010년 9월

저자: Tejun Heo <[email protected]>

저자: Florian Mickler <[email protected]>

소개

10-27

소개

Asynchronous process execution context가 필요한 경우는 많으며, workqueue(wq) API가 이런 상황에서 가장 흔히 사용하는 mechanism입니다.

이러한 asynchronous execution context가 필요하면 실행할 function을 설명하는 work item을 queue에 넣습니다. 독립 thread가 asynchronous execution context 역할을 합니다. Queue를 workqueue라 하고 thread를 worker라고 합니다.

Workqueue에 work item이 있는 동안 worker는 각 work item과 연결된 function을 차례로 실행합니다. Work item이 남지 않으면 worker는 idle 상태가 되고, 새 work item이 queue에 들어오면 다시 실행을 시작합니다.

Concurrency managed workqueue가 필요한 이유

28-67

Concurrency Managed Workqueue가 필요한 이유

원래 wq 구현에서 multi-threaded(MT) wq는 CPU마다 worker thread 하나를 두었고 single-threaded(ST) wq는 system 전체에 worker thread 하나를 두었습니다. MT wq 하나만으로도 CPU 수만큼 worker를 유지해야 했습니다. Kernel의 MT wq 사용자가 늘고 CPU core 수도 계속 증가하면서 일부 system은 boot만 해도 기본 32k PID space를 소진했습니다.

MT wq는 resource를 많이 낭비하면서도 제공하는 concurrency 수준은 만족스럽지 못했습니다. MT에서는 덜 심했지만 ST와 MT 모두 각 wq가 별도 worker pool을 유지했습니다. MT wq는 CPU마다 execution context 하나만, ST wq는 system 전체에 하나만 제공했습니다. Work item은 제한된 context를 두고 경쟁했고 단일 execution context 주변의 deadlock 가능성 등 여러 문제가 생겼습니다.

Concurrency 수준과 resource 사용량 사이의 긴장은 불필요한 tradeoff도 강요했습니다. 예를 들어 libata는 polling PIO에 ST wq를 사용하면서 polling PIO 둘이 동시에 진행할 수 없다는 제약을 받아들였습니다. MT wq도 concurrency가 크게 낫지 않아 async나 fscache처럼 더 높은 concurrency가 필요한 사용자는 자체 thread pool을 구현해야 했습니다.

Concurrency Managed Workqueue(cmwq)는 다음 목표에 초점을 맞춘 wq 재구현입니다.

  • 기존 workqueue API와 compatibility를 유지합니다.
  • 모든 wq가 공유하는 per-CPU unified worker pool로 resource를 크게 낭비하지 않으면서 필요에 따라 유연한 concurrency를 제공합니다.
  • Worker pool과 concurrency 수준을 자동으로 조절하여 API 사용자가 세부 사항을 걱정하지 않게 합니다.

설계

68-156

설계

Function의 asynchronous execution을 쉽게 하기 위해 work item이라는 abstraction을 도입합니다.

Work item은 비동기로 실행할 function pointer를 보유한 단순한 struct입니다. Driver나 subsystem이 function을 비동기로 실행하려면 그 function을 가리키는 work item을 설정하고 workqueue에 queue해야 합니다.

Work item은 thread 또는 BH(softirq) context에서 실행할 수 있습니다.

Threaded workqueue에서는 [k]worker라고 부르는 special-purpose thread가 queue의 function을 차례로 실행합니다. Queue된 work가 없으면 worker thread는 idle 상태가 됩니다. 이 thread들은 worker-pool에서 관리합니다.

Cmwq 설계는 subsystem과 driver가 work item을 넣는 user-facing workqueue와 worker-pool을 관리하고 queue된 item을 처리하는 backend mechanism을 구분합니다.

가능한 CPU마다 normal work item용과 high-priority item용 worker-pool 두 개가 있고, unbound workqueue에 들어온 item을 처리하는 추가 worker-pool이 있습니다. 추가 backing pool 수는 동적으로 변합니다.

BH workqueue도 같은 framework를 사용합니다. 다만 concurrent execution context가 하나뿐이므로 concurrency를 관리할 필요가 없습니다. Per-CPU BH worker pool은 BH execution context를 나타내는 pseudo worker 하나만 포함합니다. BH workqueue는 softirq의 convenience interface로 볼 수 있습니다.

Subsystem과 driver는 필요에 따라 전용 workqueue API function으로 work item을 만들고 queue할 수 있습니다. Workqueue flag로 CPU locality, concurrency limit, priority 등 실행 방식을 조정합니다. 자세한 내용은 아래 `alloc_workqueue()` API 설명을 참조하십시오.

Work item이 workqueue에 queue되면 queue parameter와 workqueue attribute에 따라 target worker-pool을 정하고 그 pool의 shared worklist에 추가합니다. 별도 override가 없다면 bound workqueue item은 issuer가 실행 중인 CPU에 연결된 normal 또는 highpri worker-pool의 worklist로 갑니다.

모든 thread pool 구현에서 active execution context 수인 concurrency level 관리는 중요합니다. Cmwq는 resource를 절약할 만큼 작으면서 system capacity를 모두 사용할 만큼 충분한 수준을 유지하려 합니다.

실제 CPU에 bound된 각 worker-pool은 scheduler에 hook하여 concurrency를 관리합니다. Active worker가 깨거나 잠들 때 notification을 받고 현재 runnable worker 수를 추적합니다. Work item은 보통 CPU를 독점해 cycle을 많이 소비하지 않는다고 가정하므로 처리가 멈추지 않을 만큼의 concurrency만 유지하는 것이 최적입니다.

CPU에 runnable worker가 하나 이상 있으면 worker-pool은 새 work 실행을 시작하지 않습니다. 마지막 running worker가 잠들면 pending work가 있는데 CPU가 idle 상태가 되지 않도록 새 worker를 즉시 schedule합니다. 이 방식은 execution bandwidth를 잃지 않으면서 worker 수를 최소화합니다.

Idle worker를 유지하는 비용은 kthread memory뿐이므로 cmwq는 일정 시간 유지한 뒤 제거합니다.

Unbound workqueue의 backing pool 수는 동적입니다. `apply_workqueue_attrs()`로 custom attribute를 지정하면 workqueue가 일치하는 backing worker pool을 자동 생성합니다. Concurrency level 조절 책임은 사용자에게 있습니다. Bound wq가 concurrency management를 무시하게 하는 flag도 있습니다.

Forward progress 보장은 execution context가 더 필요할 때 worker를 생성할 수 있어야 하며 rescue worker가 이를 보장합니다. Memory reclaim 처리 code path에서 사용할 수 있는 모든 work item은 memory pressure 아래에서 실행할 rescue worker를 예약한 wq에 queue해야 합니다. 그렇지 않으면 worker-pool이 execution context가 비기를 기다리며 deadlock될 수 있습니다.

Application Programming Interface

157-171

Application Programming Interface(API)

`alloc_workqueue()`는 wq를 할당합니다. 기존 `create_*workqueue()` function은 deprecated 상태이며 제거될 예정입니다. `alloc_workqueue()`는 `@name`, `@flags`, `@max_active` 세 argument를 받습니다. `@name`은 wq 이름이며 rescuer thread가 있으면 그 이름으로도 사용합니다.

Wq는 더 이상 execution resource를 직접 관리하지 않고 forward progress guarantee, flush, work item attribute를 위한 domain 역할을 합니다. `@flags`와 `@max_active`가 work item에 execution resource를 배정하고 schedule 및 실행하는 방법을 제어합니다.

flags

172-244

`flags`

`WQ_BH`: BH workqueue는 softirq의 convenience interface입니다. 항상 per-CPU이며 모든 BH work item은 queue한 CPU의 softirq context에서 queue 순서대로 실행됩니다. `max_active`는 반드시 0이어야 하며 추가로 허용되는 flag는 `WQ_HIGHPRI`뿐입니다. BH work item은 sleep할 수 없지만 delayed queueing, flushing, canceling 등 다른 기능은 지원합니다.

`WQ_PERCPU`: Per-CPU wq에 queue한 work item을 특정 CPU에 bind합니다. CPU locality가 중요할 때 적합하며 `WQ_UNBOUND`의 반대입니다.

`WQ_UNBOUND`: 특정 CPU에 bind되지 않은 worker가 있는 special worker-pool이 item을 처리합니다. Concurrency management 없는 단순 execution context provider처럼 동작하며 가능한 빨리 실행을 시작합니다. Locality를 희생하지만 다음 경우 유용합니다.

  • 필요한 concurrency 수준의 변동이 크고 issuer가 CPU 사이를 이동할 때 bound wq를 사용하면 서로 다른 CPU에 거의 쓰지 않는 worker를 많이 만들 수 있는 경우
  • System scheduler가 더 잘 관리할 수 있는 오래 실행되는 CPU-intensive workload

`WQ_FREEZABLE`: System suspend의 freeze phase에 참여합니다. Wq의 work item을 drain하고 thaw될 때까지 새 item 실행을 시작하지 않습니다.

`WQ_MEM_RECLAIM`: Memory reclaim path에서 사용할 수 있는 모든 wq는 반드시 이 flag를 설정해야 합니다. Memory pressure와 관계없이 최소 하나의 execution context를 보장합니다.

`WQ_HIGHPRI`: Highpri wq의 work item은 target CPU의 highpri worker-pool로 갑니다. 높은 nice level의 worker thread가 처리합니다. Normal pool과 highpri pool은 상호작용하지 않고 각각 별도 worker pool과 concurrency management를 유지합니다.

`WQ_CPU_INTENSIVE`: CPU-intensive wq item은 concurrency level 계산에 기여하지 않습니다. Runnable CPU-intensive item이 같은 worker-pool의 다른 item 실행을 막지 않습니다. CPU cycle을 독점할 것으로 예상되는 bound item의 실행을 system scheduler가 조절하게 할 때 유용합니다.

CPU-intensive item도 실행 시작은 concurrency management의 조절을 받으며 runnable non-CPU-intensive item이 실행을 지연시킬 수 있습니다. 이 flag는 unbound wq에는 의미가 없습니다.

max_active

245-271

`max_active`

`@max_active`는 wq의 work item에 배정할 수 있는 CPU당 최대 execution context 수를 정합니다. 예를 들어 값이 16이면 CPU마다 해당 wq의 work item을 최대 16개 동시에 실행할 수 있습니다. Unbound workqueue에서도 항상 per-CPU attribute입니다.

최댓값은 2048이고 0을 지정했을 때 기본값은 1024입니다. Runaway case를 방지하면서 일반적으로 제한 요소가 되지 않도록 충분히 큰 값입니다.

Active work item 수는 보통 wq 사용자가 동시에 queue하는 item 수로 조절합니다. Active item 수를 throttle할 특별한 필요가 없다면 0을 지정하는 것이 좋습니다.

언제나 work item 하나만 in-flight 상태이고 queue 순서대로 처리하는 strict ordering이 필요한 경우가 있습니다. 예전에는 `@max_active` 1과 `WQ_UNBOUND` 조합으로 가능했지만 이제는 그렇지 않습니다. 대신 `alloc_ordered_workqueue()`를 사용하십시오.

실행 시나리오 예제

272-341

실행 시나리오 예제

다음 예는 configuration에 따라 cmwq가 어떻게 동작하는지 보여줍니다. Work item `w0`, `w1`, `w2`를 같은 CPU의 bound wq `q0`에 넣습니다. `w0`은 CPU를 5ms 사용하고 10ms sleep한 뒤 다시 5ms 사용하고 끝납니다. `w1`과 `w2`는 CPU를 5ms 사용한 뒤 10ms sleep합니다.

다른 task, work, processing overhead를 무시하고 단순 FIFO scheduling을 가정할 때 기존 wq에서 가능한 event sequence의 매우 단순화한 예는 다음과 같습니다.

시간(ms)Event
0w0 starts and burns CPU
5w0 sleeps
15w0 wakes up and burns CPU
20w0 finishes
20w1 starts and burns CPU
25w1 sleeps
35w1 wakes up and finishes
35w2 starts and burns CPU
40w2 sleeps
50w2 wakes up and finishes

`@max_active >= 3`인 cmwq에서는 다음과 같습니다.

시간(ms)Event
0w0 starts and burns CPU
5w0 sleeps
5w1 starts and burns CPU
10w1 sleeps
10w2 starts and burns CPU
15w2 sleeps
15w0 wakes up and burns CPU
20w0 finishes
20w1 wakes up and finishes
25w2 wakes up and finishes

`@max_active == 2`이면 다음과 같습니다.

시간(ms)Event
0w0 starts and burns CPU
5w0 sleeps
5w1 starts and burns CPU
10w1 sleeps
15w0 wakes up and burns CPU
20w0 finishes
20w1 wakes up and finishes
20w2 starts and burns CPU
25w2 sleeps
35w2 wakes up and finishes

`w1`과 `w2`를 `WQ_CPU_INTENSIVE`가 설정된 다른 wq `q1`에 넣는다고 가정하면 다음과 같습니다.

시간(ms)Event
0w0 starts and burns CPU
5w0 sleeps
5w1 and w2 start and burn CPU
10w1 sleeps
15w2 sleeps
15w0 wakes up and burns CPU
20w0 finishes
20w1 wakes up and finishes
25w2 wakes up and finishes

지침

342-375

지침

  • Memory reclaim 중 사용하는 work item을 wq가 처리할 수 있다면 `WQ_MEM_RECLAIM`을 잊지 말아야 합니다. Reclaim에서 여러 work item 사이에 dependency가 있다면 각각 `WQ_MEM_RECLAIM`을 설정한 별도 wq에 넣으십시오.
  • Strict ordering이 필요하지 않다면 ST wq를 사용할 필요가 없습니다.
  • 특별한 필요가 없다면 `@max_active`에 0을 사용하는 것이 좋습니다. 대부분의 use case에서 concurrency level은 기본 limit보다 훨씬 낮습니다.
  • Wq는 `WQ_MEM_RECLAIM`, flush, work item attribute에 대한 forward progress guarantee domain입니다. Reclaim과 관계없고 group flush나 special attribute가 필요 없는 item은 system wq 중 하나를 사용할 수 있으며 dedicated wq와 실행 특성 차이가 없습니다.
  • Producer stress test 결과 `@max_active`보다 많은 outstanding item을 만들 수 있다면 system wq를 포화시켜 deadlock을 일으킬 수 있으므로 dedicated workqueue를 사용해야 합니다.
  • Work item이 CPU cycle을 매우 많이 소비할 것으로 예상되지 않는다면 wq operation과 item 실행 locality가 높은 bound wq가 보통 유리합니다.

Affinity scope

376-443

Affinity scope

Unbound workqueue는 cache locality를 높이도록 affinity scope에 따라 CPU를 group화합니다. 기본 `cache` scope라면 last-level cache boundary에 따라 group을 만들고, work item은 issuer CPU와 last-level cache를 공유하는 CPU의 worker에 배정됩니다. 시작 후 worker가 scope 밖으로 이동할 수 있는지는 `affinity_strict` 설정에 달려 있습니다.

현재 지원하는 affinity scope는 다음과 같습니다.

`default`: 항상 아래 scope 중 하나로 설정되는 module parameter `workqueue.default_affinity_scope`의 scope를 사용합니다.

`cpu`: CPU를 group화하지 않습니다. 한 CPU에서 발행한 item은 같은 CPU의 worker가 처리하며, unbound workqueue가 concurrency management 없는 per-CPU workqueue처럼 동작합니다.

`smt`: SMT boundary에 따라 CPU를 group화합니다. 보통 각 physical CPU core의 logical thread를 함께 묶습니다.

`cache`: Cache boundary에 따라 CPU를 group화합니다. 구체적인 cache boundary는 architecture code가 정하며 흔히 L3를 사용합니다. 기본 affinity scope입니다.

`numa`: NUMA boundary에 따라 CPU를 group화합니다.

`system`: 모든 CPU를 같은 group에 넣습니다. Issuer CPU와 가까운 CPU에서 item을 처리하려고 시도하지 않습니다.

기본 affinity scope는 `workqueue.default_affinity_scope` module parameter로 바꾸며, 특정 workqueue scope는 `apply_workqueue_attrs()`로 바꿀 수 있습니다.

`WQ_SYSFS`를 설정하면 `/sys/devices/virtual/workqueue/WQ_NAME/` directory 아래에 affinity 관련 interface file이 생깁니다.

`affinity_scope`: 읽으면 현재 scope를 보고 쓰면 변경합니다. 현재 scope가 default이면 `default (cache)`처럼 괄호 안에 현재 effective scope도 표시합니다.

`affinity_strict`: 기본값 0은 scope가 strict하지 않음을 나타냅니다. Item 실행을 시작할 때 worker가 scope 안에 있도록 best-effort로 시도하는 repatriation을 수행하지만, 시작 후 scheduler는 필요에 따라 worker를 system 어디로든 옮길 수 있습니다. Scope locality와 다른 가용 CPU 활용을 함께 얻습니다.

1로 설정하면 scope의 모든 worker가 항상 scope 안에 있다고 보장합니다. Scope 횡단이 power consumption이나 workload isolation에 영향을 줄 때 유용합니다. Strict NUMA scope로 구형 kernel의 workqueue 동작을 맞출 수도 있습니다.

Affinity scope와 성능

444-463

Affinity scope와 성능

추가 tuning 없이도 unbound workqueue가 대다수 use case에서 최적으로 동작하면 이상적이지만, 현재 kernel에서는 locality와 utilization 사이 tradeoff가 뚜렷해 workqueue를 많이 사용할 때 명시적 configuration이 필요합니다.

높은 locality는 같은 CPU cycle로 더 많은 work를 수행하는 효율을 높입니다. 그러나 issuer가 work item을 affinity scope 전체에 충분히 분산하지 않으면 전체 system utilization을 낮출 수 있습니다. 다음 dm-crypt performance test가 이 tradeoff를 보여줍니다.

Test는 4개 L3 cache에 나뉜 12-core/24-thread CPU인 AMD Ryzen 9 3900x에서 수행했으며 일관성을 위해 CPU clock boost를 껐습니다. `/dev/dm-0`은 Samsung 990 PRO NVME SSD 위에 기본 `cryptsetup` 설정으로 만든 dm-crypt device입니다.

시나리오 1: 충분한 issuer와 system 전체에 분산된 work

464-505

시나리오 1: 충분한 issuer와 machine 전체에 분산된 work

사용한 command:

$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k --ioengine=libaio \
  --iodepth=64 --runtime=60 --numjobs=24 --time_based --group_reporting \
  --name=iops-test-job --verify=sha512

Issuer 24개가 각각 IO 64개를 동시에 발행합니다. `--verify=sha512`는 fio가 매번 content를 생성하고 다시 읽게 하므로 issuer와 `kcryptd` 사이 execution locality가 중요합니다. 다음은 다섯 번 실행해 측정한 `kcryptd` affinity scope별 read bandwidth와 CPU utilization입니다. Bandwidth 단위는 MiBps, CPU util 단위는 percent입니다.

AffinityBandwidth (MiBps)CPU util (%)
system1159.40 ±1.3499.31 ±0.02
cache1166.40 ±0.8999.34 ±0.01
cache (strict)1166.00 ±0.7199.35 ±0.01

System 전체에 issuer가 충분히 분산되면 strict 여부와 관계없이 `cache`의 단점이 없습니다. 세 configuration 모두 machine 전체를 포화시키며 cache-affine configuration은 향상된 locality 덕분에 0.6% 높은 성능을 보입니다.

시나리오 2: 더 적은 issuer와 포화에 충분한 work

506-548

시나리오 2: 더 적은 issuer와 포화에 충분한 work

사용한 command:

$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
  --ioengine=libaio --iodepth=64 --runtime=60 --numjobs=8 \
  --time_based --group_reporting --name=iops-test-job --verify=sha512

이전 시나리오와 다른 점은 `--numjobs=8`뿐입니다. Issuer는 3분의 1이지만 total work는 여전히 system을 포화시키기에 충분합니다.

AffinityBandwidth (MiBps)CPU util (%)
system1155.40 ±0.8997.41 ±0.05
cache1154.40 ±1.1496.15 ±0.09
cache (strict)1112.00 ±4.6493.26 ±0.35

System을 포화시키기에 충분한 work입니다. `system`과 `cache` 모두 machine을 거의 포화시키지만 완전하지는 않습니다. `cache`는 CPU를 덜 사용하지만 효율이 좋아 `system`과 같은 bandwidth를 냅니다.

Issuer 8개가 L3 cache scope 4개 사이를 이동해 `cache (strict)`도 machine을 거의 포화시키지만 work conservation 손실이 영향을 주기 시작하여 bandwidth가 3.7% 감소합니다.

시나리오 3: 더 적은 issuer와 포화에 부족한 work

549-585

시나리오 3: 더 적은 issuer와 포화에 부족한 work

사용한 command:

$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
  --ioengine=libaio --iodepth=64 --runtime=60 --numjobs=4 \
  --time_based --group_reporting --name=iops-test-job --verify=sha512

다시 다른 점은 `--numjobs=4`뿐입니다. Issuer가 4개로 줄어 system 전체를 포화시킬 work가 부족하고 bandwidth는 completion latency에 의존합니다.

AffinityBandwidth (MiBps)CPU util (%)
system993.60 ±1.8275.49 ±0.06
cache973.40 ±1.5274.90 ±0.07
cache (strict)828.20 ±4.4966.84 ±0.29

이제 locality와 utilization의 tradeoff가 더 분명합니다. `cache`는 `system`보다 bandwidth가 2% 낮고, 원문의 `cache (struct)` 표기는 20%나 낮다고 설명합니다.

결론과 권장 사항

586-617

결론과 권장 사항

위 실험에서 `cache` affinity scope가 `system`보다 갖는 efficiency 이점은 일관되고 눈에 띄지만 작습니다. 다만 scope 사이 거리에 따라 영향이 달라지고 topology가 복잡한 processor에서는 더 커질 수 있습니다.

일부 시나리오에서 work conservation 손실이 해롭지만 `cache (strict)`보다는 훨씬 낫고, workqueue utilization 극대화가 흔한 case일 가능성도 낮습니다. 따라서 `cache`가 unbound pool의 기본 affinity scope입니다.

  • 대부분의 case에 훌륭한 단일 option이 없으므로 CPU를 많이 소비할 수 있는 workqueue 사용자는 `apply_workqueue_attrs()`로 구성하거나 `WQ_SYSFS`를 활성화하는 것이 좋습니다.
  • Strict `cpu` affinity scope의 unbound workqueue는 `WQ_CPU_INTENSIVE` per-CPU workqueue와 똑같이 동작합니다. 후자에 실질적 이점이 없고 unbound workqueue가 훨씬 유연합니다.
  • Affinity scope는 Linux v6.5에서 도입되었습니다. 이전 동작을 모방하려면 strict `numa` affinity scope를 사용하십시오.
  • Non-strict affinity scope의 work conservation 손실은 scheduler에서 비롯된 것으로 보입니다. 이론적으로 kernel이 올바르게 동작하지 못할 이유가 없으므로 향후 scheduler 개선으로 tuning 항목 대부분이 불필요해질 수 있습니다.

Configuration 검사

618-694

Configuration 검사

`tools/workqueue/wq_dump.py`로 unbound CPU affinity configuration, worker pool 및 workqueue와 pool의 mapping을 검사합니다.

$ tools/workqueue/wq_dump.py
Affinity Scopes
===============
wq_unbound_cpumask=0000000f

CPU
  nr_pods  4
  pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
  pod_node [0]=0 [1]=0 [2]=1 [3]=1
  cpu_pod  [0]=0 [1]=1 [2]=2 [3]=3

SMT
  nr_pods  4
  pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
  pod_node [0]=0 [1]=0 [2]=1 [3]=1
  cpu_pod  [0]=0 [1]=1 [2]=2 [3]=3

CACHE (default)
  nr_pods  2
  pod_cpus [0]=00000003 [1]=0000000c
  pod_node [0]=0 [1]=1
  cpu_pod  [0]=0 [1]=0 [2]=1 [3]=1

NUMA
  nr_pods  2
  pod_cpus [0]=00000003 [1]=0000000c
  pod_node [0]=0 [1]=1
  cpu_pod  [0]=0 [1]=0 [2]=1 [3]=1

SYSTEM
  nr_pods  1
  pod_cpus [0]=0000000f
  pod_node [0]=-1
  cpu_pod  [0]=0 [1]=0 [2]=0 [3]=0

Worker Pools
============
pool[00] ref= 1 nice=  0 idle/workers=  4/  4 cpu=  0
pool[01] ref= 1 nice=-20 idle/workers=  2/  2 cpu=  0
pool[02] ref= 1 nice=  0 idle/workers=  4/  4 cpu=  1
pool[03] ref= 1 nice=-20 idle/workers=  2/  2 cpu=  1
pool[04] ref= 1 nice=  0 idle/workers=  4/  4 cpu=  2
pool[05] ref= 1 nice=-20 idle/workers=  2/  2 cpu=  2
pool[06] ref= 1 nice=  0 idle/workers=  3/  3 cpu=  3
pool[07] ref= 1 nice=-20 idle/workers=  2/  2 cpu=  3
pool[08] ref=42 nice=  0 idle/workers=  6/  6 cpus=0000000f
pool[09] ref=28 nice=  0 idle/workers=  3/  3 cpus=00000003
pool[10] ref=28 nice=  0 idle/workers= 17/ 17 cpus=0000000c
pool[11] ref= 1 nice=-20 idle/workers=  1/  1 cpus=0000000f
pool[12] ref= 2 nice=-20 idle/workers=  1/  1 cpus=00000003
pool[13] ref= 2 nice=-20 idle/workers=  1/  1 cpus=0000000c

Workqueue CPU -> pool
=====================
[    workqueue \ CPU              0  1  2  3 dfl]
events                   percpu   0  2  4  6
events_highpri           percpu   1  3  5  7
events_long              percpu   0  2  4  6
events_unbound           unbound  9  9 10 10  8
events_freezable         percpu   0  2  4  6
events_power_efficient   percpu   0  2  4  6
events_freezable_pwr_ef  percpu   0  2  4  6
rcu_gp                   percpu   0  2  4  6
rcu_par_gp               percpu   0  2  4  6
slub_flushwq             percpu   0  2  4  6
netns                    ordered  8  8  8  8  8
...
Unbound workqueue affinity와 pool mapping
ScopePod 수CPU maskNode / 의미
CPU400000001, 00000002, 00000004, 00000008CPU별 pod
SMT400000001, 00000002, 00000004, 00000008예제 system에서는 CPU scope와 동일
CACHE (default)200000003, 0000000cnode 0, node 1
NUMA200000003, 0000000cnode 0, node 1
SYSTEM10000000f모든 CPU, node -1
Per-CPUCPU별 normal/highpri pool에 mapping됩니다.
UnboundAffinity pod pool과 default system pool을 사용합니다.
Ordered모든 CPU에서 동일한 ordered pool을 사용합니다.

`wq_dump.py` 출력의 affinity pod 구성과 backing pool mapping을 구조화했습니다.

자세한 내용은 command help message를 참조하십시오.

Monitoring

695-725

Monitoring

`tools/workqueue/wq_monitor.py`로 workqueue operation을 monitor합니다.

$ tools/workqueue/wq_monitor.py events
                            total  infl  CPUtime  CPUhog CMW/RPR  mayday rescued
events                      18545     0      6.1       0       5       -       -
events_highpri                  8     0      0.0       0       0       -       -
events_long                     3     0      0.0       0       0       -       -
events_unbound              38306     0      0.1       -       7       -       -
events_freezable                0     0      0.0       0       0       -       -
events_power_efficient      29598     0      0.2       0       0       -       -
events_freezable_pwr_ef        10     0      0.0       0       0       -       -
sock_diag_events                0     0      0.0       0       0       -       -

                            total  infl  CPUtime  CPUhog CMW/RPR  mayday rescued
events                      18548     0      6.1       0       5       -       -
events_highpri                  8     0      0.0       0       0       -       -
events_long                     3     0      0.0       0       0       -       -
events_unbound              38322     0      0.1       -       7       -       -
events_freezable                0     0      0.0       0       0       -       -
events_power_efficient      29603     0      0.2       0       0       -       -
events_freezable_pwr_ef        10     0      0.0       0       0       -       -
sock_diag_events                0     0      0.0       0       0       -       -

...

자세한 내용은 command help message를 참조하십시오.

Debugging

726-765

Debugging

Work function은 generic worker thread가 실행하므로 잘못 동작하는 workqueue 사용자를 파악하려면 몇 가지 방법이 필요합니다.

Process list에서 worker thread는 다음처럼 나타납니다.

root      5671  0.0  0.0      0     0 ?        S    12:07   0:00 [kworker/0:1]
root      5672  0.0  0.0      0     0 ?        S    12:07   0:00 [kworker/1:2]
root      5673  0.0  0.0      0     0 ?        S    12:12   0:00 [kworker/0:0]
root      5674  0.0  0.0      0     0 ?        S    12:13   0:00 [kworker/1:0]

Kworker가 CPU를 너무 많이 사용한다면 가능한 문제는 두 종류입니다.

  • 어떤 항목이 빠르게 연속 schedule되는 경우
  • Work item 하나가 CPU cycle을 많이 소비하는 경우

첫 번째 문제는 tracing으로 추적할 수 있습니다.

$ echo workqueue:workqueue_queue_work > /sys/kernel/tracing/set_event
$ cat /sys/kernel/tracing/trace_pipe > out.txt
(wait a few secs)
^C

Work queueing을 busy loop로 수행하는 항목은 output을 지배하므로 work item function으로 원인을 찾을 수 있습니다.

두 번째 문제는 문제가 있는 worker thread의 stack trace를 확인하면 됩니다.

$ cat /proc/THE_OFFENDING_KWORKER/stack

Stack trace에서 work item function이 쉽게 보여야 합니다.

Non-reentrance 조건

766-783

Non-reentrance 조건

Work item이 queue된 뒤 다음 조건을 만족하면 workqueue는 그 item이 re-entrant하지 않음을 보장합니다.

  • Work function이 변경되지 않았습니다.
  • 누구도 work item을 다른 workqueue에 queue하지 않습니다.
  • Work item을 다시 초기화하지 않았습니다.

즉 위 조건을 만족하면 어느 시점에도 system 전체에서 worker 하나만 work item을 실행한다고 보장합니다.

Self function 안에서 같은 queue로 work item을 다시 queue하는 것은 이 조건을 깨지 않으므로 안전합니다. 그 외에는 work function 안에서 조건을 깰 때 주의해야 합니다.

Kernel inline documentation reference

784-789

Kernel inline documentation reference

Workqueue header와 implementation의 kernel-doc reference는 다음과 같습니다.

.. kernel-doc:: include/linux/workqueue.h
.. kernel-doc:: kernel/workqueue.c