xref: /haiku/src/system/kernel/low_resource_manager.cpp (revision 62f5ba006a08b0df30631375878effaf67ae5dbc)
1 /*
2  * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include <low_resource_manager.h>
9 
10 #include <new>
11 #include <signal.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 
15 #include <KernelExport.h>
16 
17 #include <condition_variable.h>
18 #include <elf.h>
19 #include <lock.h>
20 #include <sem.h>
21 #include <util/AutoLock.h>
22 #include <util/DoublyLinkedList.h>
23 #include <vm/vm_page.h>
24 #include <vm/vm_priv.h>
25 
26 
27 //#define TRACE_LOW_RESOURCE_MANAGER
28 #ifdef TRACE_LOW_RESOURCE_MANAGER
29 #	define TRACE(x)	dprintf x
30 #else
31 #	define TRACE(x) ;
32 #endif
33 
34 
35 struct low_resource_handler
36 		: public DoublyLinkedListLinkImpl<low_resource_handler> {
37 	low_resource_func	function;
38 	void*				data;
39 	uint32				resources;
40 	int32				priority;
41 };
42 
43 typedef DoublyLinkedList<low_resource_handler> HandlerList;
44 
45 
46 static const bigtime_t kLowResourceInterval = 3000000;	// 3 secs
47 static const bigtime_t kWarnResourceInterval = 500000;	// 0.5 secs
48 
49 // page limits
50 static const size_t kNotePagesLimit = VM_PAGE_RESERVE_USER * 4;
51 static const size_t kWarnPagesLimit = VM_PAGE_RESERVE_USER;
52 static const size_t kCriticalPagesLimit = VM_PAGE_RESERVE_SYSTEM;
53 
54 // memory limits
55 static const off_t kMinNoteMemoryLimit		= VM_MEMORY_RESERVE_USER * 4;
56 static const off_t kMinWarnMemoryLimit		= VM_MEMORY_RESERVE_USER;
57 static const off_t kMinCriticalMemoryLimit	= VM_MEMORY_RESERVE_SYSTEM;
58 static off_t sNoteMemoryLimit;
59 static off_t sWarnMemoryLimit;
60 static off_t sCriticalMemoryLimit;
61 
62 
63 static int32 sLowPagesState = B_NO_LOW_RESOURCE;
64 static int32 sLowMemoryState = B_NO_LOW_RESOURCE;
65 static int32 sLowSemaphoresState = B_NO_LOW_RESOURCE;
66 static int32 sLowSpaceState = B_NO_LOW_RESOURCE;
67 static uint32 sLowResources = 0;	// resources that are not B_NO_LOW_RESOURCE
68 static bigtime_t sLastMeasurement;
69 
70 static recursive_lock sLowResourceLock
71 	= RECURSIVE_LOCK_INITIALIZER("low resource");
72 static sem_id sLowResourceWaitSem;
73 static HandlerList sLowResourceHandlers;
74 
75 static ConditionVariable sLowResourceWaiterCondition;
76 
77 
78 static int32
79 low_resource_state_no_update(uint32 resources)
80 {
81 	int32 state = B_NO_LOW_RESOURCE;
82 
83 	if ((resources & B_KERNEL_RESOURCE_PAGES) != 0)
84 		state = max_c(state, sLowPagesState);
85 	if ((resources & B_KERNEL_RESOURCE_MEMORY) != 0)
86 		state = max_c(state, sLowMemoryState);
87 	if ((resources & B_KERNEL_RESOURCE_SEMAPHORES) != 0)
88 		state = max_c(state, sLowSemaphoresState);
89 	if ((resources & B_KERNEL_RESOURCE_ADDRESS_SPACE) != 0)
90 		state = max_c(state, sLowSpaceState);
91 
92 	return state;
93 }
94 
95 
96 /*!	Calls low resource handlers for the given resources.
97 	sLowResourceLock must be held.
98 */
99 static void
100 call_handlers(uint32 lowResources)
101 {
102 	if (sLowResourceHandlers.IsEmpty())
103 		return;
104 
105 	// Add a marker, so we can drop the lock while calling the handlers and
106 	// still iterate safely.
107 	low_resource_handler marker;
108 	sLowResourceHandlers.Insert(&marker, false);
109 
110 	while (low_resource_handler* handler
111 			= sLowResourceHandlers.GetNext(&marker)) {
112 		// swap with handler
113 		sLowResourceHandlers.Swap(&marker, handler);
114 		marker.priority = handler->priority;
115 
116 		int32 resources = handler->resources & lowResources;
117 		if (resources != 0) {
118 			recursive_lock_unlock(&sLowResourceLock);
119 			handler->function(handler->data, resources,
120 				low_resource_state_no_update(resources));
121 			recursive_lock_lock(&sLowResourceLock);
122 		}
123 	}
124 
125 	// remove marker
126 	sLowResourceHandlers.Remove(&marker);
127 }
128 
129 
130 static void
131 compute_state(void)
132 {
133 	sLastMeasurement = system_time();
134 
135 	sLowResources = B_ALL_KERNEL_RESOURCES;
136 
137 	// free pages state
138 	uint32 freePages = vm_page_num_free_pages();
139 
140 	if (freePages < kCriticalPagesLimit) {
141 		sLowPagesState = B_LOW_RESOURCE_CRITICAL;
142 	} else if (freePages < kWarnPagesLimit) {
143 		sLowPagesState = B_LOW_RESOURCE_WARNING;
144 	} else if (freePages < kNotePagesLimit) {
145 		sLowPagesState = B_LOW_RESOURCE_NOTE;
146 	} else {
147 		sLowPagesState = B_NO_LOW_RESOURCE;
148 		sLowResources &= ~B_KERNEL_RESOURCE_PAGES;
149 	}
150 
151 	// free memory state
152 	off_t freeMemory = vm_available_not_needed_memory();
153 
154 	if (freeMemory < sCriticalMemoryLimit) {
155 		sLowMemoryState = B_LOW_RESOURCE_CRITICAL;
156 	} else if (freeMemory < sWarnMemoryLimit) {
157 		sLowMemoryState = B_LOW_RESOURCE_WARNING;
158 	} else if (freeMemory < sNoteMemoryLimit) {
159 		sLowMemoryState = B_LOW_RESOURCE_NOTE;
160 	} else {
161 		sLowMemoryState = B_NO_LOW_RESOURCE;
162 		sLowResources &= ~B_KERNEL_RESOURCE_MEMORY;
163 	}
164 
165 	// free semaphores state
166 	uint32 maxSems = sem_max_sems();
167 	uint32 freeSems = maxSems - sem_used_sems();
168 
169 	if (freeSems < maxSems >> 16) {
170 		sLowSemaphoresState = B_LOW_RESOURCE_CRITICAL;
171 	} else if (freeSems < maxSems >> 8) {
172 		sLowSemaphoresState = B_LOW_RESOURCE_WARNING;
173 	} else if (freeSems < maxSems >> 4) {
174 		sLowSemaphoresState = B_LOW_RESOURCE_NOTE;
175 	} else {
176 		sLowSemaphoresState = B_NO_LOW_RESOURCE;
177 		sLowResources &= ~B_KERNEL_RESOURCE_SEMAPHORES;
178 	}
179 
180 	// free kernel address space state
181 	// TODO: this should take fragmentation into account
182 	size_t maxSpace = KERNEL_SIZE;
183 	size_t freeSpace = vm_kernel_address_space_left();
184 
185 	if (freeSpace < maxSpace >> 16)
186 		sLowSpaceState = B_LOW_RESOURCE_CRITICAL;
187 	if (freeSpace < maxSpace >> 8)
188 		sLowSpaceState = B_LOW_RESOURCE_WARNING;
189 	if (freeSpace < maxSpace >> 4)
190 		sLowSpaceState = B_LOW_RESOURCE_NOTE;
191 	else {
192 		sLowSpaceState = B_NO_LOW_RESOURCE;
193 		sLowResources &= ~B_KERNEL_RESOURCE_ADDRESS_SPACE;
194 	}
195 }
196 
197 
198 static status_t
199 low_resource_manager(void*)
200 {
201 	bigtime_t timeout = kLowResourceInterval;
202 	while (true) {
203 		int32 state = low_resource_state_no_update(B_ALL_KERNEL_RESOURCES);
204 		if (state != B_LOW_RESOURCE_CRITICAL) {
205 			acquire_sem_etc(sLowResourceWaitSem, 1, B_RELATIVE_TIMEOUT,
206 				timeout);
207 		}
208 
209 		RecursiveLocker _(&sLowResourceLock);
210 
211 		compute_state();
212 		state = low_resource_state_no_update(B_ALL_KERNEL_RESOURCES);
213 
214 		TRACE(("low_resource_manager: state = %ld, %ld free pages, %lld free "
215 			"memory, %lu free semaphores\n", state, vm_page_num_free_pages(),
216 			vm_available_not_needed_memory(),
217 			sem_max_sems() - sem_used_sems()));
218 
219 		if (state < B_LOW_RESOURCE_NOTE)
220 			continue;
221 
222 		call_handlers(sLowResources);
223 
224 		if (state == B_LOW_RESOURCE_WARNING)
225 			timeout = kWarnResourceInterval;
226 		else
227 			timeout = kLowResourceInterval;
228 
229 		sLowResourceWaiterCondition.NotifyAll();
230 	}
231 	return 0;
232 }
233 
234 
235 static const char*
236 state_to_string(uint32 state)
237 {
238 	switch (state) {
239 		case B_LOW_RESOURCE_CRITICAL:
240 			return "critical";
241 		case B_LOW_RESOURCE_WARNING:
242 			return "warning";
243 		case B_LOW_RESOURCE_NOTE:
244 			return "note";
245 
246 		default:
247 			return "normal";
248 	}
249 }
250 
251 
252 static int
253 dump_handlers(int argc, char** argv)
254 {
255 	kprintf("current state: %c%c%c%c\n",
256 		(sLowResources & B_KERNEL_RESOURCE_PAGES) != 0 ? 'p' : '-',
257 		(sLowResources & B_KERNEL_RESOURCE_MEMORY) != 0 ? 'm' : '-',
258 		(sLowResources & B_KERNEL_RESOURCE_SEMAPHORES) != 0 ? 's' : '-',
259 		(sLowResources & B_KERNEL_RESOURCE_ADDRESS_SPACE) != 0 ? 'a' : '-');
260 	kprintf("  pages:  %s\n", state_to_string(sLowPagesState));
261 	kprintf("  memory: %s\n", state_to_string(sLowMemoryState));
262 	kprintf("  sems:   %s\n", state_to_string(sLowSemaphoresState));
263 	kprintf("  aspace: %s\n\n", state_to_string(sLowSpaceState));
264 
265 	HandlerList::Iterator iterator = sLowResourceHandlers.GetIterator();
266 	kprintf("function    data         resources  prio  function-name\n");
267 
268 	while (iterator.HasNext()) {
269 		low_resource_handler *handler = iterator.Next();
270 
271 		const char* symbol = NULL;
272 		elf_debug_lookup_symbol_address((addr_t)handler->function, NULL,
273 			&symbol, NULL, NULL);
274 
275 		char resources[16];
276 		snprintf(resources, sizeof(resources), "%c %c %c",
277 			handler->resources & B_KERNEL_RESOURCE_PAGES ? 'p' : ' ',
278 			handler->resources & B_KERNEL_RESOURCE_MEMORY ? 'm' : ' ',
279 			handler->resources & B_KERNEL_RESOURCE_SEMAPHORES ? 's' : ' ');
280 
281 		kprintf("%p  %p   %s      %4ld  %s\n", handler->function, handler->data,
282 			resources, handler->priority, symbol);
283 	}
284 
285 	return 0;
286 }
287 
288 
289 //	#pragma mark - private kernel API
290 
291 
292 /*!	Notifies the low resource manager that a resource is lacking. If \a flags
293 	and \a timeout specify a timeout, the function will wait until the low
294 	resource manager has finished its next iteration of calling low resource
295 	handlers, or until the timeout occurs (whichever happens first).
296 */
297 void
298 low_resource(uint32 resource, uint64 requirements, uint32 flags, uint32 timeout)
299 {
300 	// TODO: take requirements into account
301 
302 	switch (resource) {
303 		case B_KERNEL_RESOURCE_PAGES:
304 		case B_KERNEL_RESOURCE_MEMORY:
305 		case B_KERNEL_RESOURCE_SEMAPHORES:
306 		case B_KERNEL_RESOURCE_ADDRESS_SPACE:
307 			break;
308 	}
309 
310 	release_sem(sLowResourceWaitSem);
311 
312 	if ((flags & B_RELATIVE_TIMEOUT) == 0 || timeout > 0)
313 		sLowResourceWaiterCondition.Wait(flags, timeout);
314 }
315 
316 
317 int32
318 low_resource_state(uint32 resources)
319 {
320 	recursive_lock_lock(&sLowResourceLock);
321 
322 	if (system_time() - sLastMeasurement > 500000)
323 		compute_state();
324 
325 	int32 state = low_resource_state_no_update(resources);
326 
327 	recursive_lock_unlock(&sLowResourceLock);
328 
329 	return state;
330 }
331 
332 
333 status_t
334 low_resource_manager_init(void)
335 {
336 	new(&sLowResourceHandlers) HandlerList;
337 		// static initializers do not work in the kernel,
338 		// so we have to do it here, manually
339 
340 	sLowResourceWaiterCondition.Init(NULL, "low resource waiters");
341 
342 	// compute the free memory limits
343 	off_t totalMemory = (off_t)vm_page_num_pages() * B_PAGE_SIZE;
344 	sNoteMemoryLimit = totalMemory / 16;
345 	if (sNoteMemoryLimit < kMinNoteMemoryLimit) {
346 		sNoteMemoryLimit = kMinNoteMemoryLimit;
347 		sWarnMemoryLimit = kMinWarnMemoryLimit;
348 		sCriticalMemoryLimit = kMinCriticalMemoryLimit;
349 	} else {
350 		sWarnMemoryLimit = totalMemory / 64;
351 		sCriticalMemoryLimit = totalMemory / 256;
352 	}
353 
354 	return B_OK;
355 }
356 
357 
358 status_t
359 low_resource_manager_init_post_thread(void)
360 {
361 	sLowResourceWaitSem = create_sem(0, "low resource wait");
362 	if (sLowResourceWaitSem < B_OK)
363 		return sLowResourceWaitSem;
364 
365 	thread_id thread = spawn_kernel_thread(&low_resource_manager,
366 		"low resource manager", B_LOW_PRIORITY, NULL);
367 	send_signal_etc(thread, SIGCONT, B_DO_NOT_RESCHEDULE);
368 
369 	add_debugger_command("low_resource", &dump_handlers,
370 		"Dump list of low resource handlers");
371 	return B_OK;
372 }
373 
374 
375 status_t
376 unregister_low_resource_handler(low_resource_func function, void* data)
377 {
378 	TRACE(("unregister_low_resource_handler(function = %p, data = %p)\n",
379 		function, data));
380 
381 	RecursiveLocker locker(&sLowResourceLock);
382 	HandlerList::Iterator iterator = sLowResourceHandlers.GetIterator();
383 
384 	while (iterator.HasNext()) {
385 		low_resource_handler* handler = iterator.Next();
386 
387 		if (handler->function == function && handler->data == data) {
388 			sLowResourceHandlers.Remove(handler);
389 			free(handler);
390 			return B_OK;
391 		}
392 	}
393 
394 	return B_ENTRY_NOT_FOUND;
395 }
396 
397 
398 /*! Registers a low resource handler. The higher the \a priority, the earlier
399 	the handler will be called in low resource situations.
400 */
401 status_t
402 register_low_resource_handler(low_resource_func function, void* data,
403 	uint32 resources, int32 priority)
404 {
405 	TRACE(("register_low_resource_handler(function = %p, data = %p)\n",
406 		function, data));
407 
408 	low_resource_handler *newHandler = (low_resource_handler*)malloc(
409 		sizeof(low_resource_handler));
410 	if (newHandler == NULL)
411 		return B_NO_MEMORY;
412 
413 	newHandler->function = function;
414 	newHandler->data = data;
415 	newHandler->resources = resources;
416 	newHandler->priority = priority;
417 
418 	RecursiveLocker locker(&sLowResourceLock);
419 
420 	// sort it in after priority (higher priority comes first)
421 
422 	HandlerList::ReverseIterator iterator
423 		= sLowResourceHandlers.GetReverseIterator();
424 	low_resource_handler* last = NULL;
425 	while (iterator.HasNext()) {
426 		low_resource_handler *handler = iterator.Next();
427 
428 		if (handler->priority >= priority) {
429 			sLowResourceHandlers.Insert(last, newHandler);
430 			return B_OK;
431 		}
432 		last = handler;
433 	}
434 
435 	sLowResourceHandlers.Add(newHandler, false);
436 	return B_OK;
437 }
438