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

Linux 6.18.37 · Core API

Memory hotplug

Memory 및 NUMA node hotplug notifier event, callback contract와 취소 흐름, device_hotplug_lock과 mem_hotplug_lock의 lock ordering을 설명합니다.

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

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

1. 요약·해설

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

요약과 해설

memory-hotplug.rst:1-195

Memory notifier는 online 또는 offline 작업의 시작, 성공, 취소 단계를 subsystem에 알립니다. NUMA node notifier는 node에 첫 memory가 추가되거나 마지막 memory가 제거되는 경계를 알립니다. Callback은 priority 순서로 실행되며 중간 consumer 실패로 GOING event 없이 CANCEL event를 받을 수 있으므로 순서를 가정하면 안 됩니다.

`NOTIFY_BAD`와 `NOTIFY_STOP`은 notification queue 진행을 중단합니다. 특히 완료 단계에서는 memory_hotplug code가 rollback할 수 없으므로 callback 실패를 반환해서는 안 됩니다.

일반 RAM hotplug에서는 userspace의 sysfs request와 CPU hotplug를 직렬화하고 반대 순서의 `device_lock()`과 `mem_hotplug_lock` 획득으로 생길 수 있는 inversion을 막기 위해 `device_hotplug_lock`이 필요합니다. 전체 memory hotplug 변경은 write mode의 `mem_hotplug_lock`으로 직렬화합니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. _memory_hotplug:
2
3 ==============
4 Memory hotplug
5 ==============
6
7 Memory hotplug event notifier
8 =============================
9
10 Hotplugging events are sent to a notification queue.
11
12 Memory notifier
13 ----------------
14
15 There are six types of notification defined in ``include/linux/memory.h``:
16
17 MEM_GOING_ONLINE
18 Generated before new memory becomes available in order to be able to
19 prepare subsystems to handle memory. The page allocator is still unable
20 to allocate from the new memory.
21
22 MEM_CANCEL_ONLINE
23 Generated if MEM_GOING_ONLINE fails.
24
25 MEM_ONLINE
26 Generated when memory has successfully brought online. The callback may
27 allocate pages from the new memory.
28
29 MEM_GOING_OFFLINE
30 Generated to begin the process of offlining memory. Allocations are no
31 longer possible from the memory but some of the memory to be offlined
32 is still in use. The callback can be used to free memory known to a
33 subsystem from the indicated memory block.
34
35 MEM_CANCEL_OFFLINE
36 Generated if MEM_GOING_OFFLINE fails. Memory is available again from
37 the memory block that we attempted to offline.
38
39 MEM_OFFLINE
40 Generated after offlining memory is complete.
41
42 A callback routine can be registered by calling::
43
44 hotplug_memory_notifier(callback_func, priority)
45
46 Callback functions with higher values of priority are called before callback
47 functions with lower values.
48
49 A callback function must have the following prototype::
50
51 int callback_func(
52 struct notifier_block *self, unsigned long action, void *arg);
53
54 The first argument of the callback function (self) is a pointer to the block
55 of the notifier chain that points to the callback function itself.
56 The second argument (action) is one of the event types described above.
57 The third argument (arg) passes a pointer of struct memory_notify::
58
59 struct memory_notify {
60 unsigned long start_pfn;
61 unsigned long nr_pages;
62 }
63
64 - start_pfn is start_pfn of online/offline memory.
65 - nr_pages is # of pages of online/offline memory.
66
67 It is possible to get notified for MEM_CANCEL_ONLINE without having been notified
68 for MEM_GOING_ONLINE, and the same applies to MEM_CANCEL_OFFLINE and
69 MEM_GOING_OFFLINE.
70 This can happen when a consumer fails, meaning we break the callchain and we
71 stop calling the remaining consumers of the notifier.
72 It is then important that users of memory_notify make no assumptions and get
73 prepared to handle such cases.
74
75 The callback routine shall return one of the values
76 NOTIFY_DONE, NOTIFY_OK, NOTIFY_BAD, NOTIFY_STOP
77 defined in ``include/linux/notifier.h``
78
79 NOTIFY_DONE and NOTIFY_OK have no effect on the further processing.
80
81 NOTIFY_BAD is used as response to the MEM_GOING_ONLINE, MEM_GOING_OFFLINE,
82 MEM_ONLINE, or MEM_OFFLINE action to cancel hotplugging. It stops
83 further processing of the notification queue.
84
85 NOTIFY_STOP stops further processing of the notification queue.
86
87 Numa node notifier
88 ------------------
89
90 There are six types of notification defined in ``include/linux/node.h``:
91
92 NODE_ADDING_FIRST_MEMORY
93 Generated before memory becomes available to this node for the first time.
94
95 NODE_CANCEL_ADDING_FIRST_MEMORY
96 Generated if NODE_ADDING_FIRST_MEMORY fails.
97
98 NODE_ADDED_FIRST_MEMORY
99 Generated when memory has become available fo this node for the first time.
100
101 NODE_REMOVING_LAST_MEMORY
102 Generated when the last memory available to this node is about to be offlined.
103
104 NODE_CANCEL_REMOVING_LAST_MEMORY
105 Generated when NODE_CANCEL_REMOVING_LAST_MEMORY fails.
106
107 NODE_REMOVED_LAST_MEMORY
108 Generated when the last memory available to this node has been offlined.
109
110 A callback routine can be registered by calling::
111
112 hotplug_node_notifier(callback_func, priority)
113
114 Callback functions with higher values of priority are called before callback
115 functions with lower values.
116
117 A callback function must have the following prototype::
118
119 int callback_func(
120
121 struct notifier_block *self, unsigned long action, void *arg);
122
123 The first argument of the callback function (self) is a pointer to the block
124 of the notifier chain that points to the callback function itself.
125 The second argument (action) is one of the event types described above.
126 The third argument (arg) passes a pointer of struct node_notify::
127
128 struct node_notify {
129 int nid;
130 }
131
132 - nid is the node we are adding or removing memory to.
133
134 It is possible to get notified for NODE_CANCEL_ADDING_FIRST_MEMORY without
135 having been notified for NODE_ADDING_FIRST_MEMORY, and the same applies to
136 NODE_CANCEL_REMOVING_LAST_MEMORY and NODE_REMOVING_LAST_MEMORY.
137 This can happen when a consumer fails, meaning we break the callchain and we
138 stop calling the remaining consumers of the notifier.
139 It is then important that users of node_notify make no assumptions and get
140 prepared to handle such cases.
141
142 The callback routine shall return one of the values
143 NOTIFY_DONE, NOTIFY_OK, NOTIFY_BAD, NOTIFY_STOP
144 defined in ``include/linux/notifier.h``
145
146 NOTIFY_DONE and NOTIFY_OK have no effect on the further processing.
147
148 NOTIFY_BAD is used as response to the NODE_ADDING_FIRST_MEMORY,
149 NODE_REMOVING_LAST_MEMORY, NODE_ADDED_FIRST_MEMORY or
150 NODE_REMOVED_LAST_MEMORY action to cancel hotplugging.
151 It stops further processing of the notification queue.
152
153 NOTIFY_STOP stops further processing of the notification queue.
154
155 Please note that we should not fail for NODE_ADDED_FIRST_MEMORY /
156 NODE_REMOVED_FIRST_MEMORY, as memory_hotplug code cannot rollback at that
157 point anymore.
158
159 Locking Internals
160 =================
161
162 When adding/removing memory that uses memory block devices (i.e. ordinary RAM),
163 the device_hotplug_lock should be held to:
164
165 - synchronize against online/offline requests (e.g. via sysfs). This way, memory
166 block devices can only be accessed (.online/.state attributes) by user
167 space once memory has been fully added. And when removing memory, we
168 know nobody is in critical sections.
169 - synchronize against CPU hotplug and similar (e.g. relevant for ACPI and PPC)
170
171 Especially, there is a possible lock inversion that is avoided using
172 device_hotplug_lock when adding memory and user space tries to online that
173 memory faster than expected:
174
175 - device_online() will first take the device_lock(), followed by
176 mem_hotplug_lock
177 - add_memory_resource() will first take the mem_hotplug_lock, followed by
178 the device_lock() (while creating the devices, during bus_add_device()).
179
180 As the device is visible to user space before taking the device_lock(), this
181 can result in a lock inversion.
182
183 onlining/offlining of memory should be done via device_online()/
184 device_offline() - to make sure it is properly synchronized to actions
185 via sysfs. Holding device_hotplug_lock is advised (to e.g. protect online_type)
186
187 When adding/removing/onlining/offlining memory or adding/removing
188 heterogeneous/device memory, we should always hold the mem_hotplug_lock in
189 write mode to serialise memory hotplug (e.g. access to global/zone
190 variables).
191
192 In addition, mem_hotplug_lock (in contrast to device_hotplug_lock) in read
193 mode allows for a quite efficient get_online_mems/put_online_mems
194 implementation, so code accessing memory can protect from that memory
195 vanishing.
196

