| /* Manage sets of mmap buffers on an image. |
| * |
| * 30/10/06 |
| * - from region.c |
| * 19/3/09 |
| * - block mmaps of nodata images |
| */ |
| |
| /* |
| |
| This file is part of VIPS. |
| |
| VIPS is free software; you can redistribute it and/or modify |
| it under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| */ |
| |
| /* |
| |
| These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk |
| |
| */ |
| |
| /* |
| #define DEBUG_TOTAL |
| #define DEBUG |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif /*HAVE_CONFIG_H*/ |
| #include <vips/intl.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif /*HAVE_UNISTD_H*/ |
| #include <errno.h> |
| #include <string.h> |
| #ifdef HAVE_SYS_MMAN_H |
| #include <sys/mman.h> |
| #endif |
| #include <assert.h> |
| |
| #include <vips/vips.h> |
| #include <vips/internal.h> |
| #include <vips/thread.h> |
| |
| #ifdef OS_WIN32 |
| #include <windows.h> |
| #endif /*OS_WIN32*/ |
| |
| #ifdef WITH_DMALLOC |
| #include <dmalloc.h> |
| #endif /*WITH_DMALLOC*/ |
| |
| /* Sanity checking ... write to this during read tests to make sure we don't |
| * get optimised out. |
| */ |
| int im__read_test; |
| |
| /* Add this many lines above and below the mmap() window. |
| */ |
| int im__window_margin_pixels = IM__WINDOW_MARGIN_PIXELS; |
| |
| /* Always map at least this many bytes. There's no point making tiny windows |
| * on small files. |
| */ |
| int im__window_margin_bytes = IM__WINDOW_MARGIN_BYTES; |
| |
| /* Track global mmap usage. |
| */ |
| #ifdef DEBUG_TOTAL |
| static int total_mmap_usage = 0; |
| static int max_mmap_usage = 0; |
| #endif /*DEBUG_TOTAL*/ |
| |
| static int |
| im_window_unmap( im_window_t *window ) |
| { |
| /* unmap the old window |
| */ |
| if( window->baseaddr ) { |
| if( im__munmap( window->baseaddr, window->length ) ) |
| return( -1 ); |
| |
| #ifdef DEBUG_TOTAL |
| g_mutex_lock( im__global_lock ); |
| total_mmap_usage -= window->length; |
| assert( total_mmap_usage >= 0 ); |
| g_mutex_unlock( im__global_lock ); |
| #endif /*DEBUG_TOTAL*/ |
| |
| window->data = NULL; |
| window->baseaddr = NULL; |
| window->length = 0; |
| } |
| |
| return( 0 ); |
| } |
| |
| static int |
| im_window_free( im_window_t *window ) |
| { |
| assert( window->ref_count == 0 ); |
| |
| #ifdef DEBUG |
| printf( "** im_window_free: window top = %d, height = %d (%p)\n", |
| window->top, window->height, window ); |
| #endif /*DEBUG*/ |
| |
| if( im_window_unmap( window ) ) |
| return( -1 ); |
| |
| window->im = NULL; |
| |
| im_free( window ); |
| |
| return( 0 ); |
| } |
| |
| int |
| im_window_unref( im_window_t *window ) |
| { |
| IMAGE *im = window->im; |
| |
| g_mutex_lock( im->sslock ); |
| |
| #ifdef DEBUG |
| printf( "im_window_unref: window top = %d, height = %d, count = %d\n", |
| window->top, window->height, window->ref_count ); |
| #endif /*DEBUG*/ |
| |
| assert( window->ref_count > 0 ); |
| |
| window->ref_count -= 1; |
| |
| if( window->ref_count == 0 ) { |
| assert( g_slist_find( im->windows, window ) ); |
| im->windows = g_slist_remove( im->windows, window ); |
| |
| #ifdef DEBUG |
| printf( "im_window_unref: %d windows left\n", |
| g_slist_length( im->windows ) ); |
| #endif /*DEBUG*/ |
| |
| if( im_window_free( window ) ) { |
| g_mutex_unlock( im->sslock ); |
| return( -1 ); |
| } |
| } |
| |
| g_mutex_unlock( im->sslock ); |
| |
| return( 0 ); |
| } |
| |
| #ifdef DEBUG_TOTAL |
| static void |
| trace_mmap_usage( void ) |
| { |
| g_mutex_lock( im__global_lock ); |
| { |
| static int last_total = 0; |
| int total = total_mmap_usage / (1024 * 1024); |
| int max = max_mmap_usage / (1024 * 1024); |
| |
| if( total != last_total ) { |
| printf( "im_window_set: current mmap " |
| "usage of ~%dMB (high water mark %dMB)\n", |
| total, max ); |
| last_total = total; |
| } |
| } |
| g_mutex_unlock( im__global_lock ); |
| } |
| #endif /*DEBUG_TOTAL*/ |
| |
| static int |
| im_getpagesize() |
| { |
| static int pagesize = 0; |
| |
| if( !pagesize ) { |
| #ifdef OS_WIN32 |
| SYSTEM_INFO si; |
| |
| GetSystemInfo( &si ); |
| |
| pagesize = si.dwAllocationGranularity; |
| #else /*OS_WIN32*/ |
| pagesize = getpagesize(); |
| #endif /*OS_WIN32*/ |
| |
| #ifdef DEBUG_TOTAL |
| printf( "im_getpagesize: 0x%x\n", pagesize ); |
| #endif /*DEBUG_TOTAL*/ |
| } |
| |
| return( pagesize ); |
| } |
| |
| /* Map a window into a file. |
| */ |
| static int |
| im_window_set( im_window_t *window, int top, int height ) |
| { |
| int pagesize = im_getpagesize(); |
| |
| void *baseaddr; |
| gint64 start, end, pagestart; |
| size_t length, pagelength; |
| |
| /* Calculate start and length for our window. |
| */ |
| start = window->im->sizeof_header + |
| (gint64) IM_IMAGE_SIZEOF_LINE( window->im ) * top; |
| length = (size_t) IM_IMAGE_SIZEOF_LINE( window->im ) * height; |
| |
| pagestart = start - start % pagesize; |
| end = start + length; |
| pagelength = end - pagestart; |
| |
| /* Make sure we have enough file. |
| */ |
| if( end > window->im->file_length ) { |
| im_error( "im_window_set", |
| _( "unable to read data for \"%s\", %s" ), |
| window->im->filename, _( "file has been truncated" ) ); |
| return( -1 ); |
| } |
| |
| if( !(baseaddr = im__mmap( window->im->fd, 0, pagelength, pagestart )) ) |
| return( -1 ); |
| |
| window->baseaddr = baseaddr; |
| window->length = pagelength; |
| |
| window->data = (char *) baseaddr + (start - pagestart); |
| window->top = top; |
| window->height = height; |
| |
| /* Sanity check ... make sure the data pointer is readable. |
| */ |
| im__read_test &= window->data[0]; |
| |
| #ifdef DEBUG_TOTAL |
| g_mutex_lock( im__global_lock ); |
| total_mmap_usage += window->length; |
| if( total_mmap_usage > max_mmap_usage ) |
| max_mmap_usage = total_mmap_usage; |
| g_mutex_unlock( im__global_lock ); |
| trace_mmap_usage(); |
| #endif /*DEBUG_TOTAL*/ |
| |
| return( 0 ); |
| } |
| |
| /* Make a new window. |
| */ |
| static im_window_t * |
| im_window_new( IMAGE *im, int top, int height ) |
| { |
| im_window_t *window; |
| |
| if( !(window = IM_NEW( NULL, im_window_t )) ) |
| return( NULL ); |
| |
| window->ref_count = 0; |
| window->im = im; |
| window->top = 0; |
| window->height = 0; |
| window->data = NULL; |
| window->baseaddr = NULL; |
| window->length = 0; |
| |
| if( im_window_set( window, top, height ) ) { |
| im_window_free( window ); |
| return( NULL ); |
| } |
| |
| im->windows = g_slist_prepend( im->windows, window ); |
| window->ref_count += 1; |
| |
| #ifdef DEBUG |
| printf( "** im_window_new: window top = %d, height = %d (%p)\n", |
| window->top, window->height, window ); |
| #endif /*DEBUG*/ |
| |
| return( window ); |
| } |
| |
| /* A request for an area of pixels. |
| */ |
| typedef struct { |
| int top; |
| int height; |
| } request_t; |
| |
| static void * |
| im_window_fits( im_window_t *window, request_t *req ) |
| { |
| if( window->top <= req->top && |
| window->top + window->height >= req->top + req->height ) |
| return( window ); |
| |
| return( NULL ); |
| } |
| |
| /* Find an existing window that fits within top/height and return a ref. |
| */ |
| static im_window_t * |
| im_window_find( IMAGE *im, int top, int height ) |
| { |
| request_t req; |
| im_window_t *window; |
| |
| req.top = top; |
| req.height = height; |
| window = im_slist_map2( im->windows, |
| (VSListMap2Fn) im_window_fits, &req, NULL ); |
| |
| if( window ) { |
| window->ref_count += 1; |
| |
| #ifdef DEBUG |
| printf( "im_window_find: ref window top = %d, height = %d, " |
| "count = %d\n", |
| top, height, window->ref_count ); |
| #endif /*DEBUG*/ |
| } |
| |
| return( window ); |
| } |
| |
| /* Return a ref to a window that encloses top/height. |
| */ |
| im_window_t * |
| im_window_ref( IMAGE *im, int top, int height ) |
| { |
| im_window_t *window; |
| |
| g_mutex_lock( im->sslock ); |
| |
| if( !(window = im_window_find( im, top, height )) ) { |
| /* No existing window ... make a new one. Ask for a larger |
| * window than we strictly need. There's no point making tiny |
| * windows. |
| */ |
| int margin = IM_MIN( im__window_margin_pixels, |
| im__window_margin_bytes / IM_IMAGE_SIZEOF_LINE( im ) ); |
| |
| top -= margin; |
| height += margin * 2; |
| |
| top = IM_CLIP( 0, top, im->Ysize - 1 ); |
| height = IM_CLIP( 0, height, im->Ysize - top ); |
| |
| if( !(window = im_window_new( im, top, height )) ) { |
| g_mutex_unlock( im->sslock ); |
| return( NULL ); |
| } |
| } |
| |
| g_mutex_unlock( im->sslock ); |
| |
| return( window ); |
| } |
| |
| void |
| im_window_print( im_window_t *window ) |
| { |
| printf( "im_window_t: %p ref_count = %d, ", window, window->ref_count ); |
| printf( "im = %p, ", window->im ); |
| printf( "top = %d, ", window->top ); |
| printf( "height = %d, ", window->height ); |
| printf( "data = %p, ", window->data ); |
| printf( "baseaddr = %p, ", window->baseaddr ); |
| printf( "length = %zd\n", window->length ); |
| } |