FileDescriptor의 native 메소드가 어떻게 리눅스와 윈도우에 포팅되었는지 확인한다.

 

1. 자바 소스

java.io.FileDescriptor.java

package java.io;

public final class FileDescriptor {

private int fd;

private long handle;

…..

public native void sync() throws SyncFailedException;

/* This routine initializes JNI field offsets for the class */
private static native void initIDs();

private static native long set(int d);

}

 

2, native 소스

jdk 6u 22  소스 - FileDescriptor_md.c

/*
* @(#)FileDescriptor_md.c 1.4 10/03/23
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "io_util.h"
#include "jlong.h"
#include "io_util_md.h"

#include "java_io_FileDescriptor.h"

/*******************************************************************/
/* BEGIN JNI ********* BEGIN JNI *********** BEGIN JNI ************/
/*******************************************************************/

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;

/* field id for jlong 'handle' in java.io.FileDescriptor */
jfieldID IO_handle_fdID;

/**************************************************************
* static methods to store field IDs in initializers
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
    IO_handle_fdID = (*env)->GetFieldID(env, fdClass, "handle", "J");
}

JNIEXPORT jlong JNICALL
Java_java_io_FileDescriptor_set(JNIEnv *env, jclass fdClass, jint fd) {
    SET_HANDLE(fd);
}

/**************************************************************
* File Descriptor
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
    FD fd = THIS_FD(this);
    if (IO_Sync(fd) == -1) {
        JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
    }
}

 

3. 윈도우 구현

윈도우에서는 SET_HANDLE과 IO_Sync을 다음과 같이 정의하였다.

src/windows/native/java/io/io_util_md.h 파일

SET_HANDLE 매크로에서 input,output,error에 값에 대한 GetStdHandle 함수를 사용한다. (http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx

JNIEXPORT int handleSync(jlong fd);

/*
* Setting the handle field in Java_java_io_FileDescriptor_set for
* standard handles stdIn, stdOut, stdErr
*/
#define SET_HANDLE(fd) \
if (fd == 0) { \
return (jlong)GetStdHandle(STD_INPUT_HANDLE); \
} else if (fd == 1) { \
return (jlong)GetStdHandle(STD_OUTPUT_HANDLE); \
} else if (fd == 2) { \
return (jlong)GetStdHandle(STD_ERROR_HANDLE); \
} else { \
return (jlong)-1; \
} \

#define IO_Sync handleSync


src/windows/native/java/io/io_util_md.c 파일

결국 자바의 sync는 윈도우의 FlushFileBuffers(핸들값) 함수를 이용한다.  (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=vs.85).aspx)

JNIEXPORT int
handleSync(jlong fd) {
/*
* From the documentation:
*
* On Windows NT, the function FlushFileBuffers fails if hFile
* is a handle to console output. That is because console
* output is not buffered. The function returns FALSE, and
* GetLastError returns ERROR_INVALID_HANDLE.
*
* On the other hand, on Win95, it returns without error. I cannot
* assume that 0, 1, and 2 are console, because if someone closes
* System.out and then opens a file, they might get file descriptor
* 1. An error on *that* version of 1 should be reported, whereas
* an error on System.out (which was the original 1) should be
* ignored. So I use isatty() to ensure that such an error was due
* to this bogosity, and if it was, I ignore the error.
*/

HANDLE handle = (HANDLE)fd;

if (!FlushFileBuffers(handle)) {
if (GetLastError() != ERROR_ACCESS_DENIED) { /* from winerror.h */
return -1;
}
}
return 0;
}

 

4. 리눅스 구현

src/sloaris/native/java/io/FileDescriptor_md.c

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;

/**************************************************************
* static methods to store field ID's in initializers
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
}

/**************************************************************
* File Descriptor
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
    int fd = (*env)->GetIntField(env, this, IO_fd_fdID);
    if (JVM_Sync(fd) == -1) {
    JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
    }   
}

 

src/solaris/native/java/io/io_util_md.h 파일

set_handle은 깔끔히 무시해준다.

/*
* Route the routines through HPI
*/

#define IO_Sync JVM_Sync

