如何在Java中映射(mmap)linux块设备(例如/ dev / sdb)?

时间:2013-04-09 12:24:56

标签: java java-native-interface mmap memory-mapping

我可以使用java.nio读取/编写带有Java的linux块设备。以下代码有效:

Path fp = FileSystems.getDefault().getPath("/dev", "sdb");
FileChannel fc = null;
try {
  fc = FileChannel.open(fp, EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE));
} catch (Exception e) {
  System.out.println("Error opening file: " + e.getMessage());
}
ByteBuffer buf = ByteBuffer.allocate(50);
try {
  if(fc != null)
    fc.write(buf);
} catch (Exception e) {
  System.out.println("Error writing to file: " + e.getMessage());
}

但是,内存映射不起作用。以下代码失败

MappedByteBuffer mbb = null;
try {
  mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 100);
} catch (IOException e) {
  System.out.println("Error mapping file: " + e.getMessage());
}

此操作失败,错误:

java.io.IOException: Invalid argument
    at sun.nio.ch.FileDispatcherImpl.truncate0(Native Method)
    at sun.nio.ch.FileDispatcherImpl.truncate(FileDispatcherImpl.java:79)
    at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:817)

有解决方法吗?也许通过使用不同的库?我在某个地方看过,也许是通过使用JNI,我可以做到这一点,但我找不到任何来源。

3 个答案:

答案 0 :(得分:3)

根据documentation,实际映射文件的机制留给实现。似乎实现正在尝试截断文件(可能是因为块设备大小与您指定的大小不同?)。

我很好奇为什么你直接从块设备读取(除非你试图编写某种文件系统实用程序或需要做原始I / O的东西)。如果需要直接从块设备读取内存映射文件,则可能需要编写一些C / C ++代码来映射文件并处理读/写操作并使用Java / JNI桥接类来桥接对您的调用C / C ++代码。这样你就可以自己处理调用mmap()并指定你需要的任何选项。查看mmap() documentation您可能无法在平台上指定块设备(我猜Linux但我可能错了)。

如果您绝对需要在Java中执行此操作,则可能需要执行具有适当长度和偏移量的read()调用和write()调用。

答案 1 :(得分:2)

我发现最简单的方法是使用JNA和一点sun.* / com.sun.*魔法。首先,你需要像这样包装libc

import com.sun.jna.LastErrorException;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

public interface LibC extends Library {
    LibC instance = (LibC) Native.loadLibrary("c", LibC.class);

    Pointer mmap(Pointer address, long length, 
                 int protect, int flags, int fd, 
                 long offset) throws LastErrorException;
    // implement more methods if you like
}

然后你差不多完成了!您所需要的只是获取文件描述符。这可能有点棘手:

RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
int fd = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess()
                               .get(randomAccessFile.getFD());

那就是它。现在你可以从java调用libc

Pointer result = LibC.instance.mmap(
    /*pass your desired parameters along with obtained fd*/
);

答案 2 :(得分:1)

FileChannel.map尝试将文件截断为指定大小:See the implementation

您需要获取块设备的大小并将该确切大小传递给地图调用。

如果实际设备的大小大于可用内存,请不要担心。操作系统将根据需要处理内存和内存交换页面。