3. 한국어 전문 번역

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

Memory hotplug notifier 개요

1-11

Memory hotplug anchor는 `memory_hotplug`입니다.

Memory hotplug

Memory hotplug event notifier

Hotplug event는 notification queue로 전송됩니다.

Memory notifier event

12-41

Memory notifier

`include/linux/memory.h`에는 여섯 종류의 notification이 정의되어 있습니다.

  • `MEM_GOING_ONLINE`: 새 memory가 사용 가능해지기 전에 생성되어 subsystem이 memory를 처리할 준비를 하게 합니다. 아직 page allocator는 새 memory에서 할당할 수 없습니다.
  • `MEM_CANCEL_ONLINE`: `MEM_GOING_ONLINE`이 실패하면 생성됩니다.
  • `MEM_ONLINE`: Memory를 성공적으로 online한 뒤 생성됩니다. Callback은 새 memory에서 page를 할당할 수 있습니다.
  • `MEM_GOING_OFFLINE`: Memory offlining 절차를 시작할 때 생성됩니다. 해당 memory에서 더는 allocation할 수 없지만 offline할 memory 일부는 아직 사용 중입니다. Callback은 표시된 memory block에서 subsystem이 알고 있는 memory를 해제하는 데 사용할 수 있습니다.
  • `MEM_CANCEL_OFFLINE`: `MEM_GOING_OFFLINE`이 실패하면 생성됩니다. Offline을 시도했던 memory block을 다시 사용할 수 있습니다.
  • `MEM_OFFLINE`: Memory offlining이 완료된 뒤 생성됩니다.

