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
34struct 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
42typedef DoublyLinkedList<low_resource_handler> HandlerList;
43
44
45static const bigtime_t kLowResourceInterval = 3000000;	// 3 secs
46static const bigtime_t kWarnResourceInterval = 500000;	// 0.5 secs
47
48// page limits
49static const size_t kNotePagesLimit = VM_PAGE_RESERVE_USER * 4;
50static const size_t kWarnPagesLimit = VM_PAGE_RESERVE_USER;
51static const size_t kCriticalPagesLimit = VM_PAGE_RESERVE_SYSTEM;
52
53// memory limits
54static const off_t kMinNoteMemoryLimit		= VM_MEMORY_RESERVE_USER * 4;
55static const off_t kMinWarnMemoryLimit		= VM_MEMORY_RESERVE_USER;
56static const off_t kMinCriticalMemoryLimit	= VM_MEMORY_RESERVE_SYSTEM;
57static off_t sNoteMemoryLimit;
58static off_t sWarnMemoryLimit;
59static off_t sCriticalMemoryLimit;
60
61// address space limits
62static const size_t kMinNoteSpaceLimit		= 128 * 1024 * 1024;
63static const size_t kMinWarnSpaceLimit		= 64 * 1024 * 1024;
64static const size_t kMinCriticalSpaceLimit	= 32 * 1024 * 1024;
65
66
67static int32 sLowPagesState = B_NO_LOW_RESOURCE;
68static int32 sLowMemoryState = B_NO_LOW_RESOURCE;
69static int32 sLowSemaphoresState = B_NO_LOW_RESOURCE;
70static int32 sLowSpaceState = B_NO_LOW_RESOURCE;
71static uint32 sLowResources = 0;	// resources that are not B_NO_LOW_RESOURCE
72static bigtime_t sLastMeasurement;
73
74static recursive_lock sLowResourceLock
75	= RECURSIVE_LOCK_INITIALIZER("low resource");
76static sem_id sLowResourceWaitSem;
77static HandlerList sLowResourceHandlers;
78
79static ConditionVariable sLowResourceWaiterCondition;
80
81
82static const char*
83state_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
99static int32
100low_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*/
120static void
121call_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
151static void
152compute_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
242static status_t
243low_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
280static int
281dump_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*/
330void
331low_resource(uint32 resource, uint64 requirements, uint32 flags, uint32 timeout)
332{
333	switch (resource) {
334		case B_KERNEL_RESOURCE_PAGES:
335			if (requirements <= kCriticalPagesLimit)
336				sLowPagesState = B_LOW_RESOURCE_CRITICAL;
337			else if (requirements <= kWarnPagesLimit)
338				sLowPagesState = B_LOW_RESOURCE_WARNING;
339			else
340				sLowPagesState = B_LOW_RESOURCE_NOTE;
341			break;
342
343		case B_KERNEL_RESOURCE_MEMORY: {
344			const off_t required = requirements;
345			if (required <= sCriticalMemoryLimit)
346				sLowMemoryState = B_LOW_RESOURCE_CRITICAL;
347			else if (required <= sWarnMemoryLimit)
348				sLowMemoryState = B_LOW_RESOURCE_WARNING;
349			else
350				sLowMemoryState = B_LOW_RESOURCE_NOTE;
351			break;
352		}
353
354		case B_KERNEL_RESOURCE_SEMAPHORES:
355			if (requirements <= 4)
356				sLowSemaphoresState = B_LOW_RESOURCE_CRITICAL;
357			else if (requirements <= 32)
358				sLowSemaphoresState = B_LOW_RESOURCE_WARNING;
359			else
360				sLowSemaphoresState = B_LOW_RESOURCE_NOTE;
361			break;
362
363		case B_KERNEL_RESOURCE_ADDRESS_SPACE:
364			if (requirements <= (kCriticalPagesLimit * B_PAGE_SIZE))
365				sLowSpaceState = B_LOW_RESOURCE_CRITICAL;
366			else if (requirements <= (kWarnPagesLimit * B_PAGE_SIZE))
367				sLowSpaceState = B_LOW_RESOURCE_WARNING;
368			else
369				sLowSpaceState = B_LOW_RESOURCE_NOTE;
370			break;
371	}
372
373	release_sem(sLowResourceWaitSem);
374
375	if ((flags & B_RELATIVE_TIMEOUT) == 0 || timeout > 0)
376		sLowResourceWaiterCondition.Wait(flags, timeout);
377}
378
379
380int32
381low_resource_state(uint32 resources)
382{
383	recursive_lock_lock(&sLowResourceLock);
384
385	if (system_time() - sLastMeasurement > 500000)
386		compute_state();
387
388	int32 state = low_resource_state_no_update(resources);
389
390	recursive_lock_unlock(&sLowResourceLock);
391
392	return state;
393}
394
395
396status_t
397low_resource_manager_init(void)
398{
399	new(&sLowResourceHandlers) HandlerList;
400		// static initializers do not work in the kernel,
401		// so we have to do it here, manually
402
403	sLowResourceWaiterCondition.Init(NULL, "low resource waiters");
404
405	// compute the free memory limits
406	off_t totalMemory = (off_t)vm_page_num_pages() * B_PAGE_SIZE;
407	sNoteMemoryLimit = totalMemory / 16;
408	if (sNoteMemoryLimit < kMinNoteMemoryLimit) {
409		sNoteMemoryLimit = kMinNoteMemoryLimit;
410		sWarnMemoryLimit = kMinWarnMemoryLimit;
411		sCriticalMemoryLimit = kMinCriticalMemoryLimit;
412	} else {
413		sWarnMemoryLimit = totalMemory / 64;
414		sCriticalMemoryLimit = totalMemory / 256;
415	}
416
417	return B_OK;
418}
419
420
421status_t
422low_resource_manager_init_post_thread(void)
423{
424	sLowResourceWaitSem = create_sem(0, "low resource wait");
425	if (sLowResourceWaitSem < B_OK)
426		return sLowResourceWaitSem;
427
428	thread_id thread = spawn_kernel_thread(&low_resource_manager,
429		"low resource manager", B_LOW_PRIORITY, NULL);
430	resume_thread(thread);
431
432	add_debugger_command("low_resource", &dump_handlers,
433		"Dump list of low resource handlers");
434	return B_OK;
435}
436
437
438status_t
439unregister_low_resource_handler(low_resource_func function, void* data)
440{
441	TRACE(("unregister_low_resource_handler(function = %p, data = %p)\n",
442		function, data));
443
444	RecursiveLocker locker(&sLowResourceLock);
445	HandlerList::Iterator iterator = sLowResourceHandlers.GetIterator();
446
447	while (iterator.HasNext()) {
448		low_resource_handler* handler = iterator.Next();
449
450		if (handler->function == function && handler->data == data) {
451			sLowResourceHandlers.Remove(handler);
452			delete handler;
453			return B_OK;
454		}
455	}
456
457	return B_ENTRY_NOT_FOUND;
458}
459
460
461/*! Registers a low resource handler. The higher the \a priority, the earlier
462	the handler will be called in low resource situations.
463*/
464status_t
465register_low_resource_handler(low_resource_func function, void* data,
466	uint32 resources, int32 priority)
467{
468	TRACE(("register_low_resource_handler(function = %p, data = %p)\n",
469		function, data));
470
471	low_resource_handler *newHandler = new(std::nothrow) low_resource_handler;
472	if (newHandler == NULL)
473		return B_NO_MEMORY;
474
475	newHandler->function = function;
476	newHandler->data = data;
477	newHandler->resources = resources;
478	newHandler->priority = priority;
479
480	RecursiveLocker locker(&sLowResourceLock);
481
482	// sort it in after priority (higher priority comes first)
483
484	HandlerList::ReverseIterator iterator
485		= sLowResourceHandlers.GetReverseIterator();
486	low_resource_handler* last = NULL;
487	while (iterator.HasNext()) {
488		low_resource_handler *handler = iterator.Next();
489
490		if (handler->priority >= priority) {
491			sLowResourceHandlers.InsertBefore(last, newHandler);
492			return B_OK;
493		}
494		last = handler;
495	}
496
497	sLowResourceHandlers.Add(newHandler, false);
498	return B_OK;
499}
500