/*
* On Solaris, the handle field is unused
*/
#define SET_HANDLE(fd) return (jlong)-1

 

hotspot/src/share/vm/prims/jvm.cpp 파일

JVM_LEAF(jint, JVM_Sync(jint fd))
    JVMWrapper2("JVM_Sync (0x%x)", fd);
    //%note jvm_r6
    return hpi::fsync(fd);
JVM_END

 

JVMWrapper2 매크로는 jvm.cpp에 정의되어 있다. 특별한 일은 하지 않는다.

#define JVMWrapper2(arg1, arg2) JVMCountWrapper(arg1); JVMTraceWrapper(arg1, arg2)

#define JVMCountWrapper(arg) \
static JVMHistogramElement* e = new JVMHistogramElement(arg); \
if (e != NULL) e->increment_count(); // Due to bug in VC++, we need a NULL check here eventhough it should never happen!

class JVMTraceWrapper : public StackObj {
public:
JVMTraceWrapper(const char* format, ...) {
   if (TraceJVMCalls) {
        va_list ap;
        va_start(ap, format);
        tty->print("JVM ");
        tty->vprint_cr(format, ap);
        va_end(ap);
    }
}
};


hpi::fsync() 함수의 원형은 hotspot/src/share/vm/runtime/hpi.hpp, hotspot/src/share/vm/runtime/hpi.h 에 있다.

hotspot/src/share/vm/runtime/hpi.h

typedef struct {
  char *         (*NativePath)(char *path);
  int            (*FileType)(const char *path);
  int            (*Open)(const char *name, int openMode, int filePerm);
  int            (*Close)(int fd);
  jlong          (*Seek)(int fd, jlong offset, int whence);
  int            (*SetLength)(int fd, jlong length);
  int            (*Sync)(int fd);
  int            (*Available)(int fd, jlong *bytes);
  size_t         (*Read)(int fd, void *buf, unsigned int nBytes);
  size_t         (*Write)(int fd, const void *buf, unsigned int nBytes);
  int            (*FileSizeFD)(int fd, jlong *size);
} HPI_FileInterface;

 

hotspot/src/share/vm/runtime/hpi.cpp

//
// C++ wrapper to HPI.
//

class hpi : AllStatic {

static HPI_FileInterface* _file;

static inline int fsync(int fd);

HPIDECL(fsync, "fsync", _file, Sync, int, "%d",
(int fd),
("fd = %d", fd),
(fd));

}


#define HPIDECL(name, names, intf, func, ret_type, ret_fmt, arg_type, arg_print, arg) \
inline ret_type hpi::name arg_type { \
if (TraceHPI) { \
tty->print("hpi::" names "("); \
tty->print arg_print ; \
tty->print(") = "); \
} \
ret_type result = (*intf->func) arg ; \
if (TraceHPI) { \
tty->print_cr(ret_fmt, result); \
} \
return result; \
}

 

hpi.c

static HPI_FileInterface hpi_file_interface = {
sysNativePath,
sysFileType,
sysOpen,
sysClose,
sysSeek,
sysSetLength,
sysSync,
sysAvailable,
sysRead,
sysWrite,
sysFileSizeFD
};

 

hpi_impl.h

int sysSync(int fd);

 

sys_api_td.c

int
sysSync(int fd) {
/*
* XXX: Is fsync() interruptible by the interrupt method?
* Is so, add the TSD, sigsetjmp()/longjmp() code here.
*
* This probably shouldn't be throwing an error and should
* be a macro.
*/
int ret;
if ((ret = fsync(fd)) == -1) {
}
return ret;
}

 

리눅스의 sync 호출은 간단히 리눅스의 fsync 함수를 호출하는 효과와 동일하다.

 

마치며.

재미있는 것은 파일(file), 소켓(socket), 모니터(lock) 모두 jvm.cpp를 거쳐 hpi.hpp 로 내려오게 된다. 파일의 경우는 IO lock/unlock의 개념도 있고. 공부할 내용이 풍부하다..

이제 시간되는 대로 jvm 내용을 찾아 들어가면 될 것 같다.

Posted by '김용환'
,