ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/thttpd/mmc.c
Revision: 1.1
Committed: Mon Jun 18 21:11:57 2001 UTC (22 years, 11 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: mp_j, dp_j, cp_j, HEAD
Branch point for: connpatch, dirpatch, mmapppatch
Log Message:
*** empty log message ***

File Contents

# Content
1 /* mmc.c - mmap cache
2 **
3 ** Copyright © 1998,2001 by Jef Poskanzer <jef@acme.com>.
4 ** All rights reserved.
5 **
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions
8 ** are met:
9 ** 1. Redistributions of source code must retain the above copyright
10 ** notice, this list of conditions and the following disclaimer.
11 ** 2. Redistributions in binary form must reproduce the above copyright
12 ** notice, this list of conditions and the following disclaimer in the
13 ** documentation and/or other materials provided with the distribution.
14 **
15 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 ** SUCH DAMAGE.
26 */
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <syslog.h>
35
36 #ifdef HAVE_MMAP
37 #include <sys/mman.h>
38 #endif /* HAVE_MMAP */
39
40 #include "mmc.h"
41
42
43 /* Defines. */
44 #ifndef DEFAULT_EXPIRE_AGE
45 #define DEFAULT_EXPIRE_AGE 600
46 #endif
47 #ifndef DESIRED_FREE_COUNT
48 #define DESIRED_FREE_COUNT 100
49 #endif
50 #ifndef DESIRED_MAX_MAPPED_FILES
51 #define DESIRED_MAX_MAPPED_FILES 2000
52 #endif
53 #ifndef INITIAL_HASH_SIZE
54 #define INITIAL_HASH_SIZE (1 << 10)
55 #endif
56
57 #define max(a,b) ((a)>(b)?(a):(b))
58 #define min(a,b) ((a)<(b)?(a):(b))
59
60
61 /* The Map struct. */
62 typedef struct MapStruct {
63 ino_t ino;
64 dev_t dev;
65 off_t size;
66 time_t ctime;
67 int refcount;
68 time_t reftime;
69 void* addr;
70 unsigned int hash;
71 int hash_idx;
72 struct MapStruct* next;
73 } Map;
74
75
76 /* Globals. */
77 static Map* maps = (Map*) 0;
78 static Map* free_maps = (Map*) 0;
79 static int alloc_count = 0, map_count = 0, free_count = 0;
80 static Map** hash_table = (Map**) 0;
81 static int hash_size;
82 static unsigned int hash_mask;
83 static time_t expire_age = DEFAULT_EXPIRE_AGE;
84
85
86
87 /* Forwards. */
88 static void really_unmap( Map** mm );
89 static int check_hash_size( void );
90 static int add_hash( Map* m );
91 static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
92 static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
93
94
95 void*
96 mmc_map( char* filename, struct stat* sbP, struct timeval* nowP )
97 {
98 time_t now;
99 struct stat sb;
100 Map* m;
101 int fd;
102
103 /* Stat the file, if necessary. */
104 if ( sbP != (struct stat*) 0 )
105 sb = *sbP;
106 else
107 {
108 if ( stat( filename, &sb ) != 0 )
109 {
110 syslog( LOG_ERR, "stat - %m" );
111 return (void*) 0;
112 }
113 }
114
115 /* Get the current time, if necessary. */
116 if ( nowP != (struct timeval*) 0 )
117 now = nowP->tv_sec;
118 else
119 now = time( (time_t*) 0 );
120
121 /* See if we have it mapped already, via the hash table. */
122 if ( check_hash_size() < 0 )
123 {
124 syslog( LOG_ERR, "check_hash_size() failure" );
125 return (void*) 0;
126 }
127 m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime );
128 if ( m != (Map*) 0 )
129 {
130 /* Yep. Just return the existing map */
131 ++m->refcount;
132 m->reftime = now;
133 return m->addr;
134 }
135
136 /* Open the file. */
137 fd = open( filename, O_RDONLY );
138 if ( fd < 0 )
139 {
140 syslog( LOG_ERR, "open - %m" );
141 return (void*) 0;
142 }
143
144 /* Find a free Map entry or make a new one. */
145 if ( free_maps != (Map*) 0 )
146 {
147 m = free_maps;
148 free_maps = m->next;
149 --free_count;
150 }
151 else
152 {
153 m = (Map*) malloc( sizeof(Map) );
154 if ( m == (Map*) 0 )
155 {
156 (void) close( fd );
157 syslog( LOG_ERR, "out of memory allocating a Map" );
158 return (void*) 0;
159 }
160 ++alloc_count;
161 }
162
163 /* Fill in the Map entry. */
164 m->ino = sb.st_ino;
165 m->dev = sb.st_dev;
166 m->size = sb.st_size;
167 m->ctime = sb.st_ctime;
168 m->refcount = 1;
169 m->reftime = now;
170
171 /* Avoid doing anything for zero-length files; some systems don't like
172 ** to mmap them, other systems dislike mallocing zero bytes.
173 */
174 if ( m->size == 0 )
175 m->addr = (void*) 1; /* arbitrary non-NULL address */
176 else
177 {
178 #ifdef HAVE_MMAP
179 /* Map the file into memory. */
180 m->addr = mmap( 0, m->size, PROT_READ, MAP_SHARED, fd, 0 );
181 if ( m->addr == (void*) -1 )
182 {
183 syslog( LOG_ERR, "mmap - %m" );
184 (void) close( fd );
185 free( (void*) m );
186 --alloc_count;
187 return (void*) 0;
188 }
189 #else /* HAVE_MMAP */
190 /* Read the file into memory. */
191 m->addr = (void*) malloc( m->size );
192 if ( m->addr == (void*) 0 )
193 {
194 syslog( LOG_ERR, "out of memory storing a file" );
195 (void) close( fd );
196 free( (void*) m );
197 --alloc_count;
198 return (void*) 0;
199 }
200 if ( read( fd, m->addr, m->size ) != m->size )
201 {
202 syslog( LOG_ERR, "read - %m" );
203 (void) close( fd );
204 free( (void*) m );
205 --alloc_count;
206 return (void*) 0;
207 }
208 #endif /* HAVE_MMAP */
209 }
210 (void) close( fd );
211
212 /* Put the Map into the hash table. */
213 if ( add_hash( m ) < 0 )
214 {
215 syslog( LOG_ERR, "add_hash() failure" );
216 free( (void*) m );
217 --alloc_count;
218 return (void*) 0;
219 }
220
221 /* Put the Map on the active list. */
222 m->next = maps;
223 maps = m;
224 ++map_count;
225
226 /* And return the address. */
227 return m->addr;
228 }
229
230
231 void
232 mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP )
233 {
234 Map* m = (Map*) 0;
235
236 /* Find the Map entry for this address. First try a hash. */
237 if ( sbP != (struct stat*) 0 )
238 {
239 m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime );
240 if ( m != (Map*) 0 && m->addr != addr )
241 m = (Map*) 0;
242 }
243 /* If that didn't work, try a full search. */
244 if ( m == (Map*) 0 )
245 for ( m = maps; m != (Map*) 0; m = m->next )
246 if ( m->addr == addr )
247 break;
248 if ( m == (Map*) 0 )
249 syslog( LOG_ERR, "mmc_unmap failed to find entry!" );
250 else if ( m->refcount <= 0 )
251 syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" );
252 else
253 {
254 --m->refcount;
255 if ( nowP != (struct timeval*) 0 )
256 m->reftime = nowP->tv_sec;
257 else
258 m->reftime = time( (time_t*) 0 );
259 }
260 }
261
262
263 void
264 mmc_cleanup( struct timeval* nowP )
265 {
266 time_t now;
267 Map** mm;
268 Map* m;
269
270 /* Get the current time, if necessary. */
271 if ( nowP != (struct timeval*) 0 )
272 now = nowP->tv_sec;
273 else
274 now = time( (time_t*) 0 );
275
276 /* Really unmap any unreferenced entries older than the age limit. */
277 for ( mm = &maps; *mm != (Map*) 0; )
278 {
279 m = *mm;
280 if ( m->refcount == 0 && now - m->reftime >= expire_age )
281 really_unmap( mm );
282 else
283 mm = &(*mm)->next;
284 }
285
286 /* If there are still too many (or too few) maps, adjust the age limit. */
287 if ( map_count > DESIRED_MAX_MAPPED_FILES )
288 expire_age = max( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
289 else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 )
290 expire_age = min( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 );
291
292 /* Really free excess blocks on the free list. */
293 while ( free_count > DESIRED_FREE_COUNT )
294 {
295 m = free_maps;
296 free_maps = m->next;
297 --free_count;
298 free( (void*) m );
299 --alloc_count;
300 }
301 }
302
303
304 static void
305 really_unmap( Map** mm )
306 {
307 Map* m;
308
309 m = *mm;
310 if ( m->size != 0 )
311 {
312 #ifdef HAVE_MMAP
313 if ( munmap( m->addr, m->size ) < 0 )
314 syslog( LOG_ERR, "munmap - %m" );
315 #else /* HAVE_MMAP */
316 free( (void*) m->addr );
317 #endif /* HAVE_MMAP */
318 }
319 /* And move the Map to the free list. */
320 *mm = m->next;
321 --map_count;
322 m->next = free_maps;
323 free_maps = m;
324 ++free_count;
325 /* This will sometimes break hash chains, but that's harmless; the
326 ** unmapping code that searches the hash table knows to keep searching.
327 */
328 hash_table[m->hash_idx] = (Map*) 0;
329 }
330
331
332 void
333 mmc_destroy( void )
334 {
335 Map* m;
336
337 while ( maps != (Map*) 0 )
338 really_unmap( &maps );
339 while ( free_maps != (Map*) 0 )
340 {
341 m = free_maps;
342 free_maps = m->next;
343 --free_count;
344 free( (void*) m );
345 --alloc_count;
346 }
347 }
348
349
350 /* Make sure the hash table is big enough. */
351 static int
352 check_hash_size( void )
353 {
354 int i;
355 Map* m;
356
357 /* Are we just starting out? */
358 if ( hash_table == (Map**) 0 )
359 {
360 hash_size = INITIAL_HASH_SIZE;
361 hash_mask = hash_size - 1;
362 }
363 /* Is it at least three times bigger than the number of entries? */
364 else if ( hash_size >= map_count * 3 )
365 return 0;
366 else
367 {
368 /* No, got to expand. */
369 free( (void*) hash_table );
370 /* Double the hash size until it's big enough. */
371 do
372 {
373 hash_size = hash_size << 1;
374 }
375 while ( hash_size < map_count * 6 );
376 hash_mask = hash_size - 1;
377 }
378 /* Make the new table. */
379 hash_table = (Map**) malloc( hash_size * sizeof(Map*) );
380 if ( hash_table == (Map**) 0 )
381 return -1;
382 /* Clear it. */
383 for ( i = 0; i < hash_size; ++i )
384 hash_table[i] = (Map*) 0;
385 /* And rehash all entries. */
386 for ( m = maps; m != (Map*) 0; m = m->next )
387 if ( add_hash( m ) < 0 )
388 return -1;
389 return 0;
390 }
391
392
393 static int
394 add_hash( Map* m )
395 {
396 unsigned int h, he, i;
397
398 h = hash( m->ino, m->dev, m->size, m->ctime );
399 he = ( h + hash_size - 1 ) & hash_mask;
400 for ( i = h; ; i = ( i + 1 ) & hash_mask )
401 {
402 if ( hash_table[i] == (Map*) 0 )
403 {
404 hash_table[i] = m;
405 m->hash = h;
406 m->hash_idx = i;
407 return 0;
408 }
409 if ( i == he )
410 break;
411 }
412 return -1;
413 }
414
415
416 static Map*
417 find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
418 {
419 unsigned int h, he, i;
420 Map* m;
421
422 h = hash( ino, dev, size, ctime );
423 he = ( h + hash_size - 1 ) & hash_mask;
424 for ( i = h; ; i = ( i + 1 ) & hash_mask )
425 {
426 m = hash_table[i];
427 if ( m == (Map*) 0 )
428 break;
429 if ( m->hash == h && m->ino == ino && m->dev == dev &&
430 m->size == size && m->ctime == ctime )
431 return m;
432 if ( i == he )
433 break;
434 }
435 return (Map*) 0;
436 }
437
438
439 static unsigned int
440 hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
441 {
442 unsigned int h = 177573;
443
444 h ^= ino;
445 h += h << 5;
446 h ^= dev;
447 h += h << 5;
448 h ^= size;
449 h += h << 5;
450 h ^= ctime;
451
452 return h & hash_mask;
453 }
454
455
456 /* Generate debugging statistics syslog message. */
457 void
458 mmc_logstats( long secs )
459 {
460 syslog(
461 LOG_NOTICE, " map cache - %d allocated, %d active, %d free; hash size: %d; expire age: %ld",
462 alloc_count, map_count, free_count, hash_size, expire_age );
463 if ( map_count + free_count != alloc_count )
464 syslog( LOG_ERR, "map counts don't add up!" );
465 }