Quellcode durchsuchen

Updated memory allocator to improve support for unaligned or partially

aligned blocks.

Moved header to include/malloc.h, since we now also provide the
POSIX-like malloc()/free() pair.

Not yet tested.
tags/v0.9.3
Michael Brown vor 18 Jahren
Ursprung
Commit
b601a7d355
3 geänderte Dateien mit 224 neuen und 154 gelöschten Zeilen
  1. 158
    118
      src/core/malloc.c
  2. 0
    36
      src/include/gpxe/malloc.h
  3. 66
    0
      src/include/malloc.h

+ 158
- 118
src/core/malloc.c Datei anzeigen

@@ -16,189 +16,228 @@
16 16
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 17
  */
18 18
 
19
+#include <stddef.h>
19 20
 #include <stdint.h>
20 21
 #include <string.h>
22
+#include <strings.h>
21 23
 #include <io.h>
22 24
 #include <gpxe/list.h>
23
-#include <gpxe/malloc.h>
25
+#include <malloc.h>
24 26
 
25 27
 /** @file
26 28
  *
27
- * Memory allocation
29
+ * Dynamic memory allocation
28 30
  *
29 31
  */
30 32
 
31 33
 /** A free block of memory */
32
-struct free_block {
34
+struct memory_block {
33 35
 	/** List of free blocks */
34 36
 	struct list_head list;
35 37
 	/** Size of this block */
36 38
 	size_t size;
37 39
 };
38 40
 
41
+#define MIN_MEMBLOCK_SIZE \
42
+	( ( size_t ) ( 1 << ( fls ( sizeof ( struct memory_block ) - 1 ) ) ) )
43
+
44
+/** A block of allocated memory complete with size information */
45
+struct autosized_block {
46
+	/** Size of this block */
47
+	size_t size;
48
+	/** Remaining data */
49
+	char data[0];
50
+};
51
+
39 52
 /** List of free memory blocks */
40 53
 static LIST_HEAD ( free_blocks );
41 54
 
42 55
 /**
43
- * Round size up to a memory allocation block size
56
+ * Allocate a memory block
57
+ *
58
+ * @v size		Requested size
59
+ * @v align		Physical alignment
60
+ * @ret ptr		Memory block, or NULL
44 61
  *
45
- * @v requested		Requested size
46
- * @ret obtained	Obtained size
62
+ * Allocates a memory block @b physically aligned as requested.  No
63
+ * guarantees are provided for the alignment of the virtual address.
47 64
  *
48
- * The requested size is rounded up to the minimum allocation block
49
- * size (the size of a struct @c free_block) and then rounded up to
50
- * the nearest power of two.
65
+ * @c align must be a power of two.  @c size may not be zero.
51 66
  */
52
-static size_t block_size ( size_t requested ) {
53
-	size_t obtained = 1;
54
-
55
-	while ( ( obtained < sizeof ( struct free_block ) ) ||
56
-		( obtained < requested ) ) {
57
-		obtained <<= 1;
67
+void * alloc_memblock ( size_t size, size_t align ) {
68
+	struct memory_block *block;
69
+	size_t pre_size;
70
+	ssize_t post_size;
71
+	struct memory_block *pre;
72
+	struct memory_block *post;
73
+
74
+	/* Round up alignment and size to multiples of MIN_MEMBLOCK_SIZE */
75
+	align = ( align + MIN_MEMBLOCK_SIZE - 1 ) & ~( MIN_MEMBLOCK_SIZE - 1 );
76
+	size = ( size + MIN_MEMBLOCK_SIZE - 1 ) & ~( MIN_MEMBLOCK_SIZE - 1 );
77
+
78
+	/* Search through blocks for the first one with enough space */
79
+	list_for_each_entry ( block, &free_blocks, list ) {
80
+		pre_size = ( - virt_to_phys ( block ) ) & ( align - 1 );
81
+		post_size = block->size - pre_size - size;
82
+		if ( post_size >= 0 ) {
83
+			/* Split block into pre-block, block, and
84
+			 * post-block.  After this split, the "pre"
85
+			 * block is the one currently linked into the
86
+			 * free list.
87
+			 */
88
+			pre   = block;
89
+			block = ( ( ( void * ) pre   ) + pre_size );
90
+			post  = ( ( ( void * ) block ) + size     );
91
+			/* If there is a "post" block, add it in to
92
+			 * the free list.  Leak it if it is too small
93
+			 * (which can happen only at the very end of
94
+			 * the heap).
95
+			 */
96
+			if ( ( size_t ) post_size > MIN_MEMBLOCK_SIZE ) {
97
+				post->size = post_size;
98
+				list_add ( &post->list, &pre->list );
99
+			}
100
+			/* Shrink "pre" block, leaving the main block
101
+			 * isolated and no longer part of the free
102
+			 * list.
103
+			 */
104
+			pre->size = pre_size;
105
+			/* If there is no "pre" block, remove it from
106
+			 * the list.  Also remove it (i.e. leak it) if
107
+			 * it is too small, which can happen only at
108
+			 * the very start of the heap.
109
+			 */
110
+			if ( pre_size < MIN_MEMBLOCK_SIZE )
111
+				list_del ( &pre->list );
112
+			/* Zero allocated memory, for calloc() */
113
+			memset ( block, 0, size );
114
+			return block;
115
+		}
58 116
 	}
59
-	return obtained;
117
+	return NULL;
60 118
 }
61 119
 
62 120
 /**
63
- * Allocate memory
121
+ * Free a memory block
64 122
  *
65
- * @v size		Requested size
66
- * @ret ptr		Allocated memory
67
- *
68
- * gmalloc() will always allocate memory in power-of-two sized blocks,
69
- * aligned to the corresponding power-of-two boundary.  For example, a
70
- * request for 1500 bytes will return a 2048-byte block aligned to a
71
- * 2048-byte boundary.
72
- *
73
- * The alignment applies to the physical address, not the virtual
74
- * address.  The pointer value returned by gmalloc() therefore has no
75
- * alignment guarantees, except as provided for by the
76
- * virtual-to-physical mapping.  (In a PXE environment, this mapping
77
- * is guaranteed to be a multiple of 16 bytes.)
78
- *
79
- * Unlike traditional malloc(), the caller must remember the size of
80
- * the allocated block and pass the size to gfree().  This is done in
81
- * order to allow efficient allocation of power-of-two sized and
82
- * aligned blocks.
123
+ * @v ptr		Memory allocated by alloc_memblock(), or NULL
124
+ * @v size		Size of the memory
125
+ *
126
+ * If @c ptr is NULL, no action is taken.
83 127
  */
84
-void * gmalloc ( size_t size ) {
85
-	struct free_block *block;
86
-	struct free_block *buddy;
128
+void free_memblock ( void *ptr, size_t size ) {
129
+	struct memory_block *freeing;
130
+	struct memory_block *block;
131
+	ssize_t gap_before;
132
+	ssize_t gap_after;
133
+
134
+	/* Allow for ptr==NULL */
135
+	if ( ! ptr )
136
+		return;
87 137
 
88
-	/* Round up block size to power of two */
89
-	size = block_size ( size );
138
+	/* Round up size to match actual size that alloc_memblock()
139
+	 * would have used.
140
+	 */
141
+	size = ( size + MIN_MEMBLOCK_SIZE - 1 ) & ~( MIN_MEMBLOCK_SIZE - 1 );
142
+	freeing = ptr;
143
+	freeing->size = size;
90 144
 
91
-	/* Find the best available block */
145
+	/* Insert/merge into free list */
92 146
 	list_for_each_entry ( block, &free_blocks, list ) {
93
-		if ( block->size == size ) {
147
+		/* Calculate gaps before and after the "freeing" block */
148
+		gap_before = ( ( ( void * ) freeing ) - 
149
+			       ( ( ( void * ) block ) + block->size ) );
150
+		gap_after = ( ( ( void * ) block ) - 
151
+			      ( ( ( void * ) freeing ) + freeing->size ) );
152
+		/* Merge with immediately preceding block, if possible */
153
+		if ( gap_before == 0 ) {
154
+			block->size += size;
94 155
 			list_del ( &block->list );
95
-			memset ( block, 0, size );
96
-			return block;
156
+			freeing = block;
97 157
 		}
98
-		while ( block->size > size ) {
99
-			block->size >>= 1;
100
-			buddy = ( ( ( void * ) block ) + block->size );
101
-			buddy->size = block->size;
102
-			list_add ( &buddy->list, &block->list );
158
+		/* Insert before the immediately following block.  If
159
+		 * possible, merge the following block into the
160
+		 * "freeing" block.
161
+		 */
162
+		if ( gap_after >= 0 ) {
163
+			list_add_tail ( &freeing->list, &block->list );
164
+			if ( gap_after == 0 ) {
165
+				freeing->size += block->size;
166
+				list_del ( &block->list );
167
+			}
168
+			break;
103 169
 		}
104 170
 	}
105
-
106
-	/* Nothing available */
107
-	return NULL;
108 171
 }
109 172
 
110 173
 /**
111
- * Free memory
112
- *
113
- * @v ptr		Allocated memory
114
- * @v size		Originally requested size
174
+ * Allocate memory
115 175
  *
116
- * Frees memory originally allocated by gmalloc().
176
+ * @v size		Requested size
177
+ * @ret ptr		Memory, or NULL
117 178
  *
118
- * Calling gfree() with a NULL @c ptr is explicitly allowed, and
119
- * defined to have no effect.  Code such as
179
+ * Allocates memory with no particular alignment requirement.  @c ptr
180
+ * will be aligned to at least a multiple of sizeof(void*).
120 181
  *
121
- * @code
182
+ * Note that malloc() is very inefficient for allocating blocks where
183
+ * the size is a power of two; if you have many of these
184
+ * (e.g. descriptor rings, data buffers) you should use malloc_dma()
185
+ * instead.
186
+ */
187
+void * malloc ( size_t size ) {
188
+	size_t total_size;
189
+	struct autosized_block *block;
190
+
191
+	total_size = size + offsetof ( struct autosized_block, data );
192
+	block = alloc_memblock ( total_size, 1 );
193
+	if ( ! block )
194
+		return NULL;
195
+	block->size = size;
196
+	return &block->data;
197
+}
198
+
199
+/**
200
+ * Free memory
122 201
  *
123
- * if ( ! my_ptr )
124
- *     gfree ( my_ptr, my_size )
202
+ * @v size		Memory allocated by malloc(), or NULL
125 203
  *
126
- * @endcode
204
+ * Memory allocated with malloc_dma() cannot be freed with free(); it
205
+ * must be freed with free_dma() instead.
127 206
  *
128
- * is perfectly valid, but should be avoided as unnecessary bloat.
207
+ * If @c ptr is NULL, no action is taken.
129 208
  */
130
-void gfree ( void *ptr, size_t size ) {
131
-	struct free_block *freed_block = ptr;
132
-	struct free_block *block;
133
-	
134
-	/* Cope with gfree(NULL,x) */
135
-	if ( ! ptr )
136
-		return;
209
+void free ( void *ptr ) {
210
+	struct autosized_block *block;
137 211
 
138
-	/* Round up block size to power of two */
139
-	size = block_size ( size );
140
-	freed_block->size = size;
141
-
142
-	/* Merge back into free list */
143
-	list_for_each_entry ( block, &free_blocks, list ) {
144
-		if ( ( ( virt_to_phys ( block ) ^
145
-			 virt_to_phys ( freed_block ) ) == size ) &&
146
-		     ( block->size == size ) ) {
147
-			list_del ( &block->list );
148
-			size <<= 1;
149
-			if ( block < freed_block )
150
-				freed_block = block;
151
-			freed_block->size = size;
152
-		} else if ( block->size > size ) {
153
-			break;
154
-		}
212
+	if ( ptr ) {
213
+		block = container_of ( ptr, struct autosized_block, data );
214
+		free_memblock ( block, block->size );
155 215
 	}
156
-	list_add_tail ( &freed_block->list, &block->list );
157 216
 }
158 217
 
159 218
 /**
160 219
  * Add memory to allocation pool
161 220
  *
162 221
  * @v start		Start address
163
- * @v len		Length
222
+ * @v end		End address
164 223
  *
165
- * Adds a block of memory to the allocation pool.  This is a one-way
166
- * operation; there is no way to reclaim this memory.
224
+ * Adds a block of memory [start,end) to the allocation pool.  This is
225
+ * a one-way operation; there is no way to reclaim this memory.
167 226
  *
168
- * There are no alignment requirements on either start or len.
227
+ * @c start must be aligned to at least a multiple of sizeof(void*).
169 228
  */
170
-void gmpopulate ( void *start, size_t len ) {
171
-	size_t frag_len;
172
-
173
-	/* Split region into power-of-two sized and aligned blocks,
174
-	 * and feed them to gfree().
175
-	 */
176
-	while ( len ) {
177
-		frag_len = 1;
178
-		/* Find maximum allowed alignment for this address */
179
-		while ( ( virt_to_phys ( start ) & frag_len ) == 0 ) { 
180
-			frag_len <<= 1;
181
-		}
182
-		/* Find maximum block size that fits in remaining space */
183
-		while ( frag_len > len ) {
184
-			frag_len >>= 1;
185
-		}
186
-		/* Skip blocks that are too small */
187
-		if ( frag_len >= sizeof ( struct free_block ) )
188
-			gfree ( start, frag_len );
189
-		start += frag_len;
190
-		len -= frag_len;
191
-	}
229
+void mpopulate ( void *start, size_t len ) {
230
+	free_memblock ( start, ( len & ~( MIN_MEMBLOCK_SIZE - 1 ) ) );
192 231
 }
193 232
 
194
-#if 0
233
+#if 1
195 234
 #include <vsprintf.h>
196 235
 /**
197 236
  * Dump free block list
198 237
  *
199 238
  */
200
-void gdumpfree ( void ) {
201
-	struct free_block *block;
239
+void mdumpfree ( void ) {
240
+	struct memory_block *block;
202 241
 
203 242
 	printf ( "Free block list:\n" );
204 243
 	list_for_each_entry ( block, &free_blocks, list ) {
@@ -207,3 +246,4 @@ void gdumpfree ( void ) {
207 246
 	}
208 247
 }
209 248
 #endif
249
+

+ 0
- 36
src/include/gpxe/malloc.h Datei anzeigen

@@ -1,36 +0,0 @@
1
-#ifndef _GPXE_MALLOC_H
2
-#define _GPXE_MALLOC_H
3
-
4
-#include <stdint.h>
5
-
6
-/** @file
7
- *
8
- * Memory allocation
9
- *
10
- */
11
-
12
-extern void * gmalloc ( size_t size );
13
-extern void gfree ( void *ptr, size_t size );
14
-extern void gmpopulate ( void *start, size_t len );
15
-
16
-/**
17
- * Allocate cleared memory
18
- *
19
- * @v size		Requested size
20
- * @ret ptr		Allocated memory
21
- *
22
- * Allocate memory as per gmalloc(), and zero it.
23
- *
24
- * Note that gmalloc() and gcalloc() are identical, in the interests
25
- * of reducing code size.  Callers should not, however, rely on
26
- * gmalloc() clearing memory, since this behaviour may change in
27
- * future.
28
- */
29
-static inline void * gcalloc ( size_t size ) {
30
-	return gmalloc ( size );
31
-}
32
-
33
-/* Debug function; not compiled in by default */
34
-void gdumpfree ( void );
35
-
36
-#endif /* _GPXE_MALLOC_H */

+ 66
- 0
src/include/malloc.h Datei anzeigen

@@ -0,0 +1,66 @@
1
+#ifndef _MALLOC_H
2
+#define _MALLOC_H
3
+
4
+#include <stdint.h>
5
+
6
+/** @file
7
+ *
8
+ * Dynamic memory allocation
9
+ *
10
+ */
11
+
12
+extern void * alloc_memblock ( size_t size, size_t align );
13
+extern void free_memblock ( void *ptr, size_t size );
14
+extern void * malloc ( size_t size );
15
+extern void free ( void *ptr );
16
+extern void mpopulate ( void *start, size_t len );
17
+extern void mdumpfree ( void );
18
+
19
+/**
20
+ * Allocate memory for DMA
21
+ *
22
+ * @v size		Requested size
23
+ * @v align		Physical alignment
24
+ * @ret ptr		Memory, or NULL
25
+ *
26
+ * Allocates physically-aligned memory for DMA.
27
+ *
28
+ * @c align must be a power of two.  @c size may not be zero.
29
+ */
30
+static inline void * malloc_dma ( size_t size, size_t phys_align ) {
31
+	return alloc_memblock ( size, phys_align );
32
+}
33
+
34
+/**
35
+ * Free memory allocated with malloc_dma()
36
+ *
37
+ * @v ptr		Memory allocated by malloc_dma(), or NULL
38
+ * @v size		Size of memory, as passed to malloc_dma()
39
+ *
40
+ * Memory allocated with malloc_dma() can only be freed with
41
+ * free_dma(); it cannot be freed with the standard free().
42
+ *
43
+ * If @c ptr is NULL, no action is taken.
44
+ */
45
+static inline void free_dma ( void *ptr, size_t size ) {
46
+	free_memblock ( ptr, size );
47
+}
48
+
49
+/**
50
+ * Allocate cleared memory
51
+ *
52
+ * @v nmemb		Number of members
53
+ * @v size		Size of each member
54
+ * @ret ptr		Allocated memory
55
+ *
56
+ * Allocate memory as per malloc(), and zero it.
57
+ *
58
+ * Note that malloc() and calloc() are identical, in the interests of
59
+ * reducing code size.  Callers should not, however, rely on malloc()
60
+ * clearing memory, since this behaviour may change in future.
61
+ */
62
+static inline void * calloc ( size_t nmemb, size_t size ) {
63
+	return malloc ( nmemb * size );
64
+}
65
+
66
+#endif /* _MALLOC_H */

Laden…
Abbrechen
Speichern