This is the mail archive of the java-discuss@sources.redhat.com mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: memory mapped i/o?


Followup on some discussion from a week or two back... at the end
of this note is some code that, at least on preliminary testing,
provides GCJ programs with the ability to access a memory mapped
file as a byte array -- zero copy I/O in GNU Java.

It's not been used or tested much, so "deep" problems won't have
shown their faces yet ... :-)

- Dave


------------------------------- CUT HERE
#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.2.1).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `!/bin/sh' line above, then type `sh FILE'.
#
# Existing files will *not* be overwritten unless `-c' is specified.
#
# This shar contains:
# length mode       name
# ------ ---------- ------------------------------------------
#    591 -rw-r--r-- Makefile
#   2808 -rw-r--r-- mmap.java
#   2831 -rw-r--r-- nat_mmap.cc
#
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=FAILED
locale_dir=FAILED
first_param="$1"
for dir in $PATH
do
  if test "$gettext_dir" = FAILED && test -f $dir/gettext \
     && ($dir/gettext --version >/dev/null 2>&1)
  then
    set `$dir/gettext --version 2>&1`
    if test "$3" = GNU
    then
      gettext_dir=$dir
    fi
  fi
  if test "$locale_dir" = FAILED && test -f $dir/shar \
     && ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
  then
    locale_dir=`$dir/shar --print-text-domain-dir`
  fi
done
IFS="$save_IFS"
if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
then
  echo=echo
else
  TEXTDOMAINDIR=$locale_dir
  export TEXTDOMAINDIR
  TEXTDOMAIN=sharutils
  export TEXTDOMAIN
  echo="$gettext_dir/gettext -s"
fi
if touch -am -t 200112312359.59 $$.touch >/dev/null 2>&1 && test ! -f
200112312359.59 -a -f $$.touch; then
  shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'
elif touch -am 123123592001.59 $$.touch >/dev/null 2>&1 && test ! -f 123123592001.59 -a
! -f 123123592001.5 -a -f $$.touch; then
  shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'
elif touch -am 1231235901 $$.touch >/dev/null 2>&1 && test ! -f 1231235901 -a -f
$$.touch; then
  shar_touch='touch -am $3$4$5$6$2 "$8"'
else
  shar_touch=:
  echo
  $echo 'WARNING: not restoring timestamps.  Consider getting and'
  $echo "installing GNU \`touch', distributed in GNU File Utilities..."
  echo
fi
rm -f 200112312359.59 123123592001.59 123123592001.5 1231235901 $$.touch
#
if mkdir _sh02031; then
  $echo 'x -' 'creating lock directory'
else
  $echo 'failed to create lock directory'
  exit 1
fi
# ============= Makefile ==============
if test -f 'Makefile' && test "$first_param" != -c; then
  $echo 'x -' SKIPPING 'Makefile' '(file already exists)'
else
  $echo 'x -' extracting 'Makefile' '(text)'
  sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
CCFLAGS = -g
X
SHARFILES = Makefile mmap.java nat_mmap.cc
X
############################
X
# This has worked with GCJ 2.96 ...
X
default: mmap_test
X
shar:  mmap.shar
X
clean:
X rm -f mmap.o nat_mmap.o mmap.h mmap.class \
X  mmap_test mmap.shar Log
X
############################
X
mmap_test: mmap.o nat_mmap.o
X gcj --main=mmap -o mmap_test mmap.o nat_mmap.o
X
nat_mmap.o: nat_mmap.cc mmap.h
X
mmap.h:  mmap.class
X gcjh mmap
X
mmap.class: mmap.java
X javac mmap.java
# gcj -femit-class-file mmap.java
X
mmap.o: mmap.java
X gcj -c $(CFLAGS) mmap.java
X
mmap.shar: $(SHARFILES)
X shar $(SHARFILES) > mmap.shar
SHAR_EOF
  (set 20 00 12 15 07 42 06 'Makefile'; eval "$shar_touch") &&
  chmod 0644 'Makefile' ||
  $echo 'restore of' 'Makefile' 'failed'
  if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
  && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
    md5sum -c << SHAR_EOF >/dev/null 2>&1 \
    || $echo 'Makefile:' 'MD5 check failed'
