This is the mail archive of the
java-discuss@sources.redhat.com
mailing list for the Java project.
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