1/*
2 * Copyright 2006-2013, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 */
8
9
10/*!	This class manages a pool of areas for one client. The client is supposed
11	to clone these areas into its own address space to access the data.
12	This mechanism is only used for bitmaps for far.
13*/
14
15
16// TODO: areas could be relocated if needed (to be able to resize them)
17//		However, this would require a lock whenever a block of memory
18//		allocated by this allocator is accessed.
19
20
21#include "ClientMemoryAllocator.h"
22
23#include <stdio.h>
24#include <stdlib.h>
25
26#include <Autolock.h>
27
28#include "ServerApp.h"
29
30
31typedef block_list::Iterator block_iterator;
32typedef chunk_list::Iterator chunk_iterator;
33
34
35ClientMemoryAllocator::ClientMemoryAllocator(ServerApp* application)
36	:
37	fApplication(application),
38	fLock("client memory lock")
39{
40}
41
42
43ClientMemoryAllocator::~ClientMemoryAllocator()
44{
45	// delete all areas and chunks/blocks that are still allocated
46
47	while (true) {
48		struct block* block = fFreeBlocks.RemoveHead();
49		if (block == NULL)
50			break;
51
52		free(block);
53	}
54
55	while (true) {
56		struct chunk* chunk = fChunks.RemoveHead();
57		if (chunk == NULL)
58			break;
59
60		delete_area(chunk->area);
61		free(chunk);
62	}
63}
64
65
66void*
67ClientMemoryAllocator::Allocate(size_t size, block** _address, bool& newArea)
68{
69	// A detached allocator no longer allows any further allocations
70	if (fApplication == NULL)
71		return NULL;
72
73	BAutolock locker(fLock);
74
75	// Search best matching free block from the list
76
77	block_iterator iterator = fFreeBlocks.GetIterator();
78	struct block* block;
79	struct block* best = NULL;
80
81	while ((block = iterator.Next()) != NULL) {
82		if (block->size >= size && (best == NULL || block->size < best->size))
83			best = block;
84	}
85
86	if (best == NULL) {
87		// We didn't find a free block - we need to allocate
88		// another chunk, or resize an existing chunk
89		best = _AllocateChunk(size, newArea);
90		if (best == NULL)
91			return NULL;
92	} else
93		newArea = false;
94
95	// We need to split the chunk into two parts: the one to keep
96	// and the one to give away
97
98	if (best->size == size) {
99		// The simple case: the free block has exactly the size we wanted to have
100		fFreeBlocks.Remove(best);
101		*_address = best;
102		return best->base;
103	}
104
105	// TODO: maybe we should have the user reserve memory in its object
106	//	for us, so we don't have to do this here...
107
108	struct block* usedBlock = (struct block*)malloc(sizeof(struct block));
109	if (usedBlock == NULL)
110		return NULL;
111
112	usedBlock->base = best->base;
113	usedBlock->size = size;
114	usedBlock->chunk = best->chunk;
115
116	best->base += size;
117	best->size -= size;
118
119	*_address = usedBlock;
120	return usedBlock->base;
121}
122
123
124void
125ClientMemoryAllocator::Free(block* freeBlock)
126{
127	if (freeBlock == NULL)
128		return;
129
130	BAutolock locker(fLock);
131
132	// search for an adjacent free block
133
134	block_iterator iterator = fFreeBlocks.GetIterator();
135	struct block* before = NULL;
136	struct block* after = NULL;
137	bool inFreeList = true;
138
139	if (freeBlock->size != freeBlock->chunk->size) {
140		// TODO: this could be done better if free blocks are sorted,
141		//	and if we had one free blocks list per chunk!
142		//	IOW this is a bit slow...
143
144		while (struct block* block = iterator.Next()) {
145			if (block->chunk != freeBlock->chunk)
146				continue;
147
148			if (block->base + block->size == freeBlock->base)
149				before = block;
150
151			if (block->base == freeBlock->base + freeBlock->size)
152				after = block;
153		}
154
155		if (before != NULL && after != NULL) {
156			// merge with adjacent blocks
157			before->size += after->size + freeBlock->size;
158			fFreeBlocks.Remove(after);
159			free(after);
160			free(freeBlock);
161			freeBlock = before;
162		} else if (before != NULL) {
163			before->size += freeBlock->size;
164			free(freeBlock);
165			freeBlock = before;
166		} else if (after != NULL) {
167			after->base -= freeBlock->size;
168			after->size += freeBlock->size;
169			free(freeBlock);
170			freeBlock = after;
171		} else
172			fFreeBlocks.Add(freeBlock);
173	} else
174		inFreeList = false;
175
176	if (freeBlock->size == freeBlock->chunk->size) {
177		// We can delete the chunk now
178		struct chunk* chunk = freeBlock->chunk;
179
180		if (inFreeList)
181			fFreeBlocks.Remove(freeBlock);
182		free(freeBlock);
183
184		fChunks.Remove(chunk);
185		delete_area(chunk->area);
186
187		if (fApplication != NULL)
188			fApplication->NotifyDeleteClientArea(chunk->area);
189
190		free(chunk);
191	}
192}
193
194
195void
196ClientMemoryAllocator::Detach()
197{
198	BAutolock locker(fLock);
199	fApplication = NULL;
200}
201
202
203void
204ClientMemoryAllocator::Dump()
205{
206	if (fApplication != NULL) {
207		debug_printf("Application %" B_PRId32 ", %s: chunks:\n",
208			fApplication->ClientTeam(), fApplication->Signature());
209	}
210
211	chunk_list::Iterator iterator = fChunks.GetIterator();
212	int32 i = 0;
213	while (struct chunk* chunk = iterator.Next()) {
214		debug_printf("  [%4" B_PRId32 "] %p, area %" B_PRId32 ", base %p, "
215			"size %lu\n", i++, chunk, chunk->area, chunk->base, chunk->size);
216	}
217
218	debug_printf("free blocks:\n");
219
220	block_list::Iterator blockIterator = fFreeBlocks.GetIterator();
221	i = 0;
222	while (struct block* block = blockIterator.Next()) {
223		debug_printf("  [%6" B_PRId32 "] %p, chunk %p, base %p, size %lu\n",
224			i++, block, block->chunk, block->base, block->size);
225	}
226}
227
228
229struct block*
230ClientMemoryAllocator::_AllocateChunk(size_t size, bool& newArea)
231{
232	// round up to multiple of page size
233	size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
234
235	// At first, try to resize our existing areas
236
237	chunk_iterator iterator = fChunks.GetIterator();
238	struct chunk* chunk;
239	while ((chunk = iterator.Next()) != NULL) {
240		status_t status = resize_area(chunk->area, chunk->size + size);
241		if (status == B_OK) {
242			newArea = false;
243			break;
244		}
245	}
246
247	// TODO: resize and relocate while holding the write lock
248
249	struct block* block;
250	uint8* address;
251
252	if (chunk == NULL) {
253		// TODO: temporary measurement as long as resizing areas doesn't
254		//	work the way we need (with relocating the area, if needed)
255		if (size < B_PAGE_SIZE * 32)
256			size = B_PAGE_SIZE * 32;
257
258		// create new area for this allocation
259		chunk = (struct chunk*)malloc(sizeof(struct chunk));
260		if (chunk == NULL)
261			return NULL;
262
263		block = (struct block*)malloc(sizeof(struct block));
264		if (block == NULL) {
265			free(chunk);
266			return NULL;
267		}
268
269		char name[B_OS_NAME_LENGTH];
270#ifdef HAIKU_TARGET_PLATFORM_LIBBE_TEST
271		strcpy(name, "client heap");
272#else
273		snprintf(name, sizeof(name), "heap:%" B_PRId32 ":%s",
274			fApplication->ClientTeam(), fApplication->SignatureLeaf());
275#endif
276		area_id area = create_area(name, (void**)&address, B_ANY_ADDRESS, size,
277			B_NO_LOCK, B_READ_AREA | B_WRITE_AREA | B_CLONEABLE_AREA);
278		if (area < B_OK) {
279			free(block);
280			free(chunk);
281			return NULL;
282		}
283
284		// add chunk to list
285
286		chunk->area = area;
287		chunk->base = address;
288		chunk->size = size;
289
290		fChunks.Add(chunk);
291		newArea = true;
292	} else {
293		// create new free block for this chunk
294		block = (struct block *)malloc(sizeof(struct block));
295		if (block == NULL)
296			return NULL;
297
298		address = chunk->base + chunk->size;
299		chunk->size += size;
300	}
301
302	// add block to free list
303
304	block->chunk = chunk;
305	block->base = address;
306	block->size = size;
307
308	fFreeBlocks.Add(block);
309
310	return block;
311}
312
313
314// #pragma mark -
315
316
317ClientMemory::ClientMemory()
318	:
319	fAllocator(NULL),
320	fBlock(NULL)
321{
322}
323
324
325ClientMemory::~ClientMemory()
326{
327	if (fAllocator != NULL) {
328		if (fBlock != NULL)
329			fAllocator->Free(fBlock);
330		fAllocator.Unset();
331	}
332}
333
334
335void*
336ClientMemory::Allocate(ClientMemoryAllocator* allocator, size_t size,
337	bool& newArea)
338{
339	fAllocator.SetTo(allocator, false);
340
341	return fAllocator->Allocate(size, &fBlock, newArea);
342}
343
344
345area_id
346ClientMemory::Area()
347{
348	if (fBlock != NULL)
349		return fBlock->chunk->area;
350	return B_ERROR;
351}
352
353
354uint8*
355ClientMemory::Address()
356{
357	if (fBlock != NULL)
358		return fBlock->base;
359	return 0;
360}
361
362
363uint32
364ClientMemory::AreaOffset()
365{
366	if (fBlock != NULL)
367		return fBlock->base - fBlock->chunk->base;
368	return 0;
369}
370
371
372// #pragma mark -
373
374
375ClonedAreaMemory::ClonedAreaMemory()
376	:
377	fClonedArea(-1),
378	fOffset(0),
379	fBase(NULL)
380{
381}
382
383
384ClonedAreaMemory::~ClonedAreaMemory()
385{
386	if (fClonedArea >= 0)
387		delete_area(fClonedArea);
388}
389
390
391void*
392ClonedAreaMemory::Clone(area_id area, uint32 offset)
393{
394	fClonedArea = clone_area("server_memory", (void**)&fBase, B_ANY_ADDRESS,
395		B_READ_AREA | B_WRITE_AREA, area);
396	if (fBase == NULL)
397		return NULL;
398	fOffset = offset;
399	return Address();
400}
401
402
403area_id
404ClonedAreaMemory::Area()
405{
406	return fClonedArea;
407}
408
409
410uint8*
411ClonedAreaMemory::Address()
412{
413	return fBase + fOffset;
414}
415
416
417uint32
418ClonedAreaMemory::AreaOffset()
419{
420	return fOffset;
421}
422