83762839714cf1ba2470f7f195eb5d7b  Makefile
SHAR_EOF
  else
    shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'Makefile'`"
    test 591 -eq "$shar_count" ||
    $echo 'Makefile:' 'original size' '591,' 'current size' "$shar_count!"
  fi
fi
# ============= mmap.java ==============
if test -f 'mmap.java' && test "$first_param" != -c; then
  $echo 'x -' SKIPPING 'mmap.java' '(file already exists)'
else
  $echo 'x -' extracting 'mmap.java' '(text)'
  sed 's/^X//' << 'SHAR_EOF' > 'mmap.java' &&
import java.io.File;
import java.io.IOException;
X
/**
X * Provides access to memory-mapped I/O buffers.  Instances of this
X * class wrap those buffers; the wrapper needs to exist so long as the
X * buffer is in use.  This implementation is specific to the GNU
X * Compiler for Java (GCJ).
X *
X * <p><em>Because the buffer will typically be invisible to the garbage
X * collector, applications must be careful!</em>  If you let the wrapper
X * get garbage collected (and finalized) while you're still using the
X * buffer, your application will try to access memory that vanished.
X * Similarly with calling <em>close()<em> too early.
X * In C/C++ that's a segmentation violation ...
X */
public final class mmap
{
X    private String  path;
X    private boolean  writable;
X
X    // this non-heap object is ignored by the GC ...
X    private byte  buffer [];
X
X    /**
X     * Constructs a wrapper for a memory-mapped I/O buffer.
X     *
X     * @see #getBuffer
X     *
X     * @param path name of file to be mapped
X     * @param writable true iff the
X     *
X     * @exception IOException indicates the file can't be mapped
X     * for any of several reasons
X     */
X    public mmap (String path, boolean writable)
X    throws IOException
X    {
X int code;
X
X this.path = path;
X this.writable = writable;
X
X code = createMap (path.getBytes ("UTF8"), writable);
X if (code != 0)
X     throw new IOException ("can't create mapping, errno"
X   + code);
X    }
X
X    /**
X     * Calls close().
X     */
X    protected void finalize ()
X    {
X close ();
X    }
X
X    /**
X     * Destroys the mapping for the buffer; call this only if
X     * you are certain that the buffer is no longer in use.
X     */
X    public void close ()
X    {
X if (buffer != null)
X     destroyMap ();
X    }
X
X    /**
X     * Returns a byte array which is memory-mapped to the file
X     * passed into the constructor.  It will not be writable unless
X     * the constructor was told to create a writable mapping.
X     */
X    public byte [] getBuffer ()
X { return buffer; }
X
X
X    private long  objHeader;
X    private long  objData, objLen;
X
X    private native int  createMap (byte path [], boolean writable);
X    private native void  destroyMap ();
X
X    /**
X     * FOR TESTING -- arguments are filenames which are mapped
X     * and then printed character by (ISO-Latin-1) character.
X     */
X    public static void main (String argv [])
X    {
X mmap memory;
X byte data [];
X
X for (int i = 0; i < argv.length; i++) {
X     try {
X  memory = new mmap (argv [i], false);
X  System.out.println ("mapping " + argv [i]);
X  data = memory.getBuffer ();
X  System.out.println ("length = " + data.length);
X  for (int j = 0; j < data.length; j++) {
X      System.out.print ((char) data [j]);
X  }
X  System.out.flush ();
X     } catch (Throwable t) {
X  t.printStackTrace ();
X     }
X }
X    }
X    /**/
}
SHAR_EOF
  (set 20 00 12 15 07 42 06 'mmap.java'; eval "$shar_touch") &&
  chmod 0644 'mmap.java' ||
  $echo 'restore of' 'mmap.java' 'failed'
  if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
  && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
    md5sum -c << SHAR_EOF >/dev/null 2>&1 \
    || $echo 'mmap.java:' 'MD5 check failed'
c5f5df540cb0abcbe03a5337c574815c  mmap.java
SHAR_EOF
  else
    shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'mmap.java'`"
    test 2808 -eq "$shar_count" ||
    $echo 'mmap.java:' 'original size' '2808,' 'current size' "$shar_count!"
  fi
fi
# ============= nat_mmap.cc ==============
if test -f 'nat_mmap.cc' && test "$first_param" != -c; then
  $echo 'x -' SKIPPING 'nat_mmap.cc' '(file already exists)'
