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