본문 바로가기

OS

[JVM] Runtime.exec ~ waitFor()

Java에서 네이티브의 실행파일을 실행할 수 있게 되어 있다.

물론 OS마다 프로세스를 생성하는 방법은 다를 것이다. 윈도우라면 CreateProcess라는 Win32 API함수를 사용하고, UNIX 계열이라면 execvp와 같은 시스템 콜을 사용한다.


따라서 Java코드에서 Native 코드로 넘어가는 부분의 구현부의 차이를 Builder 패턴을 이용하여 구현을 해놓았다.


Process 클래스는 추상 클래스이다. 따라서 이 클래스를 실재 구현하고 있는 구체 클래스를 통해서 동작을 하는데,

Runtime클래스의 exec 메소드를 통해서 얻을 수 있고 직접 생성은 불가능하다.


exec 메소드는 여러가지로 오버라이드 되어 있는데, 결국 아래의 메소드가 끝판왕이다.

    public Process exec(String[] cmdarray, String[] envp, File dir)

        throws IOException {

        return new ProcessBuilder(cmdarray)

            .environment(envp)

            .directory(dir)

            .start();

    }


ProcessBuilder를 통해 인스턴스를 가져오고 start()가 인스턴스를 생성하게 해주는 메소드이다.

environment나 directory 메소드는 ProcessBuilder 타입을 받환하게 되어 있어 위 처럼 메서드 체인으로 사용을 할 수 있다.

ProcessBuilder environment(String[] envp) { ... }

public ProcessBuilder directory(File directory) { ... }


start() 메소드는 결국 ProcessImpl의 start() 메소드를 호출하게 되는데 이것이 추상 클래스 Process를 구현하고 있는 클래스이다.

    public Process start() throws IOException {

        // ...

        try {

            return ProcessImpl.start(cmdarray,

                                     environment,

                                     dir,

                                     redirectErrorStream);

        } catch (IOException e) {

            // ...

            throw new IOException( ... );

        }

    }


윈도우의 ProcessImpl

윈도우의 경우 내부적으로 create 메소드를 호출하고 이것은 JNI로 구현되어 있다.

JNI 명명 규칙에 의해 Java_java_lang_ProcessImpl_create 함수가 호출된다.

참고로 long 타입으로 넘어오는 값은 에러코드이거나 프로세스 핸들 값이다.

final class ProcessImpl extends Process {

    // ...

    private long handle = 0;

    // ...

    static Process start(String cmdarray[],

                         java.util.Map<String,String> environment,

                         String dir,

                         boolean redirectErrorStream)

        throws IOException

    {

        // ...

        handle = create(cmdstr, envblock, path, redirectErrorStream,

                        stdin_fd, stdout_fd, stderr_fd);

        // ...

    }

    // ...

    private static native long create(String cmdstr,

                                      String envblock,

                                      String dir,

                                      boolean redirectErrorStream,

                                      FileDescriptor in_fd,

                                      FileDescriptor out_fd,

                                      FileDescriptor err_fd)

        throws IOException;

    // ...

}


결국 내부에서는 CreateProcess가 호출된다.

JNIEXPORT jlong JNICALL

Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,

                                  jstring cmd,

                                  jstring envBlock,

                                  jstring dir,

                                  jboolean redirectErrorStream,

                                  jobject in_fd,

                                  jobject out_fd,

                                  jobject err_fd)

{

   // ...

    jlong ret = 0;

    PROCESS_INFORMATION pi;

   // ...

    ret = CreateProcess(0,           /* executable name */

                        pcmd,        /* command line */

                        0,           /* process security attribute */

                        0,           /* thread security attribute */

                        TRUE,        /* inherits system handles */

                        processFlag, /* selected based on exe type */

                        penvBlock,   /* environment block */

                        pdir,        /* change to the new current directory */

                        &si,         /* (in)  startup information */

                        &pi);        /* (out) process information */

   // ...

    ret = (jlong)pi.hProcess;

   // ...

    return ret;

   // ...

}


waitFor() 메소드

waitFor를 호출하면 프로세스가 종료될 때까지 블록이 되는데 이것은 C로 대략 옮겨보면 아래와 같다.

PROCESS_INFORMATION processInformation = {0};

DWORD exitCode;

BOOL result;

CreateProcess(NULL, 실행파일 정보, 

                               NULL, NULL, FALSE, 

                               NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, 

                               NULL, NULL, &startupInfo, &processInformation);

WaitForSingleObject( processInformation.hProcess, INFINITE );

result = GetExitCodeProcess(processInformation.hProcess, &exitCode);


Process 추상 클래스는 아래와 같이 되어 있다.

abstract public int waitFor() throws InterruptedException;


실제로 구현은 ProcessImpl이 한다. JNI의 waitForInterruptibly를 호출한다.

 waitFor -> waitForInterruptibly --native-> Java_java_lang_ProcessImpl_waitForInterruptibly -> WaitForMultipleObjects

 exitValue -> getExitCodeProcess --native-> Java_java_lang_ProcessImpl_getExitCodeProcess -> GetExitCodeProcess

final class ProcessImpl extends Process {

    // ..

    public int exitValue() {

        int exitCode = getExitCodeProcess(handle);

        if (exitCode == STILL_ACTIVE)

            throw new IllegalThreadStateException("process has not exited");

        return exitCode;

    }

    private static native int getExitCodeProcess(long handle);


    public int waitFor() throws InterruptedException {

        waitForInterruptibly(handle);

        if (Thread.interrupted())

            throw new InterruptedException();

        return exitValue();

    }

    private static native void waitForInterruptibly(long handle);

    // ..

}


플랫폼에 맞게 구현된 Java_java_lang_ProcessImpl_waitForInterruptibly가 호출이 된다. (아래는 윈도우)

JNIEXPORT void JNICALL

Java_java_lang_ProcessImpl_waitForInterruptibly(JNIEnv *env, jclass ignored, jlong handle)

{

    HANDLE events[2];

    events[0] = (HANDLE) handle;

    events[1] = JVM_GetThreadInterruptEvent();


    if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,

                               FALSE,    /* Wait for ANY event */

                               INFINITE) /* Wait forever */

        == WAIT_FAILED)

        win32Error(env, "WaitForMultipleObjects");

}

C로 짠 코드랑 비슷하지 아니한가? 물론 WaitForSingleObject대신에 WaitForMultipleObjects가 쓰인 것이 다르긴 하다.


리턴 코드를 받는 부분은 Java_java_lang_ProcessImpl_getExitCodeProcess가 담당한다.

JNIEXPORT jint JNICALL

Java_java_lang_ProcessImpl_getExitCodeProcess(JNIEnv *env, jclass ignored, jlong handle)

{

    DWORD exit_code;

    if (GetExitCodeProcess((HANDLE) handle, &exit_code) == 0)

        win32Error(env, "GetExitCodeProcess");

    return exit_code;

}