Memory notifier callback

42-86

다음 호출로 callback routine을 등록할 수 있습니다.

hotplug_memory_notifier(callback_func, priority)

Priority 값이 높은 callback function이 낮은 callback function보다 먼저 호출됩니다.

Callback function은 다음 prototype을 가져야 합니다.

int callback_func(
  struct notifier_block *self, unsigned long action, void *arg);

첫 번째 argument `self`는 callback function 자체를 가리키는 notifier chain block의 pointer입니다. 두 번째 argument `action`은 앞에서 설명한 event type 중 하나입니다. 세 번째 argument `arg`는 `struct memory_notify` pointer를 전달합니다.

struct memory_notify {
        unsigned long start_pfn;
        unsigned long nr_pages;
}
  • `start_pfn`은 online 또는 offline memory의 시작 PFN입니다.
  • `nr_pages`는 online 또는 offline memory의 page 수입니다.

`MEM_GOING_ONLINE` notification을 받지 않았는데 `MEM_CANCEL_ONLINE`을 받을 수 있으며, `MEM_GOING_OFFLINE`과 `MEM_CANCEL_OFFLINE`에도 같은 상황이 적용됩니다. Consumer 하나가 실패하면 callchain을 끊고 남은 notifier consumer 호출을 중단하기 때문에 이런 일이 생길 수 있습니다. 따라서 `memory_notify` 사용자는 순서를 가정하지 말고 이러한 경우를 처리할 준비를 해야 합니다.

Callback routine은 `include/linux/notifier.h`에 정의된 `NOTIFY_DONE`, `NOTIFY_OK`, `NOTIFY_BAD`, `NOTIFY_STOP` 중 하나를 반환해야 합니다.

`NOTIFY_DONE`과 `NOTIFY_OK`는 이후 처리에 영향을 주지 않습니다. `NOTIFY_BAD`는 `MEM_GOING_ONLINE`, `MEM_GOING_OFFLINE`, `MEM_ONLINE`, `MEM_OFFLINE` action에 응답하여 hotplug를 취소하고 notification queue의 후속 처리를 중단합니다. `NOTIFY_STOP`도 notification queue의 후속 처리를 중단합니다.

NUMA node notifier event

87-109

NUMA node notifier

`include/linux/node.h`에는 여섯 종류의 notification이 정의되어 있습니다.

  • `NODE_ADDING_FIRST_MEMORY`: 이 node에서 처음으로 memory가 사용 가능해지기 전에 생성됩니다.
  • `NODE_CANCEL_ADDING_FIRST_MEMORY`: `NODE_ADDING_FIRST_MEMORY`가 실패하면 생성됩니다.
  • `NODE_ADDED_FIRST_MEMORY`: 이 node에서 처음으로 memory를 사용할 수 있게 되면 생성됩니다.
  • `NODE_REMOVING_LAST_MEMORY`: 이 node에서 사용할 수 있는 마지막 memory를 곧 offline하려 할 때 생성됩니다.
  • `NODE_CANCEL_REMOVING_LAST_MEMORY`: 원문 설명대로 `NODE_CANCEL_REMOVING_LAST_MEMORY`가 실패할 때 생성되는 event입니다.
  • `NODE_REMOVED_LAST_MEMORY`: 이 node에서 사용할 수 있던 마지막 memory를 offline한 뒤 생성됩니다.

NUMA node notifier callback

