1 /* Manage sets of mmap buffers on an image.
2 *
3 * 30/10/06
4 * - from region.c
5 * 19/3/09
6 * - block mmaps of nodata images
7 */
8
9 /*
10
11 This file is part of VIPS.
12
13 VIPS is free software; you can redistribute it and/or modify
14 it under the terms of the GNU Lesser General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU Lesser General Public License for more details.
22
23 You should have received a copy of the GNU Lesser General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 02110-1301 USA
27
28 */
29
30 /*
31
32 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
33
34 */
35
36 /*
37 #define DEBUG_TOTAL
38 #define DEBUG
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include <config.h>
43 #endif /*HAVE_CONFIG_H*/
44 #include <vips/intl.h>
45
46 #include <stdio.h>
47 #include <stdlib.h>
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h>
50 #endif /*HAVE_UNISTD_H*/
51 #include <errno.h>
52 #include <string.h>
53 #ifdef HAVE_SYS_MMAN_H
54 #include <sys/mman.h>
55 #endif
56
57 #include <vips/vips.h>
58 #include <vips/internal.h>
59 #include <vips/thread.h>
60
61 #ifdef G_OS_WIN32
62 #include <windows.h>
63 #endif /*G_OS_WIN32*/
64
65 /* Sanity checking ... write to this during read tests to make sure we don't
66 * get optimized out.
67 */
68 int vips__read_test;
69
70 /* Add this many lines above and below the mmap() window.
71 */
72 int vips__window_margin_pixels = VIPS__WINDOW_MARGIN_PIXELS;
73
74 /* Always map at least this many bytes. There's no point making tiny windows
75 * on small files.
76 */
77 int vips__window_margin_bytes = VIPS__WINDOW_MARGIN_BYTES;
78
79 /* Track global mmap usage.
80 */
81 #ifdef DEBUG_TOTAL
82 static int total_mmap_usage = 0;
83 static int max_mmap_usage = 0;
84 #endif /*DEBUG_TOTAL*/
85
86 static int
vips_window_unmap(VipsWindow * window)87 vips_window_unmap( VipsWindow *window )
88 {
89 /* unmap the old window
90 */
91 if( window->baseaddr ) {
92 if( vips__munmap( window->baseaddr, window->length ) )
93 return( -1 );
94
95 #ifdef DEBUG_TOTAL
96 g_mutex_lock( vips__global_lock );
97 total_mmap_usage -= window->length;
98 g_assert( total_mmap_usage >= 0 );
99 g_mutex_unlock( vips__global_lock );
100 #endif /*DEBUG_TOTAL*/
101
102 window->data = NULL;
103 window->baseaddr = NULL;
104 window->length = 0;
105 }
106
107 return( 0 );
108 }
109
110 static int
vips_window_free(VipsWindow * window)111 vips_window_free( VipsWindow *window )
112 {
113 VipsImage *im = window->im;
114
115 g_assert( window->ref_count == 0 );
116
117 #ifdef DEBUG
118 printf( "** vips_window_free: window top = %d, height = %d (%p)\n",
119 window->top, window->height, window );
120 printf( "vips_window_unref: %d windows left\n",
121 g_slist_length( im->windows ) );
122 #endif /*DEBUG*/
123
124 g_assert( g_slist_find( im->windows, window ) );
125 im->windows = g_slist_remove( im->windows, window );
126
127 if( vips_window_unmap( window ) )
128 return( -1 );
129
130 window->im = NULL;
131
132 g_free( window );
133
134 return( 0 );
135 }
136
137 int
vips_window_unref(VipsWindow * window)138 vips_window_unref( VipsWindow *window )
139 {
140 VipsImage *im = window->im;
141
142 g_mutex_lock( im->sslock );
143
144 #ifdef DEBUG
145 printf( "vips_window_unref: window top = %d, height = %d, count = %d\n",
146 window->top, window->height, window->ref_count );
147 #endif /*DEBUG*/
148
149 g_assert( window->ref_count > 0 );
150
151 window->ref_count -= 1;
152
153 if( window->ref_count == 0 ) {
154 if( vips_window_free( window ) ) {
155 g_mutex_unlock( im->sslock );
156 return( -1 );
157 }
158 }
159
160 g_mutex_unlock( im->sslock );
161
162 return( 0 );
163 }
164
165 #ifdef DEBUG_TOTAL
166 static void
trace_mmap_usage(void)167 trace_mmap_usage( void )
168 {
169 g_mutex_lock( vips__global_lock );
170 {
171 static int last_total = 0;
172 int total = total_mmap_usage / (1024 * 1024);
173 int max = max_mmap_usage / (1024 * 1024);
174
175 if( total != last_total ) {
176 printf( "vips_window_set: current mmap "
177 "usage of ~%dMB (high water mark %dMB)\n",
178 total, max );
179 last_total = total;
180 }
181 }
182 g_mutex_unlock( vips__global_lock );
183 }
184 #endif /*DEBUG_TOTAL*/
185
186 static int
vips_getpagesize(void)187 vips_getpagesize( void )
188 {
189 static int pagesize = 0;
190
191 if( !pagesize ) {
192 #ifdef G_OS_WIN32
193 SYSTEM_INFO si;
194
195 GetSystemInfo( &si );
196
197 pagesize = si.dwAllocationGranularity;
198 #else /*!G_OS_WIN32*/
199 pagesize = getpagesize();
200 #endif /*G_OS_WIN32*/
201
202 #ifdef DEBUG_TOTAL
203 printf( "vips_getpagesize: 0x%x\n", pagesize );
204 #endif /*DEBUG_TOTAL*/
205 }
206
207 return( pagesize );
208 }
209
210 /* Map a window into a file.
211 */
212 static int
vips_window_set(VipsWindow * window,int top,int height)213 vips_window_set( VipsWindow *window, int top, int height )
214 {
215 int pagesize = vips_getpagesize();
216
217 void *baseaddr;
218 gint64 start, end, pagestart;
219 size_t length, pagelength;
220
221 /* Calculate start and length for our window.
222 */
223 start = window->im->sizeof_header +
224 VIPS_IMAGE_SIZEOF_LINE( window->im ) * top;
225 length = VIPS_IMAGE_SIZEOF_LINE( window->im ) * height;
226
227 pagestart = start - start % pagesize;
228 end = start + length;
229 pagelength = end - pagestart;
230
231 /* Make sure we have enough file.
232 */
233 if( end > window->im->file_length ) {
234 vips_error( "vips_window_set",
235 _( "unable to read data for \"%s\", %s" ),
236 window->im->filename, _( "file has been truncated" ) );
237 return( -1 );
238 }
239
240 if( vips_window_unmap( window ) )
241 return( -1 );
242
243 if( !(baseaddr = vips__mmap( window->im->fd,
244 0, pagelength, pagestart )) )
245 return( -1 );
246
247 window->baseaddr = baseaddr;
248 window->length = pagelength;
249
250 window->data = (VipsPel *) baseaddr + (start - pagestart);
251 window->top = top;
252 window->height = height;
253
254 /* Sanity check ... make sure the data pointer is readable.
255 */
256 vips__read_test &= window->data[0];
257
258 #ifdef DEBUG_TOTAL
259 g_mutex_lock( vips__global_lock );
260 total_mmap_usage += window->length;
261 if( total_mmap_usage > max_mmap_usage )
262 max_mmap_usage = total_mmap_usage;
263 g_mutex_unlock( vips__global_lock );
264 trace_mmap_usage();
265 #endif /*DEBUG_TOTAL*/
266
267 return( 0 );
268 }
269
270 /* Make a new window.
271 */
272 static VipsWindow *
vips_window_new(VipsImage * im,int top,int height)273 vips_window_new( VipsImage *im, int top, int height )
274 {
275 VipsWindow *window;
276
277 if( !(window = VIPS_NEW( NULL, VipsWindow )) )
278 return( NULL );
279
280 window->ref_count = 1;
281 window->im = im;
282 window->top = 0;
283 window->height = 0;
284 window->data = NULL;
285 window->baseaddr = NULL;
286 window->length = 0;
287 im->windows = g_slist_prepend( im->windows, window );
288
289 if( vips_window_set( window, top, height ) ) {
290 vips_window_free( window );
291 return( NULL );
292 }
293
294 #ifdef DEBUG
295 printf( "** vips_window_new: window top = %d, height = %d (%p)\n",
296 window->top, window->height, window );
297 #endif /*DEBUG*/
298
299 return( window );
300 }
301
302 /* A request for an area of pixels.
303 */
304 typedef struct {
305 int top;
306 int height;
307 } request_t;
308
309 static void *
vips_window_fits(VipsWindow * window,request_t * req,void * b)310 vips_window_fits( VipsWindow *window, request_t *req, void *b )
311 {
312 if( window->top <= req->top &&
313 window->top + window->height >= req->top + req->height )
314 return( window );
315
316 return( NULL );
317 }
318
319 /* Find an existing window that fits within top/height and return a ref.
320 */
321 static VipsWindow *
vips_window_find(VipsImage * im,int top,int height)322 vips_window_find( VipsImage *im, int top, int height )
323 {
324 request_t req;
325 VipsWindow *window;
326
327 req.top = top;
328 req.height = height;
329 window = vips_slist_map2( im->windows,
330 (VipsSListMap2Fn) vips_window_fits, &req, NULL );
331
332 if( window ) {
333 window->ref_count += 1;
334
335 #ifdef DEBUG
336 printf( "vips_window_find: ref window top = %d, height = %d, "
337 "count = %d\n",
338 top, height, window->ref_count );
339 #endif /*DEBUG*/
340 }
341
342 return( window );
343 }
344
345 /* Old API. Just a compat stub now.
346 */
347 VipsWindow *
vips_window_ref(VipsImage * im,int top,int height)348 vips_window_ref( VipsImage *im, int top, int height )
349 {
350 return( NULL );
351 }
352
353 /* Update a window to make it enclose top/height.
354 */
355 VipsWindow *
vips_window_take(VipsWindow * window,VipsImage * im,int top,int height)356 vips_window_take( VipsWindow *window, VipsImage *im, int top, int height )
357 {
358 int margin;
359
360 /* We have a window and it has the pixels we need.
361 */
362 if( window &&
363 window->top <= top &&
364 window->top + window->height >= top + height )
365 return( window );
366
367 g_mutex_lock( im->sslock );
368
369 /* We have a window and we are the only ref to it ... scroll.
370 */
371 if( window &&
372 window->ref_count == 1 ) {
373 if( vips_window_set( window, top, height ) ) {
374 g_mutex_unlock( im->sslock );
375 vips_window_unref( window );
376
377 return( NULL );
378 }
379
380 g_mutex_unlock( im->sslock );
381
382 return( window );
383 }
384
385 /* There's more than one ref to the window. We can just decrement.
386 * Don't call _unref, since we've inside the lock.
387 */
388 if( window )
389 window->ref_count -= 1;
390
391 /* Is there an existing window we can reuse?
392 */
393 if( (window = vips_window_find( im, top, height )) ) {
394 g_mutex_unlock( im->sslock );
395
396 return( window );
397 }
398
399 /* We have to make a new window. Make it a bit bigger than strictly
400 * necessary.
401 */
402 margin = VIPS_MIN( vips__window_margin_pixels,
403 vips__window_margin_bytes / VIPS_IMAGE_SIZEOF_LINE( im ) );
404 top -= margin;
405 height += margin * 2;
406 top = VIPS_CLIP( 0, top, im->Ysize - 1 );
407 height = VIPS_CLIP( 0, height, im->Ysize - top );
408
409 if( !(window = vips_window_new( im, top, height )) ) {
410 g_mutex_unlock( im->sslock );
411 return( NULL );
412 }
413
414 g_mutex_unlock( im->sslock );
415
416 return( window );
417 }
418
419 void
vips_window_print(VipsWindow * window)420 vips_window_print( VipsWindow *window )
421 {
422 printf( "VipsWindow: %p ref_count = %d, ", window, window->ref_count );
423 printf( "im = %p, ", window->im );
424 printf( "top = %d, ", window->top );
425 printf( "height = %d, ", window->height );
426 printf( "data = %p, ", window->data );
427 printf( "baseaddr = %p, ", window->baseaddr );
428 printf( "length = %zd\n", window->length );
429 }
430