else
  $echo 'x -' extracting 'nat_mmap.cc' '(text)'
  sed 's/^X//' << 'SHAR_EOF' > 'nat_mmap.cc' &&
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
X
#ifdef _POSIX_MAPPED_FILES
#   include <sys/mman.h>
#else
#   error "need some other memory mapping API"
#endif
X
#include <gcj/cni.h>
#include "mmap.h"
X
#define DEBUG
X
#ifndef DEBUG
#   define perror(x)
#else
#   include <stdio.h>
#endif
X
extern _Jv_VTable _Jv_byteVTable;  /* byte array vtable */
X
/*
X * This handcrafts a CNI object, as used with GCJ.
X *
X * The object is a byte array; no object pointers will ever be
X * found inside it, even if the GC somehow learns about it.
X */
jint
mmap::createMap (jbyteArray name, jboolean writable)
{
X    char *path = (char *) elements (name);
X    size_t pagesize = getpagesize ();
X    int  fd = open (path, writable ? O_RDWR : O_RDONLY);
X    struct stat statb;
X    int  zero;
X    char *header, *data;
X    jbyteArray array;
X    int  retval;
X
X    if (fd < 0) {
X retval = errno;
X     perror ("can't open file");
X return retval;
X    }
X    if (fstat (fd, &statb) < 0) {
X retval = errno;
X     perror ("can't fstat file");
done1:
X ::close (fd);
X return retval;
X    }
X    objLen = statb.st_size;
X
X    //
X    // map header page ... it goes right before the file
X    // we're mapping.  we make sure there's enough contiguous
X    // address space there for object header and data.
X    //
X    zero = open ("/dev/zero", O_RDWR);
X    if (zero < 0) {
X retval = errno;
X     perror ("can't open /dev/zero");
X goto done1;
X    }
X    header = (char *) ::mmap (0, pagesize + statb.st_size,
X PROT_READ | PROT_WRITE,
X MAP_PRIVATE,
X zero, 0);
X    if (header == MAP_FAILED) {
X retval = errno;
X     perror ("can't mmap /dev/zero");
done2:
X ::close (zero);
X goto done1;
X    }
X    data = header + pagesize;
X    if (munmap (data, statb.st_size) < 0) {
X retval = errno;
X     perror ("can't unmap /dev/zero");
X munmap (header, pagesize + statb.st_size);
X goto done2;
X    }
X
X    //
X    // remap the file's pages immediately after the header page
X    //
X    data = (char *) ::mmap (data, statb.st_size,
X writable ? (PROT_READ | PROT_WRITE) : PROT_READ,
X MAP_FIXED | MAP_SHARED,
X fd, 0);
X    if (data == MAP_FAILED) {
X retval = errno;
X     perror ("can't mmap data file");
done3:
X munmap (header, pagesize);
X goto done2;
X    }
X
X    ::close (zero);
X    ::close (fd);
X
X    //
X    // arrays in cni are contiguous:  header + data
X    // header init based on libgcj/libjava/prims.cc
X    // _Jv_NewPrimArray (jclass, count)
X    //
X    array = (jbyteArray) (data - sizeof *array);
X    // GCJ runtime creates synch monitors lazily
X    *((_Jv_VTable **) array) = &_Jv_byteVTable;
X    array->length = (jsize) statb.st_size;
X
X    // assert (elements (array) == (jbyte *) data);
X
X    buffer = array;
X    return 0;
}
X
void
mmap::destroyMap ()
{
X    buffer = 0;
X    ::munmap ((void *)objData, objLen);
X    ::munmap ((void *)objHeader, getpagesize ());
}
SHAR_EOF
  (set 20 00 12 15 07 42 06 'nat_mmap.cc'; eval "$shar_touch") &&
  chmod 0644 'nat_mmap.cc' ||
  $echo 'restore of' 'nat_mmap.cc' 'failed'
  if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
  && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
    md5sum -c << SHAR_EOF >/dev/null 2>&1 \
    || $echo 'nat_mmap.cc:' 'MD5 check failed'
a8f76edd1a153c678d4be71d662a51d9  nat_mmap.cc
SHAR_EOF
  else
    shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'nat_mmap.cc'`"
    test 2831 -eq "$shar_count" ||
    $echo 'nat_mmap.cc:' 'original size' '2831,' 'current size' "$shar_count!"
  fi
fi
rm -fr _sh02031
exit 0



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]