110-158

다음 호출로 callback routine을 등록할 수 있습니다.

hotplug_node_notifier(callback_func, priority)

Priority 값이 높은 callback function이 낮은 callback function보다 먼저 호출됩니다.

Callback function은 다음 prototype을 가져야 합니다.

int callback_func(

  struct notifier_block *self, unsigned long action, void *arg);

첫 번째 argument `self`는 callback function 자체를 가리키는 notifier chain block의 pointer입니다. 두 번째 argument `action`은 앞에서 설명한 event type 중 하나입니다. 세 번째 argument `arg`는 `struct node_notify` pointer를 전달합니다.

struct node_notify {
        int nid;
}
  • `nid`는 memory를 추가하거나 제거할 node입니다.

`NODE_ADDING_FIRST_MEMORY` notification을 받지 않았는데 `NODE_CANCEL_ADDING_FIRST_MEMORY`을 받을 수 있으며, `NODE_REMOVING_LAST_MEMORY`와 `NODE_CANCEL_REMOVING_LAST_MEMORY`에도 같은 상황이 적용됩니다. Consumer 하나가 실패하면 callchain을 끊고 남은 notifier consumer 호출을 중단하기 때문에 생길 수 있습니다. 따라서 `node_notify` 사용자는 순서를 가정하지 말고 이러한 경우를 처리할 준비를 해야 합니다.

Callback routine은 `include/linux/notifier.h`에 정의된 `NOTIFY_DONE`, `NOTIFY_OK`, `NOTIFY_BAD`, `NOTIFY_STOP` 중 하나를 반환해야 합니다. `NOTIFY_DONE`과 `NOTIFY_OK`는 이후 처리에 영향을 주지 않습니다.

`NOTIFY_BAD`는 `NODE_ADDING_FIRST_MEMORY`, `NODE_REMOVING_LAST_MEMORY`, `NODE_ADDED_FIRST_MEMORY`, `NODE_REMOVED_LAST_MEMORY` action에 응답하여 hotplug를 취소하고 notification queue의 후속 처리를 중단합니다. `NOTIFY_STOP`도 후속 처리를 중단합니다.

`NODE_ADDED_FIRST_MEMORY` 또는 원문에 표기된 `NODE_REMOVED_FIRST_MEMORY` 단계에서는 memory_hotplug code가 더 이상 rollback할 수 없으므로 실패해서는 안 됩니다.

Memory hotplug locking 내부 동작

159-195

Locking Internals

Memory block device를 사용하는 memory, 즉 일반 RAM을 추가하거나 제거할 때는 다음 동기화를 위해 `device_hotplug_lock`을 보유해야 합니다.

  • Sysfs 등을 통한 online 또는 offline request와 동기화합니다. 그러면 memory를 완전히 추가한 뒤에만 userspace가 memory block device의 `.online`과 `.state` attribute에 접근할 수 있습니다. Memory를 제거할 때도 다른 실행 흐름이 critical section 안에 없다는 것을 알 수 있습니다.
  • CPU hotplug와 유사한 작업에 대해 동기화합니다. ACPI와 PPC 등이 관련됩니다.

특히 memory를 추가하는 중 userspace가 예상보다 빨리 그 memory를 online하려 할 때 생길 수 있는 lock inversion을 `device_hotplug_lock`으로 방지합니다.

  • `device_online()`은 먼저 `device_lock()`을 획득한 뒤 `mem_hotplug_lock`을 획득합니다.
  • `add_memory_resource()`는 먼저 `mem_hotplug_lock`을 획득한 뒤 device를 만드는 `bus_add_device()` 과정에서 `device_lock()`을 획득합니다.

Device는 `device_lock()`을 획득하기 전에 userspace에 보이므로 이 순서 차이로 lock inversion이 발생할 수 있습니다.

Memory onlining과 offlining은 sysfs action과 올바르게 동기화되도록 `device_online()`과 `device_offline()`을 통해 수행해야 합니다. 예를 들어 `online_type`을 보호하려면 `device_hotplug_lock`을 보유하는 것이 권장됩니다.

Memory를 추가, 제거, online, offline하거나 heterogeneous 또는 device memory를 추가하고 제거할 때는 항상 write mode의 `mem_hotplug_lock`을 보유하여 global 또는 zone variable 접근 같은 memory hotplug operation을 직렬화해야 합니다.

또한 read mode의 `mem_hotplug_lock`은 `device_hotplug_lock`과 달리 효율적인 `get_online_mems()`와 `put_online_mems()` 구현을 가능하게 합니다. Memory에 접근하는 code는 이를 사용해 접근 중인 memory가 사라지지 않도록 보호할 수 있습니다.